diff --git a/package.json b/package.json index 86257312ac..58e82c53ee 100644 --- a/package.json +++ b/package.json @@ -60,6 +60,7 @@ "cbor": "8.1.0", "chalk": "4.1.2", "cli-highlight": "2.1.11", + "co-body": "6.1.0", "content-disposition": "0.5.4", "crc-32": "1.2.2", "cropperjs": "1.5.13", @@ -101,7 +102,6 @@ "koa": "2.13.4", "koa-bodyparser": "4.3.0", "koa-favicon": "2.1.0", - "koa-json-body": "5.3.0", "koa-logger": "3.2.1", "koa-mount": "4.0.0", "koa-send": "5.0.1", @@ -201,6 +201,7 @@ "@types/bcryptjs": "2.4.2", "@types/bull": "4.10.0", "@types/cbor": "6.0.0", + "@types/co-body": "6.1.3", "@types/dateformat": "3.0.1", "@types/double-ended-queue": "2.1.2", "@types/escape-regexp": "0.0.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 4c7a0c5863..a9f4008774 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -90,6 +90,9 @@ dependencies: cli-highlight: specifier: 2.1.11 version: 2.1.11 + co-body: + specifier: 6.1.0 + version: 6.1.0 content-disposition: specifier: 0.5.4 version: 0.5.4 @@ -213,9 +216,6 @@ dependencies: koa-favicon: specifier: 2.1.0 version: 2.1.0 - koa-json-body: - specifier: 5.3.0 - version: 5.3.0 koa-logger: specifier: 3.2.1 version: 3.2.1 @@ -509,6 +509,9 @@ devDependencies: '@types/cbor': specifier: 6.0.0 version: 6.0.0 + '@types/co-body': + specifier: 6.1.3 + version: 6.1.3 '@types/dateformat': specifier: 3.0.1 version: 3.0.1 @@ -1266,6 +1269,13 @@ packages: cbor: 8.1.0 dev: true + /@types/co-body@6.1.3: + resolution: {integrity: sha512-UhuhrQ5hclX6UJctv5m4Rfp52AfG9o9+d9/HwjxhVB5NjXxr5t9oKgJxN8xRHgr35oo8meUEHUPFWiKg6y71aA==} + dependencies: + '@types/node': 18.11.18 + '@types/qs': 6.9.7 + dev: true + /@types/connect@3.4.35: resolution: {integrity: sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ==} dependencies: @@ -3102,15 +3112,6 @@ packages: resolution: {integrity: sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA==} engines: {node: '>=0.10.0'} - /co-body@5.2.0: - resolution: {integrity: sha512-sX/LQ7LqUhgyaxzbe7IqwPeTr2yfpfUIQ/dgpKo6ZI4y4lpQA0YxAomWIY+7I7rHWcG02PG+OuPREzMW/5tszQ==} - dependencies: - inflation: 2.0.0 - qs: 6.11.0 - raw-body: 2.5.1 - type-is: 1.6.18 - dev: false - /co-body@6.1.0: resolution: {integrity: sha512-m7pOT6CdLN7FuXUcpuz/8lfQ/L77x8SchHCF4G0RBTJO20Wzmhn5Sp4/5WsKy8OSpifBSUrmg83qEqaDHdyFuQ==} dependencies: @@ -6529,13 +6530,6 @@ packages: mz: 2.7.0 dev: false - /koa-json-body@5.3.0: - resolution: {integrity: sha512-M2P3zLOs2XiYCpJLGSTXOKij4u5vJ8pbAMXXarXQnwsx4DwDav9qn081tYI2RdZ79B159Pdk4bRfvwl/sazL8A==} - engines: {node: '>= 4.0.0'} - dependencies: - co-body: 5.2.0 - dev: false - /koa-logger@3.2.1: resolution: {integrity: sha512-MjlznhLLKy9+kG8nAXKJLM0/ClsQp/Or2vI3a5rbSQmgl8IJBQO0KI5FA70BvW+hqjtxjp49SpH2E7okS6NmHg==} engines: {node: '>= 7.6.0'} diff --git a/src/@types/koa-json-body.d.ts b/src/@types/koa-json-body.d.ts deleted file mode 100644 index 7e9516a0c3..0000000000 --- a/src/@types/koa-json-body.d.ts +++ /dev/null @@ -1,15 +0,0 @@ -declare module 'koa-json-body' { - import { Middleware } from 'koa'; - - interface IKoaJsonBodyOptions { - strict?: boolean; - limit?: string; - fallback?: boolean; - } - - function koaJsonBody(opt?: IKoaJsonBodyOptions): Middleware; - - namespace koaJsonBody {} // Hack - - export = koaJsonBody; -} diff --git a/src/server/activitypub.ts b/src/server/activitypub.ts index aab027e2c9..578430d1d4 100644 --- a/src/server/activitypub.ts +++ b/src/server/activitypub.ts @@ -1,6 +1,11 @@ import * as Router from '@koa/router'; -import * as json from 'koa-json-body'; +import config from '../config'; +import * as coBody from 'co-body'; +import * as crypto from 'crypto'; +import { IActivity } from '../remote/activitypub/type'; import * as httpSignature from 'http-signature'; +import Logger from '../services/logger'; +import { inspect } from 'util'; import { renderActivity } from '../remote/activitypub/renderer'; import renderNote from '../remote/activitypub/renderer/note'; @@ -18,22 +23,72 @@ import { ILocalUser, User } from '../models/entities/user'; import { In } from 'typeorm'; import { ensure } from '../prelude/ensure'; +const logger = new Logger('activitypub'); + // Init router const router = new Router(); //#region Routing -function inbox(ctx: Router.RouterContext) { - let signature; +async function inbox(ctx: Router.RouterContext) { + if (ctx.req.headers.host !== config.host) { + ctx.status = 400; + return; + } + + // parse body + const { parsed, raw } = await coBody.json(ctx, { + limit: '64kb', + returnRawBody: true, + }); + ctx.request.body = parsed; + + let signature: httpSignature.IParsedSignature; try { - signature = httpSignature.parseRequest(ctx.req, { 'headers': [] }); + signature = httpSignature.parseRequest(ctx.req, { 'headers': ['(request-target)', 'digest', 'host', 'date'] }); } catch (e) { + logger.warn(`inbox: signature parse error: ${inspect(e)}`); + ctx.status = 401; + return; + } + + // Digestヘッダーの検証 + const digest = ctx.req.headers.digest; + + // 無いとか複数あるとかダメ! + if (typeof digest !== 'string') { + logger.warn(`inbox: unrecognized digest header 1`); + ctx.status = 401; + return; + } + + const match = digest.match(/^([0-9A-Za-z-]+)=(.+)$/); + + if (match == null) { + logger.warn(`inbox: unrecognized digest header 2`); + ctx.status = 401; + return; + } + + const digestAlgo = match[1]; + const digestExpected = match[2]; + + if (digestAlgo.toUpperCase() !== 'SHA-256') { + logger.warn(`inbox: unsupported algorithm`); + ctx.status = 401; + return; + } + + const digestActual = crypto.createHash('sha256').update(raw).digest('base64'); + + if (digestExpected !== digestActual) { + logger.warn(`inbox: digest missmatch`); ctx.status = 401; return; } - processInbox(ctx.request.body, signature); + processInbox(ctx.request.body as IActivity, signature); ctx.status = 202; } @@ -59,8 +114,8 @@ export function setResponseType(ctx: Router.RouterContext) { } // inbox -router.post('/inbox', json({ limit: '64kb' }), inbox); -router.post('/users/:user/inbox', json({ limit: '64kb' }), inbox); +router.post('/inbox', inbox); +router.post('/users/:user/inbox', inbox); // note router.get('/notes/:note', async (ctx, next) => {