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