1 /* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
4 import * as chai from 'chai'
5 import { buildDigest } from '@server/helpers/peertube-crypto'
6 import { HttpStatusCode } from '../../../../shared/core-utils/miscs/http-error-codes'
8 buildAbsoluteFixturePath,
10 createMultipleServers,
14 } from '../../../../shared/extra-utils'
15 import { makeFollowRequest, makePOSTAPRequest } from '../../../../shared/extra-utils/requests/activitypub'
16 import { activityPubContextify, buildSignedActivity } from '../../../helpers/activitypub'
17 import { HTTP_SIGNATURE } from '../../../initializers/constants'
18 import { buildGlobalHeaders } from '../../../lib/job-queue/handlers/utils/activitypub-http-utils'
20 const expect = chai.expect
22 function setKeysOfServer (onServer: PeerTubeServer, ofServer: PeerTubeServer, publicKey: string, privateKey: string) {
23 const url = 'http://localhost:' + ofServer.port + '/accounts/peertube'
26 onServer.sql.setActorField(url, 'publicKey', publicKey),
27 onServer.sql.setActorField(url, 'privateKey', privateKey)
31 function setUpdatedAtOfServer (onServer: PeerTubeServer, ofServer: PeerTubeServer, updatedAt: string) {
32 const url = 'http://localhost:' + ofServer.port + '/accounts/peertube'
35 onServer.sql.setActorField(url, 'createdAt', updatedAt),
36 onServer.sql.setActorField(url, 'updatedAt', updatedAt)
40 function getAnnounceWithoutContext (server: PeerTubeServer) {
41 const json = require(buildAbsoluteFixturePath('./ap-json/peertube/announce-without-context.json'))
42 const result: typeof json = {}
44 for (const key of Object.keys(json)) {
45 if (Array.isArray(json[key])) {
46 result[key] = json[key].map(v => v.replace(':9002', `:${server.port}`))
48 result[key] = json[key].replace(':9002', `:${server.port}`)
55 describe('Test ActivityPub security', function () {
56 let servers: PeerTubeServer[]
59 const keys = require(buildAbsoluteFixturePath('./ap-json/peertube/keys.json'))
60 const invalidKeys = require(buildAbsoluteFixturePath('./ap-json/peertube/invalid-keys.json'))
61 const baseHttpSignature = () => ({
62 algorithm: HTTP_SIGNATURE.ALGORITHM,
63 authorizationHeaderName: HTTP_SIGNATURE.HEADER_NAME,
64 keyId: 'acct:peertube@localhost:' + servers[1].port,
66 headers: HTTP_SIGNATURE.HEADERS_TO_SIGN
69 // ---------------------------------------------------------------
71 before(async function () {
74 servers = await createMultipleServers(3)
76 url = servers[0].url + '/inbox'
78 await setKeysOfServer(servers[0], servers[1], keys.publicKey, null)
79 await setKeysOfServer(servers[1], servers[1], keys.publicKey, keys.privateKey)
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 }
83 await makeFollowRequest(to, by)
86 describe('When checking HTTP signature', function () {
88 it('Should fail with an invalid digest', async function () {
89 const body = activityPubContextify(getAnnounceWithoutContext(servers[1]))
91 Digest: buildDigest({ hello: 'coucou' })
95 await makePOSTAPRequest(url, body, baseHttpSignature(), headers)
96 expect(true, 'Did not throw').to.be.false
98 expect(err.statusCode).to.equal(HttpStatusCode.FORBIDDEN_403)
102 it('Should fail with an invalid date', async function () {
103 const body = activityPubContextify(getAnnounceWithoutContext(servers[1]))
104 const headers = buildGlobalHeaders(body)
105 headers['date'] = 'Wed, 21 Oct 2015 07:28:00 GMT'
108 await makePOSTAPRequest(url, body, baseHttpSignature(), headers)
109 expect(true, 'Did not throw').to.be.false
111 expect(err.statusCode).to.equal(HttpStatusCode.FORBIDDEN_403)
115 it('Should fail with bad keys', async function () {
116 await setKeysOfServer(servers[0], servers[1], invalidKeys.publicKey, invalidKeys.privateKey)
117 await setKeysOfServer(servers[1], servers[1], invalidKeys.publicKey, invalidKeys.privateKey)
119 const body = activityPubContextify(getAnnounceWithoutContext(servers[1]))
120 const headers = buildGlobalHeaders(body)
123 await makePOSTAPRequest(url, body, baseHttpSignature(), headers)
124 expect(true, 'Did not throw').to.be.false
126 expect(err.statusCode).to.equal(HttpStatusCode.FORBIDDEN_403)
130 it('Should reject requests without appropriate signed headers', async function () {
131 await setKeysOfServer(servers[0], servers[1], keys.publicKey, keys.privateKey)
132 await setKeysOfServer(servers[1], servers[1], keys.publicKey, keys.privateKey)
134 const body = activityPubContextify(getAnnounceWithoutContext(servers[1]))
135 const headers = buildGlobalHeaders(body)
137 const signatureOptions = baseHttpSignature()
138 const badHeadersMatrix = [
139 [ '(request-target)', 'date', 'digest' ],
140 [ 'host', 'date', 'digest' ],
141 [ '(request-target)', 'host', 'digest' ]
144 for (const badHeaders of badHeadersMatrix) {
145 signatureOptions.headers = badHeaders
148 await makePOSTAPRequest(url, body, signatureOptions, headers)
149 expect(true, 'Did not throw').to.be.false
151 expect(err.statusCode).to.equal(HttpStatusCode.FORBIDDEN_403)
156 it('Should succeed with a valid HTTP signature', async function () {
157 const body = activityPubContextify(getAnnounceWithoutContext(servers[1]))
158 const headers = buildGlobalHeaders(body)
160 const { statusCode } = await makePOSTAPRequest(url, body, baseHttpSignature(), headers)
161 expect(statusCode).to.equal(HttpStatusCode.NO_CONTENT_204)
164 it('Should refresh the actor keys', async function () {
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)
170 await setUpdatedAtOfServer(servers[0], servers[1], '2015-07-17 22:00:00+00')
172 // Invalid peertube actor cache
173 await killallServers([ servers[1] ])
174 await servers[1].run()
176 const body = activityPubContextify(getAnnounceWithoutContext(servers[1]))
177 const headers = buildGlobalHeaders(body)
180 await makePOSTAPRequest(url, body, baseHttpSignature(), headers)
181 expect(true, 'Did not throw').to.be.false
184 expect(err.statusCode).to.equal(HttpStatusCode.FORBIDDEN_403)
189 describe('When checking Linked Data Signature', function () {
190 before(async function () {
193 await setKeysOfServer(servers[0], servers[1], keys.publicKey, keys.privateKey)
194 await setKeysOfServer(servers[1], servers[1], keys.publicKey, keys.privateKey)
195 await setKeysOfServer(servers[2], servers[2], keys.publicKey, keys.privateKey)
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 }
199 await makeFollowRequest(to, by)
202 it('Should fail with bad keys', async function () {
205 await setKeysOfServer(servers[0], servers[2], invalidKeys.publicKey, invalidKeys.privateKey)
206 await setKeysOfServer(servers[2], servers[2], invalidKeys.publicKey, invalidKeys.privateKey)
208 const body = getAnnounceWithoutContext(servers[1])
209 body.actor = 'http://localhost:' + servers[2].port + '/accounts/peertube'
211 const signer: any = { privateKey: invalidKeys.privateKey, url: 'http://localhost:' + servers[2].port + '/accounts/peertube' }
212 const signedBody = await buildSignedActivity(signer, body)
214 const headers = buildGlobalHeaders(signedBody)
217 await makePOSTAPRequest(url, signedBody, baseHttpSignature(), headers)
218 expect(true, 'Did not throw').to.be.false
220 expect(err.statusCode).to.equal(HttpStatusCode.FORBIDDEN_403)
224 it('Should fail with an altered body', async function () {
227 await setKeysOfServer(servers[0], servers[2], keys.publicKey, keys.privateKey)
228 await setKeysOfServer(servers[0], servers[2], keys.publicKey, keys.privateKey)
230 const body = getAnnounceWithoutContext(servers[1])
231 body.actor = 'http://localhost:' + servers[2].port + '/accounts/peertube'
233 const signer: any = { privateKey: keys.privateKey, url: 'http://localhost:' + servers[2].port + '/accounts/peertube' }
234 const signedBody = await buildSignedActivity(signer, body)
236 signedBody.actor = 'http://localhost:' + servers[2].port + '/account/peertube'
238 const headers = buildGlobalHeaders(signedBody)
241 await makePOSTAPRequest(url, signedBody, baseHttpSignature(), headers)
242 expect(true, 'Did not throw').to.be.false
244 expect(err.statusCode).to.equal(HttpStatusCode.FORBIDDEN_403)
248 it('Should succeed with a valid signature', async function () {
251 const body = getAnnounceWithoutContext(servers[1])
252 body.actor = 'http://localhost:' + servers[2].port + '/accounts/peertube'
254 const signer: any = { privateKey: keys.privateKey, url: 'http://localhost:' + servers[2].port + '/accounts/peertube' }
255 const signedBody = await buildSignedActivity(signer, body)
257 const headers = buildGlobalHeaders(signedBody)
259 const { statusCode } = await makePOSTAPRequest(url, signedBody, baseHttpSignature(), headers)
260 expect(statusCode).to.equal(HttpStatusCode.NO_CONTENT_204)
263 it('Should refresh the actor keys', async function () {
266 // Wait refresh invalidation
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)
273 const body = getAnnounceWithoutContext(servers[1])
274 body.actor = 'http://localhost:' + servers[2].port + '/accounts/peertube'
276 const signer: any = { privateKey: keys.privateKey, url: 'http://localhost:' + servers[2].port + '/accounts/peertube' }
277 const signedBody = await buildSignedActivity(signer, body)
279 const headers = buildGlobalHeaders(signedBody)
282 await makePOSTAPRequest(url, signedBody, baseHttpSignature(), headers)
283 expect(true, 'Did not throw').to.be.false
285 expect(err.statusCode).to.equal(HttpStatusCode.FORBIDDEN_403)
290 after(async function () {
293 await cleanupTests(servers)