diff options
| author | MeiMei <30769358+mei23@users.noreply.github.com> | 2020-05-09 08:21:42 +0900 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2020-05-09 08:21:42 +0900 |
| commit | 070f1f3c6ee3188e1b16236366877c1c243601c1 (patch) | |
| tree | f77fa43be8524698bc9e46246748a3163dbd4a7a /src/queue | |
| parent | WebAuthnでログインできないのを修正 (#6327) (diff) | |
| download | sharkey-070f1f3c6ee3188e1b16236366877c1c243601c1.tar.gz sharkey-070f1f3c6ee3188e1b16236366877c1c243601c1.tar.bz2 sharkey-070f1f3c6ee3188e1b16236366877c1c243601c1.zip | |
APリファクタとLD-Signatureの検証に対応 (#6300)
* DbResolver
* inbox types
* 認証順を変更
* User/Keyあたりをまとめる
* LD-Signatue
* Validate contexts url
* LD-Signature DocumentLoaderにProxyとTimeout
Diffstat (limited to 'src/queue')
| -rw-r--r-- | src/queue/index.ts | 7 | ||||
| -rw-r--r-- | src/queue/processors/inbox.ts | 164 |
2 files changed, 79 insertions, 92 deletions
diff --git a/src/queue/index.ts b/src/queue/index.ts index 76e26d8e46..163c57d691 100644 --- a/src/queue/index.ts +++ b/src/queue/index.ts @@ -12,6 +12,7 @@ import procesObjectStorage from './processors/object-storage'; import { queueLogger } from './logger'; import { DriveFile } from '../models/entities/drive-file'; import { getJobInfo } from './get-job-info'; +import { IActivity } from '../remote/activitypub/type'; function initializeQueue(name: string, limitPerSec = -1) { return new Queue(name, { @@ -29,6 +30,12 @@ function initializeQueue(name: string, limitPerSec = -1) { }); } +export type InboxJobData = { + activity: IActivity, + /** HTTP-Signature */ + signature: httpSignature.IParsedSignature +}; + function renderError(e: Error): any { return { stack: e?.stack, diff --git a/src/queue/processors/inbox.ts b/src/queue/processors/inbox.ts index 74108f354b..6eea2e5646 100644 --- a/src/queue/processors/inbox.ts +++ b/src/queue/processors/inbox.ts @@ -1,95 +1,111 @@ import * as Bull from 'bull'; import * as httpSignature from 'http-signature'; -import { IRemoteUser } from '../../models/entities/user'; import perform from '../../remote/activitypub/perform'; -import { resolvePerson, updatePerson } from '../../remote/activitypub/models/person'; import Logger from '../../services/logger'; import { registerOrFetchInstanceDoc } from '../../services/register-or-fetch-instance-doc'; -import { Instances, Users, UserPublickeys } from '../../models'; +import { Instances } from '../../models'; import { instanceChart } from '../../services/chart'; -import { UserPublickey } from '../../models/entities/user-publickey'; import { fetchMeta } from '../../misc/fetch-meta'; -import { toPuny } from '../../misc/convert-host'; -import { validActor } from '../../remote/activitypub/type'; -import { ensure } from '../../prelude/ensure'; +import { toPuny, extractDbHost } from '../../misc/convert-host'; +import { getApId } from '../../remote/activitypub/type'; import { fetchNodeinfo } from '../../services/fetch-nodeinfo'; +import { InboxJobData } from '..'; +import DbResolver from '../../remote/activitypub/db-resolver'; +import { resolvePerson } from '../../remote/activitypub/models/person'; +import { LdSignature } from '../../remote/activitypub/misc/ld-signature'; const logger = new Logger('inbox'); // ユーザーのinboxにアクティビティが届いた時の処理 -export default async (job: Bull.Job): Promise<void> => { - const signature = job.data.signature; +export default async (job: Bull.Job<InboxJobData>): Promise<string> => { + const signature = job.data.signature; // HTTP-signature const activity = job.data.activity; //#region Log const info = Object.assign({}, activity); delete info['@context']; - delete info['signature']; logger.debug(JSON.stringify(info, null, 2)); //#endregion - const keyIdLower = signature.keyId.toLowerCase(); - let user: IRemoteUser; - let key: UserPublickey; - - if (keyIdLower.startsWith('acct:')) { - logger.warn(`Old keyId is no longer supported. ${keyIdLower}`); - return; - } - - // アクティビティ内のホストの検証 const host = toPuny(new URL(signature.keyId).hostname); - try { - ValidateActivity(activity, host); - } catch (e) { - logger.warn(e.message); - return; - } // ブロックしてたら中断 const meta = await fetchMeta(); if (meta.blockedHosts.includes(host)) { - logger.info(`Blocked request: ${host}`); - return; + return `Blocked request: ${host}`; } - const _key = await UserPublickeys.findOne({ - keyId: signature.keyId - }); + const keyIdLower = signature.keyId.toLowerCase(); + if (keyIdLower.startsWith('acct:')) { + return `Old keyId is no longer supported. ${keyIdLower}`; + } - if (_key) { - // 登録済みユーザー - user = await Users.findOne(_key.userId) as IRemoteUser; - key = _key; - } else { - // 未登録ユーザーの場合はリモート解決 - user = await resolvePerson(activity.actor) as IRemoteUser; - if (user == null) { - throw new Error('failed to resolve user'); - } + const dbResolver = new DbResolver(); + + // HTTP-Signature keyIdを元にDBから取得 + let authUser = await dbResolver.getAuthUserFromKeyId(signature.keyId); - key = await UserPublickeys.findOne(user.id).then(ensure); + // keyIdでわからなければ、activity.actorを元にDBから取得 || activity.actorを元にリモートから取得 + if (authUser == null) { + authUser = await dbResolver.getAuthUserFromApId(getApId(activity.actor)); } - // Update Person activityの場合は、ここで署名検証/更新処理まで実施して終了 - if (activity.type === 'Update') { - if (activity.object && validActor.includes(activity.object.type)) { - if (!httpSignature.verifySignature(signature, key.keyPem)) { - logger.warn('Update activity received, but signature verification failed.'); - } else { - updatePerson(activity.actor, null, activity.object); + // それでもわからなければ終了 + if (authUser == null) { + return `skip: failed to resolve user`; + } + + // HTTP-Signatureの検証 + if (!httpSignature.verifySignature(signature, authUser.key.keyPem)) { + return 'signature verification failed'; + } + + // signatureのsignerは、activity.actorと一致する必要がある + if (authUser.user.uri !== activity.actor) { + // 一致しなくても、でもLD-Signatureがありそうならそっちも見る + if (activity.signature) { + if (activity.signature.type !== 'RsaSignature2017') { + return `skip: unsupported LD-signature type ${activity.signature.type}`; + } + + // activity.signature.creator: https://example.oom/users/user#main-key + // みたいになっててUserを引っ張れば公開キーも入ることを期待する + if (activity.signature.creator) { + const candicate = activity.signature.creator.replace(/#.*/, ''); + await resolvePerson(candicate).catch(() => null); + } + + // keyIdからLD-Signatureのユーザーを取得 + authUser = await dbResolver.getAuthUserFromKeyId(activity.signature.creator); + if (authUser == null) { + return `skip: LD-Signatureのユーザーが取得できませんでした`; + } + + // LD-Signature検証 + const ldSignature = new LdSignature(); + const verified = await ldSignature.verifyRsaSignature2017(activity, authUser.key.keyPem).catch(() => false); + if (!verified) { + return `skip: LD-Signatureの検証に失敗しました`; + } + + // もう一度actorチェック + if (authUser.user.uri !== activity.actor) { + return `skip: LD-Signature user(${authUser.user.uri}) !== activity.actor(${activity.actor})`; } - return; } } - if (!httpSignature.verifySignature(signature, key.keyPem)) { - logger.error('signature verification failed'); - return; + // activity.idがあればホストが署名者のホストであることを確認する + if (typeof activity.id === 'string') { + const signerHost = extractDbHost(authUser.user.uri!); + const activityIdHost = extractDbHost(activity.id); + if (signerHost !== activityIdHost) { + return `skip: signerHost(${signerHost}) !== activity.id host(${activityIdHost}`; + } } // Update stats - registerOrFetchInstanceDoc(user.host).then(i => { + registerOrFetchInstanceDoc(authUser.user.host).then(i => { Instances.update(i.id, { latestRequestReceivedAt: new Date(), lastCommunicatedAt: new Date(), @@ -102,42 +118,6 @@ export default async (job: Bull.Job): Promise<void> => { }); // アクティビティを処理 - await perform(user, activity); + await perform(authUser.user, activity); + return `ok`; }; - -/** - * Validate host in activity - * @param activity Activity - * @param host Expect host - */ -function ValidateActivity(activity: any, host: string) { - // id (if exists) - if (typeof activity.id === 'string') { - const uriHost = toPuny(new URL(activity.id).hostname); - if (host !== uriHost) { - const diag = activity.signature ? '. Has LD-Signature. Forwarded?' : ''; - throw new Error(`activity.id(${activity.id}) has different host(${host})${diag}`); - } - } - - // actor (if exists) - if (typeof activity.actor === 'string') { - const uriHost = toPuny(new URL(activity.actor).hostname); - if (host !== uriHost) throw new Error('activity.actor has different host'); - } - - // For Create activity - if (activity.type === 'Create' && activity.object) { - // object.id (if exists) - if (typeof activity.object.id === 'string') { - const uriHost = toPuny(new URL(activity.object.id).hostname); - if (host !== uriHost) throw new Error('activity.object.id has different host'); - } - - // object.attributedTo (if exists) - if (typeof activity.object.attributedTo === 'string') { - const uriHost = toPuny(new URL(activity.object.attributedTo).hostname); - if (host !== uriHost) throw new Error('activity.object.attributedTo has different host'); - } - } -} |