]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/blame - server/tests/api/activitypub/security.ts
Move test functions outside extra-utils
[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'
c0e8b12e 5import { activityPubContextify, buildSignedActivity } from '@server/helpers/activitypub'
8dc8a34e 6import { buildDigest } from '@server/helpers/peertube-crypto'
c0e8b12e
C
7import { HTTP_SIGNATURE } from '@server/initializers/constants'
8import { buildGlobalHeaders } from '@server/lib/job-queue/handlers/utils/activitypub-http-utils'
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 () {
48f07b4a 83 const body = activityPubContextify(getAnnounceWithoutContext(servers[1]))
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 () {
48f07b4a 97 const body = activityPubContextify(getAnnounceWithoutContext(servers[1]))
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
48f07b4a 113 const body = activityPubContextify(getAnnounceWithoutContext(servers[1]))
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
48f07b4a 128 const body = activityPubContextify(getAnnounceWithoutContext(servers[1]))
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
150 it('Should succeed with a valid HTTP signature', async function () {
151 const body = activityPubContextify(getAnnounceWithoutContext(servers[1]))
152 const headers = buildGlobalHeaders(body)
df66d815 153
db4b15f2 154 const { statusCode } = await makePOSTAPRequest(url, body, baseHttpSignature(), headers)
db4b15f2 155 expect(statusCode).to.equal(HttpStatusCode.NO_CONTENT_204)
df66d815 156 })
095e2258
C
157
158 it('Should refresh the actor keys', async function () {
159 this.timeout(20000)
160
095e2258
C
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)
e7053b1d
C
164 await setUpdatedAtOfServer(servers[0], servers[1], '2015-07-17 22:00:00+00')
165
166 // Invalid peertube actor cache
9293139f 167 await killallServers([ servers[1] ])
254d3579 168 await servers[1].run()
095e2258
C
169
170 const body = activityPubContextify(getAnnounceWithoutContext(servers[1]))
171 const headers = buildGlobalHeaders(body)
172
b5c36108
C
173 try {
174 await makePOSTAPRequest(url, body, baseHttpSignature(), headers)
175 expect(true, 'Did not throw').to.be.false
176 } catch (err) {
e7053b1d 177 console.error(err)
b5c36108
C
178 expect(err.statusCode).to.equal(HttpStatusCode.FORBIDDEN_403)
179 }
095e2258 180 })
df66d815
C
181 })
182
183 describe('When checking Linked Data Signature', function () {
095e2258
C
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)
48f07b4a 189 await setKeysOfServer(servers[2], servers[2], keys.publicKey, keys.privateKey)
df66d815 190
48f07b4a
C
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 }
df66d815
C
193 await makeFollowRequest(to, by)
194 })
195
196 it('Should fail with bad keys', async function () {
197 this.timeout(10000)
198
48f07b4a
C
199 await setKeysOfServer(servers[0], servers[2], invalidKeys.publicKey, invalidKeys.privateKey)
200 await setKeysOfServer(servers[2], servers[2], invalidKeys.publicKey, invalidKeys.privateKey)
df66d815 201
48f07b4a
C
202 const body = getAnnounceWithoutContext(servers[1])
203 body.actor = 'http://localhost:' + servers[2].port + '/accounts/peertube'
df66d815 204
48f07b4a 205 const signer: any = { privateKey: invalidKeys.privateKey, url: 'http://localhost:' + servers[2].port + '/accounts/peertube' }
df66d815
C
206 const signedBody = await buildSignedActivity(signer, body)
207
208 const headers = buildGlobalHeaders(signedBody)
209
b5c36108
C
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 }
df66d815
C
216 })
217
218 it('Should fail with an altered body', async function () {
219 this.timeout(10000)
220
48f07b4a
C
221 await setKeysOfServer(servers[0], servers[2], keys.publicKey, keys.privateKey)
222 await setKeysOfServer(servers[0], servers[2], keys.publicKey, keys.privateKey)
df66d815 223
48f07b4a
C
224 const body = getAnnounceWithoutContext(servers[1])
225 body.actor = 'http://localhost:' + servers[2].port + '/accounts/peertube'
df66d815 226
48f07b4a 227 const signer: any = { privateKey: keys.privateKey, url: 'http://localhost:' + servers[2].port + '/accounts/peertube' }
df66d815
C
228 const signedBody = await buildSignedActivity(signer, body)
229
48f07b4a 230 signedBody.actor = 'http://localhost:' + servers[2].port + '/account/peertube'
df66d815
C
231
232 const headers = buildGlobalHeaders(signedBody)
233
b5c36108
C
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 }
df66d815
C
240 })
241
242 it('Should succeed with a valid signature', async function () {
243 this.timeout(10000)
244
48f07b4a
C
245 const body = getAnnounceWithoutContext(servers[1])
246 body.actor = 'http://localhost:' + servers[2].port + '/accounts/peertube'
df66d815 247
48f07b4a 248 const signer: any = { privateKey: keys.privateKey, url: 'http://localhost:' + servers[2].port + '/accounts/peertube' }
df66d815
C
249 const signedBody = await buildSignedActivity(signer, body)
250
251 const headers = buildGlobalHeaders(signedBody)
252
db4b15f2 253 const { statusCode } = await makePOSTAPRequest(url, signedBody, baseHttpSignature(), headers)
db4b15f2 254 expect(statusCode).to.equal(HttpStatusCode.NO_CONTENT_204)
df66d815 255 })
095e2258
C
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
b5c36108
C
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 }
095e2258 281 })
df66d815
C
282 })
283
284 after(async function () {
48f07b4a
C
285 this.timeout(10000)
286
287 await cleanupTests(servers)
df66d815
C
288 })
289})