parent
efb04293bb
commit
337b42bcb1
52 changed files with 691 additions and 1099 deletions
|
@ -3,10 +3,11 @@
|
|||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import * as crypto from 'node:crypto';
|
||||
import { IncomingMessage } from 'node:http';
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import fastifyAccepts from '@fastify/accepts';
|
||||
import { verifyDigestHeader, parseRequestSignature } from '@misskey-dev/node-http-message-signatures';
|
||||
import httpSignature from '@peertube/http-signature';
|
||||
import { Brackets, In, IsNull, LessThan, Not } from 'typeorm';
|
||||
import accepts from 'accepts';
|
||||
import vary from 'vary';
|
||||
|
@ -30,17 +31,12 @@ import { IActivity } from '@/core/activitypub/type.js';
|
|||
import { isQuote, isRenote } from '@/misc/is-renote.js';
|
||||
import type { FastifyInstance, FastifyRequest, FastifyReply, FastifyPluginOptions, FastifyBodyParser } from 'fastify';
|
||||
import type { FindOptionsWhere } from 'typeorm';
|
||||
import { LoggerService } from '@/core/LoggerService.js';
|
||||
import Logger from '@/logger.js';
|
||||
|
||||
const ACTIVITY_JSON = 'application/activity+json; charset=utf-8';
|
||||
const LD_JSON = 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"; charset=utf-8';
|
||||
|
||||
@Injectable()
|
||||
export class ActivityPubServerService {
|
||||
private logger: Logger;
|
||||
private inboxLogger: Logger;
|
||||
|
||||
constructor(
|
||||
@Inject(DI.config)
|
||||
private config: Config,
|
||||
|
@ -75,11 +71,8 @@ export class ActivityPubServerService {
|
|||
private queueService: QueueService,
|
||||
private userKeypairService: UserKeypairService,
|
||||
private queryService: QueryService,
|
||||
private loggerService: LoggerService,
|
||||
) {
|
||||
//this.createServer = this.createServer.bind(this);
|
||||
this.logger = this.loggerService.getLogger('server-ap', 'gray');
|
||||
this.inboxLogger = this.logger.createSubLogger('inbox', 'gray');
|
||||
}
|
||||
|
||||
@bindThis
|
||||
|
@ -107,44 +100,70 @@ export class ActivityPubServerService {
|
|||
}
|
||||
|
||||
@bindThis
|
||||
private async inbox(request: FastifyRequest, reply: FastifyReply) {
|
||||
if (request.body == null) {
|
||||
this.inboxLogger.warn('request body is empty');
|
||||
reply.code(400);
|
||||
return;
|
||||
}
|
||||
private inbox(request: FastifyRequest, reply: FastifyReply) {
|
||||
let signature;
|
||||
|
||||
let signature: ReturnType<typeof parseRequestSignature>;
|
||||
|
||||
const verifyDigest = await verifyDigestHeader(request.raw, request.rawBody || '', true);
|
||||
if (verifyDigest !== true) {
|
||||
this.inboxLogger.warn('digest verification failed');
|
||||
try {
|
||||
signature = httpSignature.parseRequest(request.raw, { 'headers': [] });
|
||||
} catch (e) {
|
||||
reply.code(401);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
signature = parseRequestSignature(request.raw, {
|
||||
requiredInputs: {
|
||||
draft: ['(request-target)', 'digest', 'host', 'date'],
|
||||
},
|
||||
});
|
||||
} catch (err) {
|
||||
this.inboxLogger.warn('signature header parsing failed', { err });
|
||||
if (signature.params.headers.indexOf('host') === -1
|
||||
|| request.headers.host !== this.config.host) {
|
||||
// Host not specified or not match.
|
||||
reply.code(401);
|
||||
return;
|
||||
}
|
||||
|
||||
if (typeof request.body === 'object' && 'signature' in request.body) {
|
||||
// LD SignatureがあればOK
|
||||
this.queueService.inbox(request.body as IActivity, null);
|
||||
reply.code(202);
|
||||
if (signature.params.headers.indexOf('digest') === -1) {
|
||||
// Digest not found.
|
||||
reply.code(401);
|
||||
} else {
|
||||
const digest = request.headers.digest;
|
||||
|
||||
if (typeof digest !== 'string') {
|
||||
// Huh?
|
||||
reply.code(401);
|
||||
return;
|
||||
}
|
||||
|
||||
this.inboxLogger.warn('signature header parsing failed and LD signature not found');
|
||||
reply.code(401);
|
||||
return;
|
||||
const re = /^([a-zA-Z0-9\-]+)=(.+)$/;
|
||||
const match = digest.match(re);
|
||||
|
||||
if (match == null) {
|
||||
// Invalid digest
|
||||
reply.code(401);
|
||||
return;
|
||||
}
|
||||
|
||||
const algo = match[1].toUpperCase();
|
||||
const digestValue = match[2];
|
||||
|
||||
if (algo !== 'SHA-256') {
|
||||
// Unsupported digest algorithm
|
||||
reply.code(401);
|
||||
return;
|
||||
}
|
||||
|
||||
if (request.rawBody == null) {
|
||||
// Bad request
|
||||
reply.code(400);
|
||||
return;
|
||||
}
|
||||
|
||||
const hash = crypto.createHash('sha256').update(request.rawBody).digest('base64');
|
||||
|
||||
if (hash !== digestValue) {
|
||||
// Invalid digest
|
||||
reply.code(401);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
this.queueService.inbox(request.body as IActivity, signature);
|
||||
|
||||
reply.code(202);
|
||||
}
|
||||
|
||||
|
@ -621,7 +640,7 @@ export class ActivityPubServerService {
|
|||
if (this.userEntityService.isLocalUser(user)) {
|
||||
reply.header('Cache-Control', 'public, max-age=180');
|
||||
this.setResponseType(request, reply);
|
||||
return (this.apRendererService.addContext(this.apRendererService.renderKey(user, keypair.publicKey)));
|
||||
return (this.apRendererService.addContext(this.apRendererService.renderKey(user, keypair)));
|
||||
} else {
|
||||
reply.code(400);
|
||||
return;
|
||||
|
|
|
@ -94,13 +94,6 @@ export class NodeinfoServerService {
|
|||
localComments: 0,
|
||||
},
|
||||
metadata: {
|
||||
/**
|
||||
* '00': Draft, RSA only
|
||||
* '01': Draft, Ed25519 suported
|
||||
* '11': RFC 9421, Ed25519 supported
|
||||
*/
|
||||
httpMessageSignaturesImplementationLevel: '01',
|
||||
|
||||
nodeName: meta.name,
|
||||
nodeDescription: meta.description,
|
||||
nodeAdmins: [{
|
||||
|
|
|
@ -56,8 +56,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
const res = [] as [string, number][];
|
||||
|
||||
for (const job of jobs) {
|
||||
const signature = job.data.signature ? 'version' in job.data.signature ? job.data.signature.value : job.data.signature : null;
|
||||
const host = signature ? Array.isArray(signature) ? 'TODO' : new URL(signature.keyId).host : new URL(job.data.activity.actor).host;
|
||||
const host = new URL(job.data.signature.keyId).host;
|
||||
if (res.find(x => x[0] === host)) {
|
||||
res.find(x => x[0] === host)![1]++;
|
||||
} else {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue