Add compatibility with other Linked Signature algorithms

This commit is contained in:
Chocobozzz 2018-10-23 11:38:48 +02:00
parent b83b8dd5ae
commit df66d81583
No known key found for this signature in database
GPG Key ID: 583A612D890159BE
28 changed files with 1012 additions and 22 deletions

View File

@ -12,7 +12,6 @@ killall -q peertube || true
if [ "$1" = "misc" ]; then
npm run build -- --light-fr
mocha --timeout 5000 --exit --require ts-node/register/type-check --bail server/tests/client.ts \
server/tests/activitypub.ts \
server/tests/feeds/index.ts \
server/tests/misc-endpoints.ts \
server/tests/helpers/index.ts

View File

@ -28,7 +28,7 @@ import { checkMissedConfig, checkFFmpeg } from './server/initializers/checker-be
// Do not use barrels because we don't want to load all modules here (we need to initialize database first)
import { logger } from './server/helpers/logger'
import { API_VERSION, CONFIG, CACHE } from './server/initializers/constants'
import { API_VERSION, CONFIG, CACHE, HTTP_SIGNATURE } from './server/initializers/constants'
const missed = checkMissedConfig()
if (missed.length !== 0) {
@ -96,6 +96,7 @@ import { RemoveOldJobsScheduler } from './server/lib/schedulers/remove-old-jobs-
import { UpdateVideosScheduler } from './server/lib/schedulers/update-videos-scheduler'
import { YoutubeDlUpdateScheduler } from './server/lib/schedulers/youtube-dl-update-scheduler'
import { VideosRedundancyScheduler } from './server/lib/schedulers/videos-redundancy-scheduler'
import { isHTTPSignatureDigestValid } from './server/helpers/peertube-crypto'
// ----------- Command line -----------
@ -131,7 +132,11 @@ app.use(morgan('combined', {
app.use(bodyParser.urlencoded({ extended: false }))
app.use(bodyParser.json({
type: [ 'application/json', 'application/*+json' ],
limit: '500kb'
limit: '500kb',
verify: (req: express.Request, _, buf: Buffer, encoding: string) => {
const valid = isHTTPSignatureDigestValid(buf, req)
if (valid !== true) throw new Error('Invalid digest')
}
}))
// Cookies
app.use(cookieParser())

View File

@ -1,5 +1,5 @@
import * as AsyncLRU from 'async-lru'
import * as jsonld from 'jsonld/'
import * as jsonld from 'jsonld'
import * as jsig from 'jsonld-signatures'
const nodeDocumentLoader = jsonld.documentLoaders.node()
@ -17,4 +17,4 @@ jsonld.documentLoader = (url, cb) => {
jsig.use('jsonld', jsonld)
export { jsig }
export { jsig, jsonld }

View File

@ -1,9 +1,12 @@
import { Request } from 'express'
import { BCRYPT_SALT_SIZE, HTTP_SIGNATURE, PRIVATE_RSA_KEY_SIZE } from '../initializers'
import { ActorModel } from '../models/activitypub/actor'
import { bcryptComparePromise, bcryptGenSaltPromise, bcryptHashPromise, createPrivateKey, getPublicKey } from './core-utils'
import { jsig } from './custom-jsonld-signature'
import { bcryptComparePromise, bcryptGenSaltPromise, bcryptHashPromise, createPrivateKey, getPublicKey, sha256 } from './core-utils'
import { jsig, jsonld } from './custom-jsonld-signature'
import { logger } from './logger'
import { cloneDeep } from 'lodash'
import { createVerify } from 'crypto'
import { buildDigest } from '../lib/job-queue/handlers/utils/activitypub-http-utils'
const httpSignature = require('http-signature')
@ -30,21 +33,36 @@ async function cryptPassword (password: string) {
// HTTP Signature
function isHTTPSignatureVerified (httpSignatureParsed: any, actor: ActorModel) {
function isHTTPSignatureDigestValid (rawBody: Buffer, req: Request): boolean {
if (req.headers[HTTP_SIGNATURE.HEADER_NAME] && req.headers['digest']) {
return buildDigest(rawBody.toString()) === req.headers['digest']
}
return true
}
function isHTTPSignatureVerified (httpSignatureParsed: any, actor: ActorModel): boolean {
return httpSignature.verifySignature(httpSignatureParsed, actor.publicKey) === true
}
function parseHTTPSignature (req: Request) {
return httpSignature.parse(req, { authorizationHeaderName: HTTP_SIGNATURE.HEADER_NAME })
function parseHTTPSignature (req: Request, clockSkew?: number) {
return httpSignature.parse(req, { authorizationHeaderName: HTTP_SIGNATURE.HEADER_NAME, clockSkew })
}
// JSONLD
function isJsonLDSignatureVerified (fromActor: ActorModel, signedDocument: any) {
async function isJsonLDSignatureVerified (fromActor: ActorModel, signedDocument: any): Promise<boolean> {
if (signedDocument.signature.type === 'RsaSignature2017') {
// Mastodon algorithm
const res = await isJsonLDRSA2017Verified(fromActor, signedDocument)
// Success? If no, try with our library
if (res === true) return true
}
const publicKeyObject = {
'@context': jsig.SECURITY_CONTEXT_URL,
id: fromActor.url,
type: 'CryptographicKey',
type: 'CryptographicKey',
owner: fromActor.url,
publicKeyPem: fromActor.publicKey
}
@ -69,6 +87,44 @@ function isJsonLDSignatureVerified (fromActor: ActorModel, signedDocument: any)
})
}
// Backward compatibility with "other" implementations
async function isJsonLDRSA2017Verified (fromActor: ActorModel, signedDocument: any) {
function hash (obj: any): Promise<any> {
return jsonld.promises
.normalize(obj, {
algorithm: 'URDNA2015',
format: 'application/n-quads'
})
.then(res => sha256(res))
}
const signatureCopy = cloneDeep(signedDocument.signature)
Object.assign(signatureCopy, {
'@context': [
'https://w3id.org/security/v1',
{ RsaSignature2017: 'https://w3id.org/security#RsaSignature2017' }
]
})
delete signatureCopy.type
delete signatureCopy.id
delete signatureCopy.signatureValue
const docWithoutSignature = cloneDeep(signedDocument)
delete docWithoutSignature.signature
const [ documentHash, optionsHash ] = await Promise.all([
hash(docWithoutSignature),
hash(signatureCopy)
])
const toVerify = optionsHash + documentHash
const verify = createVerify('RSA-SHA256')
verify.update(toVerify, 'utf8')
return verify.verify(fromActor.publicKey, signedDocument.signature.signatureValue, 'base64')
}
function signJsonLDObject (byActor: ActorModel, data: any) {
const options = {
privateKeyPem: byActor.privateKey,
@ -82,6 +138,7 @@ function signJsonLDObject (byActor: ActorModel, data: any) {
// ---------------------------------------------------------------------------
export {
isHTTPSignatureDigestValid,
parseHTTPSignature,
isHTTPSignatureVerified,
isJsonLDSignatureVerified,

View File

@ -535,7 +535,7 @@ const ACTIVITY_PUB_ACTOR_TYPES: { [ id: string ]: ActivityPubActorType } = {
const HTTP_SIGNATURE = {
HEADER_NAME: 'signature',
ALGORITHM: 'rsa-sha256',
HEADERS_TO_SIGN: [ 'date', 'host', 'digest', '(request-target)' ]
HEADERS_TO_SIGN: [ '(request-target)', 'host', 'date', 'digest' ]
}
// ---------------------------------------------------------------------------

View File

@ -38,15 +38,20 @@ async function buildSignedRequestOptions (payload: Payload) {
}
}
function buildGlobalHeaders (body: object) {
const digest = 'SHA-256=' + sha256(JSON.stringify(body), 'base64')
function buildGlobalHeaders (body: any) {
return {
'Digest': digest
'Digest': buildDigest(body)
}
}
function buildDigest (body: any) {
const rawBody = typeof body === 'string' ? body : JSON.stringify(body)
return 'SHA-256=' + sha256(rawBody, 'base64')
}
export {
buildDigest,
buildGlobalHeaders,
computeBody,
buildSignedRequestOptions

View File

@ -53,7 +53,8 @@ function executeIfActivityPub (fun: RequestHandler | RequestHandler[]) {
export {
checkSignature,
executeIfActivityPub
executeIfActivityPub,
checkHttpSignature
}
// ---------------------------------------------------------------------------
@ -94,7 +95,7 @@ async function checkHttpSignature (req: Request, res: Response) {
async function checkJsonLDSignature (req: Request, res: Response) {
const signatureObject: ActivityPubSignature = req.body.signature
if (!signatureObject.creator) {
if (!signatureObject || !signatureObject.creator) {
res.sendStatus(403)
return false
}

View File

@ -2,7 +2,7 @@
import * as chai from 'chai'
import 'mocha'
import { flushTests, killallServers, makeActivityPubGetRequest, runServer, ServerInfo, setAccessTokensToServers } from './utils'
import { flushTests, killallServers, makeActivityPubGetRequest, runServer, ServerInfo, setAccessTokensToServers } from '../../utils'
const expect = chai.expect

View File

@ -0,0 +1,182 @@
/* tslint:disable:no-unused-expression */
import 'mocha'
import { expect } from 'chai'
import { buildRequestStub } from '../../utils'
import { isHTTPSignatureVerified, isJsonLDSignatureVerified, parseHTTPSignature } from '../../../helpers/peertube-crypto'
import { cloneDeep } from 'lodash'
import { buildSignedActivity } from '../../../helpers/activitypub'
describe('Test activity pub helpers', function () {
describe('When checking the Linked Signature', function () {
it('Should fail with an invalid Mastodon signature', async function () {
const body = require('./json/mastodon/create-bad-signature.json')
const publicKey = require('./json/mastodon/public-key.json').publicKey
const fromActor = { publicKey, url: 'http://localhost:9002/accounts/peertube' }
const result = await isJsonLDSignatureVerified(fromActor as any, body)
expect(result).to.be.false
})
it('Should fail with an invalid public key', async function () {
const body = require('./json/mastodon/create.json')
const publicKey = require('./json/mastodon/bad-public-key.json').publicKey
const fromActor = { publicKey, url: 'http://localhost:9002/accounts/peertube' }
const result = await isJsonLDSignatureVerified(fromActor as any, body)
expect(result).to.be.false
})
it('Should succeed with a valid Mastodon signature', async function () {
const body = require('./json/mastodon/create.json')
const publicKey = require('./json/mastodon/public-key.json').publicKey
const fromActor = { publicKey, url: 'http://localhost:9002/accounts/peertube' }
const result = await isJsonLDSignatureVerified(fromActor as any, body)
expect(result).to.be.true
})
it('Should fail with an invalid PeerTube signature', async function () {
const keys = require('./json/peertube/invalid-keys.json')
const body = require('./json/peertube/announce-without-context.json')
const actorSignature = { url: 'http://localhost:9002/accounts/peertube', privateKey: keys.privateKey }
const signedBody = await buildSignedActivity(actorSignature as any, body)
const fromActor = { publicKey: keys.publicKey, url: 'http://localhost:9002/accounts/peertube' }
const result = await isJsonLDSignatureVerified(fromActor as any, signedBody)
expect(result).to.be.false
})
it('Should fail with an invalid PeerTube URL', async function () {
const keys = require('./json/peertube/keys.json')
const body = require('./json/peertube/announce-without-context.json')
const actorSignature = { url: 'http://localhost:9002/accounts/peertube', privateKey: keys.privateKey }
const signedBody = await buildSignedActivity(actorSignature as any, body)
const fromActor = { publicKey: keys.publicKey, url: 'http://localhost:9003/accounts/peertube' }
const result = await isJsonLDSignatureVerified(fromActor as any, signedBody)
expect(result).to.be.false
})
it('Should succeed with a valid PeerTube signature', async function () {
const keys = require('./json/peertube/keys.json')
const body = require('./json/peertube/announce-without-context.json')
const actorSignature = { url: 'http://localhost:9002/accounts/peertube', privateKey: keys.privateKey }
const signedBody = await buildSignedActivity(actorSignature as any, body)
const fromActor = { publicKey: keys.publicKey, url: 'http://localhost:9002/accounts/peertube' }
const result = await isJsonLDSignatureVerified(fromActor as any, signedBody)
expect(result).to.be.true
})
})
describe('When checking HTTP signature', function () {
it('Should fail with an invalid http signature', async function () {
const req = buildRequestStub()
req.method = 'POST'
req.url = '/accounts/ronan/inbox'
const mastodonObject = cloneDeep(require('./json/mastodon/bad-http-signature.json'))
req.body = mastodonObject.body
req.headers = mastodonObject.headers
req.headers.signature = 'Signature ' + req.headers.signature
const parsed = parseHTTPSignature(req, 3600 * 365 * 3)
const publicKey = require('./json/mastodon/public-key.json').publicKey
const actor = { publicKey }
const verified = isHTTPSignatureVerified(parsed, actor as any)
expect(verified).to.be.false
})
it('Should fail with an invalid public key', async function () {
const req = buildRequestStub()
req.method = 'POST'
req.url = '/accounts/ronan/inbox'
const mastodonObject = cloneDeep(require('./json/mastodon/http-signature.json'))
req.body = mastodonObject.body
req.headers = mastodonObject.headers
req.headers.signature = 'Signature ' + req.headers.signature
const parsed = parseHTTPSignature(req, 3600 * 365 * 3)
const publicKey = require('./json/mastodon/bad-public-key.json').publicKey
const actor = { publicKey }
const verified = isHTTPSignatureVerified(parsed, actor as any)
expect(verified).to.be.false
})
it('Should fail because of clock skew', async function () {
const req = buildRequestStub()
req.method = 'POST'
req.url = '/accounts/ronan/inbox'
const mastodonObject = cloneDeep(require('./json/mastodon/http-signature.json'))
req.body = mastodonObject.body
req.headers = mastodonObject.headers
req.headers.signature = 'Signature ' + req.headers.signature
let errored = false
try {
parseHTTPSignature(req)
} catch {
errored = true
}
expect(errored).to.be.true
})
it('Should fail without scheme', async function () {
const req = buildRequestStub()
req.method = 'POST'
req.url = '/accounts/ronan/inbox'
const mastodonObject = cloneDeep(require('./json/mastodon/http-signature.json'))
req.body = mastodonObject.body
req.headers = mastodonObject.headers
let errored = false
try {
parseHTTPSignature(req, 3600 * 365 * 3)
} catch {
errored = true
}
expect(errored).to.be.true
})
it('Should succeed with a valid signature', async function () {
const req = buildRequestStub()
req.method = 'POST'
req.url = '/accounts/ronan/inbox'
const mastodonObject = cloneDeep(require('./json/mastodon/http-signature.json'))
req.body = mastodonObject.body
req.headers = mastodonObject.headers
req.headers.signature = 'Signature ' + req.headers.signature
const parsed = parseHTTPSignature(req, 3600 * 365 * 3)
const publicKey = require('./json/mastodon/public-key.json').publicKey
const actor = { publicKey }
const verified = isHTTPSignatureVerified(parsed, actor as any)
expect(verified).to.be.true
})
})
})

View File

@ -0,0 +1,3 @@
import './client'
import './helpers'
import './security'

View File

@ -0,0 +1,93 @@
{
"headers": {
"user-agent": "http.rb/3.3.0 (Mastodon/2.5.0; +http://localhost:3000/)",
"host": "localhost",
"date": "Mon, 22 Oct 2018 13:34:22 GMT",
"accept-encoding": "gzip",
"digest": "SHA-256=FEr5j2WSSfdEMcG3NTOXuGU0lUchfTJx4+BtUlWOwDk=",
"content-type": "application/activity+json",
"signature": "keyId=\"http://localhost:3000/users/ronan2#main-key\",algorithm=\"rsa-sha256\",headers=\"(request-target) host date digest content-type\",signature=\"oLKbgxdFXdXsHJ3x/UsG9Svu7oa8Dyqiy6Jif4wqNuhAqRVMRaG18f+dd2OcfFX3XRGF8p8flZkU6vvoEQBauTwGRGcgXAJuKC1zYIWGk+PeiW8lNUnE4qGapWcTiFnIo7FKauNdsgqg/tvgs1pQIdHkDDjZMI64twP7sTN/4vG1PCq+kyqi/DM+ORLi/W7vFuLVHt2Iz7ikfw/R3/mMtS4FwLops+tVYBQ2iQ9DVRhTwLKVbeL/LLVB/tdGzNZ4F4nImBAQQ9I7WpPM6J/k+cBmoEbrUKs8ptx9gbX3OSsl5wlvPVMNzU9F9yb2MrB/Y/J4qssKz+LbiaktKGj7OQ==\"",
"content-length": "2815"
},
"body": {
"@context": [
"https://www.w3.org/ns/activitystreams",
"https://w3id.org/security/v1",
{
"manuallyApprovesFollowers": "as:manuallyApprovesFollowers",
"sensitive": "as:sensitive",
"movedTo": {
"@id": "as:movedTo",
"@type": "@id"
},
"Hashtag": "as:Hashtag",
"ostatus": "http://ostatus.org#",
"atomUri": "ostatus:atomUri",
"inReplyToAtomUri": "ostatus:inReplyToAtomUri",
"conversation": "ostatus:conversation",
"toot": "http://joinmastodon.org/ns#",
"Emoji": "toot:Emoji",
"focalPoint": {
"@container": "@list",
"@id": "toot:focalPoint"
},
"featured": {
"@id": "toot:featured",
"@type": "@id"
},
"schema": "http://schema.org#",
"PropertyValue": "schema:PropertyValue",
"value": "schema:value"
}
],
"id": "http://localhost:3000/users/ronan2/statuses/100939547203370948/activity",
"type": "Create",
"actor": "http://localhost:3000/users/ronan2",
"published": "2018-10-22T13:34:18Z",
"to": [
"https://www.w3.org/ns/activitystreams#Public"
],
"cc": [
"http://localhost:3000/users/ronan2/followers",
"http://localhost:9000/accounts/ronan"
],
"object": {
"id": "http://localhost:3000/users/ronan2/statuses/100939547203370948",
"type": "Note",
"summary": null,
"inReplyTo": "http://localhost:9000/videos/watch/90e6f8ed-b369-423c-b0c8-f44e5350c752",
"published": "2018-10-22T13:34:18Z",
"url": "http://localhost:3000/@ronan2/100939547203370948",
"attributedTo": "http://localhost:3000/users/ronan2",
"to": [
"https://www.w3.org/ns/activitystreams#Public"
],
"cc": [
"http://localhost:3000/users/ronan2/followers",
"http://localhost:9000/accounts/ronan"
],
"sensitive": false,
"atomUri": "http://localhost:3000/users/ronan2/statuses/100939547203370948",
"inReplyToAtomUri": "http://localhost:9000/videos/watch/90e6f8ed-b369-423c-b0c8-f44e5350c752",
"conversation": "tag:localhost:3000,2018-10-19:objectId=72:objectType=Conversation",
"content": "<p><span class=\"h-card\"><a href=\"http://localhost:9000/accounts/ronan\" class=\"u-url mention\">@<span>ronan</span></a></span> zergzerg</p>",
"contentMap": {
"en": "<p><span class=\"h-card\"><a href=\"http://localhost:9000/accounts/ronan\" class=\"u-url mention\">@<span>ronan</span></a></span> zergzerg</p>"
},
"attachment": [],
"tag": [
{
"type": "Mention",
"href": "http://localhost:9000/accounts/ronan",
"name": "@ronan@localhost:9000"
}
]
},
"signature": {
"type": "RsaSignature2017",
"creator": "http://localhost:3000/users/ronan2#main-key",
"created": "2018-10-22T13:34:19Z",
"signatureValue": "x+xL4l8ERziYVhwEafHJyBQOInvNZ0gV4ccYd9AtFYeGJagc8fY6jjjhbDRCD7yMhgTjBX69z20MXnDuwpmM6wej3dt1wLKdIyXVViO84nAlqFz7KmNxtk5lDnAVX/vttscT5YUFvw4dbPT2mQiEd1lKbaLftRiIPEomZpQ37+fUkQdcPrnhruPAISO/Sof1n1LFW4mYIffozteQSZBH6HaCVp+MRMIhdMi5e8w7PD48/cZz8D/EU8Vqi91FM76/3tMqg6nLqQ+8bq74Jvt2kzwZlIufe+I55QMpZOmF6hGIJEt+R0JXdjQbtgcELONmNj2dr8sAlzu7zKlAGuJ24Q=="
}
}
}

View File

@ -0,0 +1,93 @@
{
"headers": {
"user-agent": "http.rb/3.3.0 (Mastodon/2.5.0; +http://localhost:3000/)",
"host": "localhost",
"date": "Mon, 22 Oct 2018 13:34:22 GMT",
"accept-encoding": "gzip",
"digest": "SHA-256=FEr5j2WSSfdEMcG3NTOXuGU0lUchfTJx4+BtUlWOwDk=",
"content-type": "application/activity+json",
"signature": "keyId=\"http://localhost:3000/users/ronan2#main-key\",algorithm=\"rsa-sha256\",headers=\"(request-target) host date digest content-type\",signature=\"oLKbgxdFXdXsHJ3x/UsG9Svu7oa8Dyqiy6Jif4wqNuhAqRVMRaG18f+dd2OcfFX3XRGF8p8flZkU6vvoEQBauTwGRGcgXAJuKC1zYIWGk+PeiW8lNUnE4qGapWcTiFnIo7FKauNdsgqg/tvgs1pQIdHkDDjZMI64twP7sTN/4vG1PCq+kyqi/DM+ORLi/W7vFuLVHt2Iz7ikfw/R3/mMtS4FwLops+tVYBQ2iQ9DVRhTwLKVbeL/LLVB/tdGzNZ4F4nImBAQQ9I7WpPM6J/k+cBmoEbrUKs8ptx9gbX3OSsl4wlvPVMNzU9F9yb2MrB/Y/J4qssKz+LbiaktKGj7OQ==\"",
"content-length": "2815"
},
"body": {
"@context": [
"https://www.w3.org/ns/activitystreams",
"https://w3id.org/security/v1",
{
"manuallyApprovesFollowers": "as:manuallyApprovesFollowers",
"sensitive": "as:sensitive",
"movedTo": {
"@id": "as:movedTo",
"@type": "@id"
},
"Hashtag": "as:Hashtag",
"ostatus": "http://ostatus.org#",
"atomUri": "ostatus:atomUri",
"inReplyToAtomUri": "ostatus:inReplyToAtomUri",
"conversation": "ostatus:conversation",
"toot": "http://joinmastodon.org/ns#",
"Emoji": "toot:Emoji",
"focalPoint": {
"@container": "@list",
"@id": "toot:focalPoint"
},
"featured": {
"@id": "toot:featured",
"@type": "@id"
},
"schema": "http://schema.org#",
"PropertyValue": "schema:PropertyValue",
"value": "schema:value"
}
],
"id": "http://localhost:3000/users/ronan2/statuses/100939547203370948/activity",
"type": "Create",
"actor": "http://localhost:3000/users/ronan2",
"published": "2018-10-22T13:34:18Z",
"to": [
"https://www.w3.org/ns/activitystreams#Public"
],
"cc": [
"http://localhost:3000/users/ronan2/followers",
"http://localhost:9000/accounts/ronan"
],
"object": {
"id": "http://localhost:3000/users/ronan2/statuses/100939547203370948",
"type": "Note",
"summary": null,
"inReplyTo": "http://localhost:9000/videos/watch/90e6f8ed-b369-423c-b0c8-f44e5350c752",
"published": "2018-10-22T13:34:18Z",
"url": "http://localhost:3000/@ronan2/100939547203370948",
"attributedTo": "http://localhost:3000/users/ronan2",
"to": [
"https://www.w3.org/ns/activitystreams#Public"
],
"cc": [
"http://localhost:3000/users/ronan2/followers",
"http://localhost:9000/accounts/ronan"
],
"sensitive": false,
"atomUri": "http://localhost:3000/users/ronan2/statuses/100939547203370948",
"inReplyToAtomUri": "http://localhost:9000/videos/watch/90e6f8ed-b369-423c-b0c8-f44e5350c752",
"conversation": "tag:localhost:3000,2018-10-19:objectId=72:objectType=Conversation",
"content": "<p><span class=\"h-card\"><a href=\"http://localhost:9000/accounts/ronan\" class=\"u-url mention\">@<span>ronan</span></a></span> zergzerg</p>",
"contentMap": {
"en": "<p><span class=\"h-card\"><a href=\"http://localhost:9000/accounts/ronan\" class=\"u-url mention\">@<span>ronan</span></a></span> zergzerg</p>"
},
"attachment": [],
"tag": [
{
"type": "Mention",
"href": "http://localhost:9000/accounts/ronan",
"name": "@ronan@localhost:9000"
}
]
},
"signature": {
"type": "RsaSignature2017",
"creator": "http://localhost:3000/users/ronan2#main-key",
"created": "2018-10-22T13:34:19Z",
"signatureValue": "x+xL4l8ERziYVhwEafHJyBQOInvNZ0gV4ccYd9AtFYeGJagc8fY6jjjhbDRCD7yMhgTjBX69z20MXnDuwpmM6wej3dt1wLKdIyXVViO84nAlqFz7KmNxtk5lDnAVX/vttscT5YUFvw4dbPT2mQiEd1lKbaLftRiIPEomZpQ37+fUkQdcPrnhruPAISO/Sof1n1LFW4mYIffozteQSZBH6HaCVp+MRMIhdMi5e8w7PD48/cZz8D/EU8Vqi91FM76/3tMqg6nLqQ+8bq74Jvt2kzwZlIufe+I55QMpZOmF6hGIJEt+R0JXdjQbtgcELONmNj2dr8sAlzu7zKlAGuJ24Q=="
}
}
}

View File

@ -0,0 +1,3 @@
{
"publicKey": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0YyuthHtWWgDe0Fdgdp2\ndC5dTJsRqW6pFw5omIYYYjoES/WRewhVxEA54BhmxD3L1zChfx131N1TS8jVowhW\nm999jpUffKCCvLgYKIXETJDHiDeMONVx8wp7v9fS1HiFXo/E5und39gUMs14CMFZ\n6PE5jRV3r4XIKQJHQl7/X5n5FOb2934K+1TKUeBkbft/AushlKatYQakt3qHxpwx\nFvE+JjGo7QTnzdjaOx/e5QvojdGi2Kx4+jl77j2WVcSo5lOBz04OAVJtChtn82vS\nulPdDh3hZcDn+WK67yAhGP6AnzvOybZZS4zowlKiQ3kqjVVXKdl8gAsL4Y7MZ40R\nJQIDAQAB\n-----END PUBLIC KEY-----\n"
}

View File

@ -0,0 +1,81 @@
{
"@context": [
"https://www.w3.org/ns/activitystreams",
"https://w3id.org/security/v1",
{
"manuallyApprovesFollowers": "as:manuallyApprovesFollowers",
"sensitive": "as:sensitive",
"movedTo": {
"@id": "as:movedTo",
"@type": "@id"
},
"Hashtag": "as:Hashtag",
"ostatus": "http://ostatus.org#",
"atomUri": "ostatus:atomUri",
"inReplyToAtomUri": "ostatus:inReplyToAtomUri",
"conversation": "ostatus:conversation",
"toot": "http://joinmastodon.org/ns#",
"Emoji": "toot:Emoji",
"focalPoint": {
"@container": "@list",
"@id": "toot:focalPoint"
},
"featured": {
"@id": "toot:featured",
"@type": "@id"
},
"schema": "http://schema.org#",
"PropertyValue": "schema:PropertyValue",
"value": "schema:value"
}
],
"id": "http://localhost:3000/users/ronan2/statuses/100939345950887698/activity",
"type": "Create",
"actor": "http://localhost:3000/users/ronan2",
"published": "2018-10-22T12:43:07Z",
"to": [
"https://www.w3.org/ns/activitystreams#Public"
],
"cc": [
"http://localhost:3000/users/ronan2/followers",
"http://localhost:9000/accounts/ronan"
],
"object": {
"id": "http://localhost:3000/users/ronan2/statuses/100939345950887698",
"type": "Note",
"summary": null,
"inReplyTo": "http://localhost:9000/videos/watch/90e6f8ed-b369-423c-b0c8-f44e5350c752",
"published": "2018-10-22T12:43:07Z",
"url": "http://localhost:3000/@ronan2/100939345950887698",
"attributedTo": "http://localhost:3000/users/ronan2",
"to": [
"https://www.w3.org/ns/activitystreams#Public"
],
"cc": [
"http://localhost:3000/users/ronan2/followers",
"http://localhost:9000/accounts/ronan"
],
"sensitive": false,
"atomUri": "http://localhost:3000/users/ronan2/statuses/100939345950887698",
"inReplyToAtomUri": "http://localhost:9000/videos/watch/90e6f8ed-b369-423c-b0c8-f44e5350c752",
"conversation": "tag:localhost:3000,2018-10-19:objectId=72:objectType=Conversation",
"content": "<p><span class=\"h-card\"><a href=\"http://localhost:9000/accounts/ronan\" class=\"u-url mention\">@<span>ronan</span></a></span> zerg</p>",
"contentMap": {
"en": "<p><span class=\"h-card\"><a href=\"http://localhost:9000/accounts/ronan\" class=\"u-url mention\">@<span>ronan</span></a></span> zerg</p>"
},
"attachment": [],
"tag": [
{
"type": "Mention",
"href": "http://localhost:9000/accounts/ronan",
"name": "@ronan@localhost:9000"
}
]
},
"signature": {
"type": "RsaSignature2017",
"creator": "http://localhost:3000/users/ronan2#main-key",
"created": "2018-10-22T12:43:08Z",
"signatureValue": "Vgr8nA0agPr9TcA4BlX+MWhmuE+rBcoIJLpnPbm3E5SnOCXbgjEfEaTLqfuzzkKNsR3PBbkvi3YWK4/DxJ0zmpzSB7yy4NRzluQMVQHqJiFKXAX3Sr3fIrK24xkWW9/F207c1NpFajSGbgnFKBdtFE0e5VqwSrSoOJkZukZW/2ATSnsyzblieuUmvTWpD0PqpUOsynPjw+RqZnqPn0cjw1z2Dm7ZRt3trnyMTXFYZw5U/YuqMY2kpadD6vq780md8kXlJIylxG6ZrlO2jz9fJdnfuVq43d4QFNsBm1K1r2WtNqX+i+wiqh+u3PjF4pzXtl/a3hJOH18IfZnK7I21mQ=="
}
}

View File

@ -0,0 +1,81 @@
{
"@context": [
"https://www.w3.org/ns/activitystreams",
"https://w3id.org/security/v1",
{
"manuallyApprovesFollowers": "as:manuallyApprovesFollowers",
"sensitive": "as:sensitive",
"movedTo": {
"@id": "as:movedTo",
"@type": "@id"
},
"Hashtag": "as:Hashtag",
"ostatus": "http://ostatus.org#",
"atomUri": "ostatus:atomUri",
"inReplyToAtomUri": "ostatus:inReplyToAtomUri",
"conversation": "ostatus:conversation",
"toot": "http://joinmastodon.org/ns#",
"Emoji": "toot:Emoji",
"focalPoint": {
"@container": "@list",
"@id": "toot:focalPoint"
},
"featured": {
"@id": "toot:featured",
"@type": "@id"
},
"schema": "http://schema.org#",
"PropertyValue": "schema:PropertyValue",
"value": "schema:value"
}
],
"id": "http://localhost:3000/users/ronan2/statuses/100939345950887698/activity",
"type": "Create",
"actor": "http://localhost:3000/users/ronan2",
"published": "2018-10-22T12:43:07Z",
"to": [
"https://www.w3.org/ns/activitystreams#Public"
],
"cc": [
"http://localhost:3000/users/ronan2/followers",
"http://localhost:9000/accounts/ronan"
],
"object": {
"id": "http://localhost:3000/users/ronan2/statuses/100939345950887698",
"type": "Note",
"summary": null,
"inReplyTo": "http://localhost:9000/videos/watch/90e6f8ed-b369-423c-b0c8-f44e5350c752",
"published": "2018-10-22T12:43:07Z",
"url": "http://localhost:3000/@ronan2/100939345950887698",
"attributedTo": "http://localhost:3000/users/ronan2",
"to": [
"https://www.w3.org/ns/activitystreams#Public"
],
"cc": [
"http://localhost:3000/users/ronan2/followers",
"http://localhost:9000/accounts/ronan"
],
"sensitive": false,
"atomUri": "http://localhost:3000/users/ronan2/statuses/100939345950887698",
"inReplyToAtomUri": "http://localhost:9000/videos/watch/90e6f8ed-b369-423c-b0c8-f44e5350c752",
"conversation": "tag:localhost:3000,2018-10-19:objectId=72:objectType=Conversation",
"content": "<p><span class=\"h-card\"><a href=\"http://localhost:9000/accounts/ronan\" class=\"u-url mention\">@<span>ronan</span></a></span> zerg</p>",
"contentMap": {
"en": "<p><span class=\"h-card\"><a href=\"http://localhost:9000/accounts/ronan\" class=\"u-url mention\">@<span>ronan</span></a></span> zerg</p>"
},
"attachment": [],
"tag": [
{
"type": "Mention",
"href": "http://localhost:9000/accounts/ronan",
"name": "@ronan@localhost:9000"
}
]
},
"signature": {
"type": "RsaSignature2017",
"creator": "http://localhost:3000/users/ronan2#main-key",
"created": "2018-10-22T12:43:08Z",
"signatureValue": "VgR8nA0agPr9TcA4BlX+MWhmuE+rBcoIJLpnPbm3E5SnOCXbgjEfEaTLqfuzzkKNsR3PBbkvi3YWK4/DxJ0zmpzSB7yy4NRzluQMVQHqJiFKXAX3Sr3fIrK24xkWW9/F207c1NpFajSGbgnFKBdtFE0e5VqwSrSoOJkZukZW/2ATSnsyzblieuUmvTWpD0PqpUOsynPjw+RqZnqPn0cjw1z2Dm7ZRt3trnyMTXFYZw5U/YuqMY2kpadD6vq780md8kXlJIylxG6ZrlO2jz9fJdnfuVq43d4QFNsBm1K1r2WtNqX+i+wiqh+u3PjF4pzXtl/a3hJOH18IfZnK7I21mQ=="
}
}

View File

@ -0,0 +1,93 @@
{
"headers": {
"user-agent": "http.rb/3.3.0 (Mastodon/2.5.0; +http://localhost:3000/)",
"host": "localhost",
"date": "Mon, 22 Oct 2018 13:34:22 GMT",
"accept-encoding": "gzip",
"digest": "SHA-256=FEr5j2WSSfdEMcG3NTOXuGU0lUchfTJx4+BtUlWOwDk=",
"content-type": "application/activity+json",
"signature": "keyId=\"http://localhost:3000/users/ronan2#main-key\",algorithm=\"rsa-sha256\",headers=\"(request-target) host date digest content-type\",signature=\"oLKbgxdFXdXsHJ3x/UsG9Svu7oa8Dyqiy6Jif4wqNuhAqRVMRaG18f+dd2OcfFX3XRGF8p8flZkU6vvoEQBauTwGRGcgXAJuKC1zYIWGk+PeiW8lNUnE4qGapWcTiFnIo7FKauNdsgqg/tvgs1pQIdHkDDjZMI64twP7sTN/4vG1PCq+kyqi/DM+ORLi/W7vFuLVHt2Iz7ikfw/R3/mMtS4FwLops+tVYBQ2iQ9DVRhTwLKVbeL/LLVB/tdGzNZ4F4nImBAQQ9I7WpPM6J/k+cBmoEbrUKs8ptx9gbX3OSsl5wlvPVMNzU9F9yb2MrB/Y/J4qssKz+LbiaktKGj7OQ==\"",
"content-length": "2815"
},
"body": {
"@context": [
"https://www.w3.org/ns/activitystreams",
"https://w3id.org/security/v1",
{
"manuallyApprovesFollowers": "as:manuallyApprovesFollowers",
"sensitive": "as:sensitive",
"movedTo": {
"@id": "as:movedTo",
"@type": "@id"
},
"Hashtag": "as:Hashtag",
"ostatus": "http://ostatus.org#",
"atomUri": "ostatus:atomUri",
"inReplyToAtomUri": "ostatus:inReplyToAtomUri",
"conversation": "ostatus:conversation",
"toot": "http://joinmastodon.org/ns#",
"Emoji": "toot:Emoji",
"focalPoint": {
"@container": "@list",
"@id": "toot:focalPoint"
},
"featured": {
"@id": "toot:featured",
"@type": "@id"
},
"schema": "http://schema.org#",
"PropertyValue": "schema:PropertyValue",
"value": "schema:value"
}
],
"id": "http://localhost:3000/users/ronan2/statuses/100939547203370948/activity",
"type": "Create",
"actor": "http://localhost:3000/users/ronan2",
"published": "2018-10-22T13:34:18Z",
"to": [
"https://www.w3.org/ns/activitystreams#Public"
],
"cc": [
"http://localhost:3000/users/ronan2/followers",
"http://localhost:9000/accounts/ronan"
],
"object": {
"id": "http://localhost:3000/users/ronan2/statuses/100939547203370948",
"type": "Note",
"summary": null,
"inReplyTo": "http://localhost:9000/videos/watch/90e6f8ed-b369-423c-b0c8-f44e5350c752",
"published": "2018-10-22T13:34:18Z",
"url": "http://localhost:3000/@ronan2/100939547203370948",
"attributedTo": "http://localhost:3000/users/ronan2",
"to": [
"https://www.w3.org/ns/activitystreams#Public"
],
"cc": [
"http://localhost:3000/users/ronan2/followers",
"http://localhost:9000/accounts/ronan"
],
"sensitive": false,
"atomUri": "http://localhost:3000/users/ronan2/statuses/100939547203370948",
"inReplyToAtomUri": "http://localhost:9000/videos/watch/90e6f8ed-b369-423c-b0c8-f44e5350c752",
"conversation": "tag:localhost:3000,2018-10-19:objectId=72:objectType=Conversation",
"content": "<p><span class=\"h-card\"><a href=\"http://localhost:9000/accounts/ronan\" class=\"u-url mention\">@<span>ronan</span></a></span> zergzerg</p>",
"contentMap": {
"en": "<p><span class=\"h-card\"><a href=\"http://localhost:9000/accounts/ronan\" class=\"u-url mention\">@<span>ronan</span></a></span> zergzerg</p>"
},
"attachment": [],
"tag": [
{
"type": "Mention",
"href": "http://localhost:9000/accounts/ronan",
"name": "@ronan@localhost:9000"
}
]
},
"signature": {
"type": "RsaSignature2017",
"creator": "http://localhost:3000/users/ronan2#main-key",
"created": "2018-10-22T13:34:19Z",
"signatureValue": "x+xL4l8ERziYVhwEafHJyBQOInvNZ0gV4ccYd9AtFYeGJagc8fY6jjjhbDRCD7yMhgTjBX69z20MXnDuwpmM6wej3dt1wLKdIyXVViO84nAlqFz7KmNxtk5lDnAVX/vttscT5YUFvw4dbPT2mQiEd1lKbaLftRiIPEomZpQ37+fUkQdcPrnhruPAISO/Sof1n1LFW4mYIffozteQSZBH6HaCVp+MRMIhdMi5e8w7PD48/cZz8D/EU8Vqi91FM76/3tMqg6nLqQ+8bq74Jvt2kzwZlIufe+I55QMpZOmF6hGIJEt+R0JXdjQbtgcELONmNj2dr8sAlzu7zKlAGuJ24Q=="
}
}
}

View File

@ -0,0 +1,3 @@
{
"publicKey": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0YyuthHtWWgDe0Fdgdp2\ndC5dTJsRqW6pFw5omIYYYjoES/WRewhVxEA54BhmxD3L1zChfx131N1TS8jVowhW\nm999jpUffKCCvLgYKIXETJDHiDeMONVx8wp7v9fS1HiFXo/E5und39gUMs14CMFZ\n6PE5jRV3r4XIKQJHQl7/X5n5FOb2934K+1TKUeBkbft/AushlKatYQakt3qHxpwx\nFvE+JjGo7QTnzdjaOx/e5QvojdGi2Kx4+jl87j2WVcSo5lOBz04OAVJtChtn82vS\nulPdDh3hZcDn+WK67yAhGP6AnzvOybZZS4zowlKiQ3kqjVVXKdl8gAsL4Y7MZ40R\nJQIDAQAB\n-----END PUBLIC KEY-----\n"
}

View File

@ -0,0 +1,13 @@
{
"type": "Announce",
"id": "http://localhost:9002/videos/watch/997111d4-e8d8-4f45-99d3-857905785d05/announces/1",
"actor": "http://localhost:9002/accounts/peertube",
"object": "http://localhost:9002/videos/watch/997111d4-e8d8-4f45-99d3-857905785d05",
"to": [
"https://www.w3.org/ns/activitystreams#Public",
"http://localhost:9002/accounts/peertube/followers",
"http://localhost:9002/video-channels/root_channel/followers",
"http://localhost:9002/accounts/root/followers"
],
"cc": []
}

View File

@ -0,0 +1,6 @@
{
"publicKey": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqjQGdH6D3naKmSbbr/Df\nEh1H42F3WlHYXuxKLkm5Bemjdde+GwHYdz5m3fcIWw3HTzfA+y9Of8epGdfSrtYO\nwAyc3Zoy7afPNa4bZXqhJ1Im41rMGieiCuUn4uTPPucIjC0gCkVwvuQr3Elbk55s\nIkczDkseJuadTvG+A1e4uNY2lnRmVhf4g5B90u6CLe2KdbPpifRoKlw9zaUBj4/F\npP5S75TS5l1DfJQIq2lp8RwrH6FvGKLnWlbGeNYX96DDvlA5Sxoxz6a+bTV9OopM\n7mS7eP8zF8lKXYUu8cjIscKm+XqGmyRoPyw2Pp53tew29idRUocVQHGBnlNbpKdd\naQIDAQAB\n-----END PUBLIC KEY-----\n",
"privateKey": "-----BEGIN RSA PRIVATE KEY-----\nMIIEpAIBAAKCAQEAqjQGdH6D3naKmSbbr/DfEh1H42F3WlHYXuxKLkm5Bemjdde+\nGwHYdz5m3fcIWw3HTzfA+y9Of8epGdfSrtYOwAyc3Zoy7afPNa4bZXqhJ1Im41rM\nGieiCuUn4uTPPucIjC0gCkVwvuQr3Elbk55sIkczDkseJuadTvG+A1e4uNY2lnRm\nVhf4g5B90u6CLe2KdbPpifRoKlw9zaUBj4/FpP5S75TS5l1DfJQIq2lp8RwrH6Fv\nGKLnWlbGeNYX96DDvlA5Sxoxz6a+bTV9OopM7mS7eP8zF8lKXYUu8cjIscKm+XqG\nmyRoPyw3Pp53tew29idRUocVQHGBnlNbpKddaQIDAQABAoIBAQCnBZawCtbtH/ay\ng+dhqEW/SOyavbKZ92cU/1tsQPxISRYXNjdf2VfK7HmVqC2S7NqBanz+AVZPHmda\n7OfamkSvQbFN5VvEy8ATNV+9HbG3HG78/MT9hZcGigmyJkcZuy4wILgoXCxfpxlD\netla60PB/4yioiRcmEIWjjOgpByphDJ7RuuuptyEvgjUjpPtvHK47O/loaD2HFJk\nbIYbRirbjUjITRjQxGVIvanqiwPG9pB26YDLxDOoXEumcnzRcEFWNdvoleaLgquS\nn/zVsXWEq4+1i7t44DDstWUt/2Bw5ksIkSdayQ6oy3vzre3YFHwvbVZ7qtQQgpru\nx+NIolZhAoGBAN1RgNj8zy9Py3SJdsoXtnuCItfD7eo7LWXUa06cM/NS695Q+/to\naa5i3cJnRlv+b+b3VvnhkhIBLfFQW+hWwPnnxJEehcm09ddN9zbWrZ4Yv9yYu+8d\nTLGyWL8kPFF1dz+29DcrSv3tXEOwxByX/O4U/X/i3wl2WhkybxVFnCuvAoGBAMTf\n91BgLzvcYKOxH+vRPOJY7g2HKGFe35R91M4E+9Eq1rq4LUQHBb3fhRh4+scNu0yb\nNfN1Zdx2nbgCXdTKomF1Ahxp58/A2iU65vVzL6hYfWXEGSmoBqsGCIpIxQ9jgB9k\nCl7t/Ban8Z/ORHTjI9fpHlSZyCWJ3ajepiM2a1ZnAoGAPpDO6wi1DXvyWVSPF1yS\nwuGsNfD2rjPihpoBZ+yypwP3GBcu1QjUb28Vn+KQOmt4eQPNO8DwCVT6BvEfulPk\nJAHISPom+jnFEgPBcmhIFpyKiLNI1bUjvExd2FNHFgQuHP38ligQAC782Un8dtTk\ntO2MKH4bbVJe8CaYzpuqJZMCgYABZyMpBHZxs8FQiUuT75rCdiXEHOlxwC5RrY/d\no/VzaR28mOFhsbcdwkD9iqcm0fc6tYRt5rFCH+pBzGqEwKjljuLj9vE67sHfMAtD\nRn3Zcj/6gKo5PMRHZbSb36bf1DKuhpT4VjPMqYe0PtEIEDJKMJQRwELH2bKlqGiA\nqbucEwKBgQCkS85JnpHEV/tSylsEEn2W3CQCx58zl7iZNV7h/tWMR4AyrcI0HqP6\nllJ7V/Cfw66MgelPnosKgagwLVI6gsqDtjnzYo3XuMRVlYIySJ/jV3eiUNkV2Ky2\nfp/gA9sVgp38QSr+xB9E0LNStcbqDzoCCcDRws/SK7PbkQH9KV47tQ==\n-----END RSA PRIVATE KEY-----"
}

View File

@ -0,0 +1,4 @@
{
"publicKey": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqjQGdH6D3naKmSbbr/Df\nEh1H42F3WlHYXuxKLkm5Bemjdde+GwHYdz5m3fcIWw3HTzfA+y9Of8epGdfSrtYO\nwAyc3Zoy7afPNa4bZXqhJ1Im41rMGieiCuUn4uTPPucIjC0gCkVwvuQr3Elbk55s\nIkczDkseJuadTvG+A1e4uNY2lnRmVhf4g5B90u6CLe2KdbPpifRoKlw9zaUBj4/F\npP5S75TS5l1DfJQIq2lp8RwrH6FvGKLnWlbGeNYX96DDvlA5Sxoxz6a+bTV9OopM\n7mS7eP8zF8lKXYUu8cjIscKm+XqGmyRoPyw3Pp53tew29idRUocVQHGBnlNbpKdd\naQIDAQAB\n-----END PUBLIC KEY-----\n",
"privateKey": "-----BEGIN RSA PRIVATE KEY-----\nMIIEpAIBAAKCAQEAqjQGdH6D3naKmSbbr/DfEh1H42F3WlHYXuxKLkm5Bemjdde+\nGwHYdz5m3fcIWw3HTzfA+y9Of8epGdfSrtYOwAyc3Zoy7afPNa4bZXqhJ1Im41rM\nGieiCuUn4uTPPucIjC0gCkVwvuQr3Elbk55sIkczDkseJuadTvG+A1e4uNY2lnRm\nVhf4g5B90u6CLe2KdbPpifRoKlw9zaUBj4/FpP5S75TS5l1DfJQIq2lp8RwrH6Fv\nGKLnWlbGeNYX96DDvlA5Sxoxz6a+bTV9OopM7mS7eP8zF8lKXYUu8cjIscKm+XqG\nmyRoPyw3Pp53tew29idRUocVQHGBnlNbpKddaQIDAQABAoIBAQCnBZawCtbtH/ay\ng+dhqEW/SOyavbKZ92cU/1tsQPxISRYXNjdf2VfK7HmVqC2S7NqBanz+AVZPHmda\n7OfamkSvQbFN5VvEy8ATNV+9HbG3HG78/MT9hZcGigmyJkcZuy4wILgoXCxfpxlD\netla60PB/4yioiRcmEIWjjOgpByphDJ7RuuuptyEvgjUjpPtvHK47O/loaD2HFJk\nbIYbRirbjUjITRjQxGVIvanqiwPG9pB26YDLxDOoXEumcnzRcEFWNdvoleaLgquS\nn/zVsXWEq4+1i7t44DDstWUt/2Bw5ksIkSdayQ6oy3vzre3YFHwvbVZ7qtQQgpru\nx+NIolZhAoGBAN1RgNj8zy9Py3SJdsoXtnuCItfD7eo7LWXUa06cM/NS695Q+/to\naa5i3cJnRlv+b+b3VvnhkhIBLfFQW+hWwPnnxJEehcm09ddN9zbWrZ4Yv9yYu+8d\nTLGyWL8kPFF1dz+29DcrSv3tXEOwxByX/O4U/X/i3wl2WhkybxVFnCuvAoGBAMTf\n91BgLzvcYKOxH+vRPOJY7g2HKGFe35R91M4E+9Eq1rq4LUQHBb3fhRh4+scNu0yb\nNfN1Zdx2nbgCXdTKomF1Ahxp58/A2iU65vVzL6hYfWXEGSmoBqsGCIpIxQ9jgB9k\nCl7t/Ban8Z/ORHTjI9fpHlSZyCWJ3ajepiM2a1ZnAoGAPpDO6wi1DXvyWVSPF1yS\nwuGsNfD2rjPihpoBZ+yypwP3GBcu1QjUb28Vn+KQOmt4eQPNO8DwCVT6BvEfulPk\nJAHISPom+jnFEgPBcmhIFpyKiLNI1bUjvExd2FNHFgQuHP38ligQAC782Un8dtTk\ntO2MKH4bbVJe8CaYzpuqJZMCgYABZyMpBHZxs8FQiUuT75rCdiXEHOlxwC5RrY/d\no/VzaR28mOFhsbcdwkD9iqcm0fc6tYRt5rFCH+pBzGqEwKjljuLj9vE67sHfMAtD\nRn3Zcj/6gKo5PMRHZbSb36bf1DKuhpT4VjPMqYe0PtEIEDJKMJQRwELH2bKlqGiA\nqbucEwKBgQCkS85JnpHEV/tSylsEEn2W3CQCx58zl7iZNV7h/tWMR4AyrcI0HqP6\nllJ7V/Cfw66MgelPnosKgagwLVI6gsqDtjnzYo3XuMRVlYIySJ/jV3eiUNkV2Ky2\nfp/gA9sVgp38QSr+xB9E0LNStcbqDzoCCcDRws/SK7PbkQH9KV47tQ==\n-----END RSA PRIVATE KEY-----"
}

View File

@ -0,0 +1,180 @@
/* tslint:disable:no-unused-expression */
import 'mocha'
import { flushAndRunMultipleServers, flushTests, killallServers, makeAPRequest, makeFollowRequest, ServerInfo } from '../../utils'
import { HTTP_SIGNATURE } from '../../../initializers'
import { buildDigest, buildGlobalHeaders } from '../../../lib/job-queue/handlers/utils/activitypub-http-utils'
import * as chai from 'chai'
import { setActorField } from '../../utils/miscs/sql'
import { activityPubContextify, buildSignedActivity } from '../../../helpers/activitypub'
const expect = chai.expect
function setKeysOfServer2 (serverNumber: number, publicKey: string, privateKey: string) {
return Promise.all([
setActorField(serverNumber, 'http://localhost:9002/accounts/peertube', 'publicKey', publicKey),
setActorField(serverNumber, 'http://localhost:9002/accounts/peertube', 'privateKey', privateKey)
])
}
function setKeysOfServer3 (serverNumber: number, publicKey: string, privateKey: string) {
return Promise.all([
setActorField(serverNumber, 'http://localhost:9003/accounts/peertube', 'publicKey', publicKey),
setActorField(serverNumber, 'http://localhost:9003/accounts/peertube', 'privateKey', privateKey)
])
}
describe('Test ActivityPub security', function () {
let servers: ServerInfo[]
let url: string
const keys = require('./json/peertube/keys.json')
const invalidKeys = require('./json/peertube/invalid-keys.json')
const baseHttpSignature = {
algorithm: HTTP_SIGNATURE.ALGORITHM,
authorizationHeaderName: HTTP_SIGNATURE.HEADER_NAME,
keyId: 'acct:peertube@localhost:9002',
key: keys.privateKey,
headers: HTTP_SIGNATURE.HEADERS_TO_SIGN
}
// ---------------------------------------------------------------
before(async function () {
this.timeout(60000)
servers = await flushAndRunMultipleServers(3)
url = servers[0].url + '/inbox'
await setKeysOfServer2(1, keys.publicKey, keys.privateKey)
const to = { url: 'http://localhost:9001/accounts/peertube' }
const by = { url: 'http://localhost:9002/accounts/peertube', privateKey: keys.privateKey }
await makeFollowRequest(to, by)
})
describe('When checking HTTP signature', function () {
it('Should fail with an invalid digest', async function () {
const body = activityPubContextify(require('./json/peertube/announce-without-context.json'))
const headers = {
Digest: buildDigest({ hello: 'coucou' })
}
const { response } = await makeAPRequest(url, body, baseHttpSignature, headers)
expect(response.statusCode).to.equal(403)
})
it('Should fail with an invalid date', async function () {
const body = activityPubContextify(require('./json/peertube/announce-without-context.json'))
const headers = buildGlobalHeaders(body)
headers['date'] = 'Wed, 21 Oct 2015 07:28:00 GMT'
const { response } = await makeAPRequest(url, body, baseHttpSignature, headers)
expect(response.statusCode).to.equal(403)
})
it('Should fail with bad keys', async function () {
await setKeysOfServer2(1, invalidKeys.publicKey, invalidKeys.privateKey)
await setKeysOfServer2(2, invalidKeys.publicKey, invalidKeys.privateKey)
const body = activityPubContextify(require('./json/peertube/announce-without-context.json'))
const headers = buildGlobalHeaders(body)
const { response } = await makeAPRequest(url, body, baseHttpSignature, headers)
expect(response.statusCode).to.equal(403)
})
it('Should succeed with a valid HTTP signature', async function () {
await setKeysOfServer2(1, keys.publicKey, keys.privateKey)
await setKeysOfServer2(2, keys.publicKey, keys.privateKey)
const body = activityPubContextify(require('./json/peertube/announce-without-context.json'))
const headers = buildGlobalHeaders(body)
const { response } = await makeAPRequest(url, body, baseHttpSignature, headers)
expect(response.statusCode).to.equal(204)
})
})
describe('When checking Linked Data Signature', function () {
before(async () => {
await setKeysOfServer3(3, keys.publicKey, keys.privateKey)
const to = { url: 'http://localhost:9001/accounts/peertube' }
const by = { url: 'http://localhost:9003/accounts/peertube', privateKey: keys.privateKey }
await makeFollowRequest(to, by)
})
it('Should fail with bad keys', async function () {
this.timeout(10000)
await setKeysOfServer3(1, invalidKeys.publicKey, invalidKeys.privateKey)
await setKeysOfServer3(3, invalidKeys.publicKey, invalidKeys.privateKey)
const body = require('./json/peertube/announce-without-context.json')
body.actor = 'http://localhost:9003/accounts/peertube'
const signer: any = { privateKey: invalidKeys.privateKey, url: 'http://localhost:9003/accounts/peertube' }
const signedBody = await buildSignedActivity(signer, body)
const headers = buildGlobalHeaders(signedBody)
const { response } = await makeAPRequest(url, signedBody, baseHttpSignature, headers)
expect(response.statusCode).to.equal(403)
})
it('Should fail with an altered body', async function () {
this.timeout(10000)
await setKeysOfServer3(1, keys.publicKey, keys.privateKey)
await setKeysOfServer3(3, keys.publicKey, keys.privateKey)
const body = require('./json/peertube/announce-without-context.json')
body.actor = 'http://localhost:9003/accounts/peertube'
const signer: any = { privateKey: keys.privateKey, url: 'http://localhost:9003/accounts/peertube' }
const signedBody = await buildSignedActivity(signer, body)
signedBody.actor = 'http://localhost:9003/account/peertube'
const headers = buildGlobalHeaders(signedBody)
const { response } = await makeAPRequest(url, signedBody, baseHttpSignature, headers)
expect(response.statusCode).to.equal(403)
})
it('Should succeed with a valid signature', async function () {
this.timeout(10000)
const body = require('./json/peertube/announce-without-context.json')
body.actor = 'http://localhost:9003/accounts/peertube'
const signer: any = { privateKey: keys.privateKey, url: 'http://localhost:9003/accounts/peertube' }
const signedBody = await buildSignedActivity(signer, body)
const headers = buildGlobalHeaders(signedBody)
const { response } = await makeAPRequest(url, signedBody, baseHttpSignature, headers)
expect(response.statusCode).to.equal(204)
})
})
after(async function () {
killallServers(servers)
// Keep the logs if the test failed
if (this['ok']) {
await flushTests()
}
})
})

View File

@ -1 +1,2 @@
import './redundancy'
import './activitypub'

View File

@ -1,6 +1,5 @@
// Order of the tests we want to execute
import './client'
import './activitypub'
import './feeds/'
import './cli/'
import './api/'

View File

@ -4,8 +4,10 @@ export * from './server/clients'
export * from './server/config'
export * from './users/login'
export * from './miscs/miscs'
export * from './miscs/stubs'
export * from './server/follows'
export * from './requests/requests'
export * from './requests/activitypub'
export * from './server/servers'
export * from './videos/services'
export * from './users/users'

View File

@ -0,0 +1,29 @@
import * as Sequelize from 'sequelize'
function getSequelize (serverNumber: number) {
const dbname = 'peertube_test' + serverNumber
const username = 'peertube'
const password = 'peertube'
const host = 'localhost'
const port = 5432
return new Sequelize(dbname, username, password, {
dialect: 'postgres',
host,
port,
operatorsAliases: false,
logging: false
})
}
function setActorField (serverNumber: number, to: string, field: string, value: string) {
const seq = getSequelize(serverNumber)
const options = { type: Sequelize.QueryTypes.UPDATE }
return seq.query(`UPDATE actor SET "${field}" = '${value}' WHERE url = '${to}'`, options)
}
export {
setActorField
}

View File

@ -0,0 +1,14 @@
function buildRequestStub (): any {
return { }
}
function buildResponseStub (): any {
return {
locals: {}
}
}
export {
buildResponseStub,
buildRequestStub
}

View File

@ -0,0 +1,43 @@
import { doRequest } from '../../../helpers/requests'
import { HTTP_SIGNATURE } from '../../../initializers'
import { buildGlobalHeaders } from '../../../lib/job-queue/handlers/utils/activitypub-http-utils'
import { activityPubContextify } from '../../../helpers/activitypub'
function makeAPRequest (url: string, body: any, httpSignature: any, headers: any) {
const options = {
method: 'POST',
uri: url,
json: body,
httpSignature,
headers
}
return doRequest(options)
}
async function makeFollowRequest (to: { url: string }, by: { url: string, privateKey }) {
const follow = {
type: 'Follow',
id: by.url + '/toto',
actor: by.url,
object: to.url
}
const body = activityPubContextify(follow)
const httpSignature = {
algorithm: HTTP_SIGNATURE.ALGORITHM,
authorizationHeaderName: HTTP_SIGNATURE.HEADER_NAME,
keyId: by.url,
key: by.privateKey,
headers: HTTP_SIGNATURE.HEADERS_TO_SIGN
}
const headers = buildGlobalHeaders(body)
return makeAPRequest(to.url, body, httpSignature, headers)
}
export {
makeAPRequest,
makeFollowRequest
}

View File

@ -5054,7 +5054,7 @@ jsonify@~0.0.0:
jsonld@^0.5.12:
version "0.5.21"
resolved "http://registry.npmjs.org/jsonld/-/jsonld-0.5.21.tgz#4d5b78d717eb92bcd1ac9d88e34efad95370c0bf"
resolved "https://registry.yarnpkg.com/jsonld/-/jsonld-0.5.21.tgz#4d5b78d717eb92bcd1ac9d88e34efad95370c0bf"
integrity sha512-1dQhaw1Eb3p7Cz5ECE2DNPwLvTmK+f6D45hACBdonJaFKP1bN9zlKLZWbPZQeZtduAc/LNv10J4ML0IiTBVahw==
dependencies:
rdf-canonize "^0.2.1"