Compact LD-signed activities against well-known context
This should defend against some spoofing attacks, see also https://nvd.nist.gov/vuln/detail/CVE-2022-24307 for Mastodon,febb499fcb
from Iceshrimp ande790d6be90
for Firefish Thanks to @tesaguri@fedibird.com for reporting and providing the patch.
This commit is contained in:
parent
e0afeff248
commit
6ae01e28aa
|
@ -31,6 +31,7 @@ import { IdService } from '@/core/IdService.js';
|
||||||
import { MetaService } from '../MetaService.js';
|
import { MetaService } from '../MetaService.js';
|
||||||
import { LdSignatureService } from './LdSignatureService.js';
|
import { LdSignatureService } from './LdSignatureService.js';
|
||||||
import { ApMfmService } from './ApMfmService.js';
|
import { ApMfmService } from './ApMfmService.js';
|
||||||
|
import { CONTEXT } from './misc/contexts.js';
|
||||||
import type { IAccept, IActivity, IAdd, IAnnounce, IApDocument, IApEmoji, IApHashtag, IApImage, IApMention, IBlock, ICreate, IDelete, IFlag, IFollow, IKey, ILike, IMove, IObject, IPost, IQuestion, IReject, IRemove, ITombstone, IUndo, IUpdate } from './type.js';
|
import type { IAccept, IActivity, IAdd, IAnnounce, IApDocument, IApEmoji, IApHashtag, IApImage, IApMention, IBlock, ICreate, IDelete, IFlag, IFollow, IKey, ILike, IMove, IObject, IPost, IQuestion, IReject, IRemove, ITombstone, IUndo, IUpdate } from './type.js';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
|
@ -785,48 +786,7 @@ export class ApRendererService {
|
||||||
x.id = `${this.config.url}/${randomUUID()}`;
|
x.id = `${this.config.url}/${randomUUID()}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
return Object.assign({
|
return Object.assign({ '@context': CONTEXT }, x as T & { id: string });
|
||||||
'@context': [
|
|
||||||
'https://www.w3.org/ns/activitystreams',
|
|
||||||
'https://w3id.org/security/v1',
|
|
||||||
{
|
|
||||||
Key: 'sec:Key',
|
|
||||||
// as non-standards
|
|
||||||
manuallyApprovesFollowers: 'as:manuallyApprovesFollowers',
|
|
||||||
sensitive: 'as:sensitive',
|
|
||||||
Hashtag: 'as:Hashtag',
|
|
||||||
quoteUrl: 'as:quoteUrl',
|
|
||||||
fedibird: 'http://fedibird.com/ns#',
|
|
||||||
quoteUri: 'fedibird:quoteUri',
|
|
||||||
// Mastodon
|
|
||||||
toot: 'http://joinmastodon.org/ns#',
|
|
||||||
Emoji: 'toot:Emoji',
|
|
||||||
featured: 'toot:featured',
|
|
||||||
discoverable: 'toot:discoverable',
|
|
||||||
// schema
|
|
||||||
schema: 'http://schema.org#',
|
|
||||||
PropertyValue: 'schema:PropertyValue',
|
|
||||||
value: 'schema:value',
|
|
||||||
// Misskey
|
|
||||||
misskey: 'https://misskey-hub.net/ns#',
|
|
||||||
'_misskey_content': 'misskey:_misskey_content',
|
|
||||||
'_misskey_quote': 'misskey:_misskey_quote',
|
|
||||||
'_misskey_reaction': 'misskey:_misskey_reaction',
|
|
||||||
'_misskey_votes': 'misskey:_misskey_votes',
|
|
||||||
'_misskey_summary': 'misskey:_misskey_summary',
|
|
||||||
'isCat': 'misskey:isCat',
|
|
||||||
// Firefish
|
|
||||||
firefish: 'https://joinfirefish.org/ns#',
|
|
||||||
speakAsCat: 'firefish:speakAsCat',
|
|
||||||
// Sharkey
|
|
||||||
sharkey: 'https://joinsharkey.org/ns#',
|
|
||||||
backgroundUrl: 'sharkey:backgroundUrl',
|
|
||||||
listenbrainz: 'sharkey:listenbrainz',
|
|
||||||
// vcard
|
|
||||||
vcard: 'http://www.w3.org/2006/vcard/ns#',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
}, x as T & { id: string });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
|
|
|
@ -88,6 +88,16 @@ class LdSignature {
|
||||||
return verifyData;
|
return verifyData;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@bindThis
|
||||||
|
public async compact(data: any, context: any = CONTEXT): Promise<JsonLdDocument> {
|
||||||
|
const customLoader = this.getLoader();
|
||||||
|
// XXX: Importing jsonld dynamically since Jest frequently fails to import it statically
|
||||||
|
// https://github.com/misskey-dev/misskey/pull/9894#discussion_r1103753595
|
||||||
|
return (await import('jsonld')).default.compact(data, context, {
|
||||||
|
documentLoader: customLoader,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
public async normalize(data: JsonLdDocument): Promise<string> {
|
public async normalize(data: JsonLdDocument): Promise<string> {
|
||||||
const customLoader = this.getLoader();
|
const customLoader = this.getLoader();
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type { JsonLd } from 'jsonld/jsonld-spec.js';
|
import type { Context, JsonLd } from 'jsonld/jsonld-spec.js';
|
||||||
|
|
||||||
/* eslint:disable:quotemark indent */
|
/* eslint:disable:quotemark indent */
|
||||||
const id_v1 = {
|
const id_v1 = {
|
||||||
|
@ -526,6 +526,50 @@ const activitystreams = {
|
||||||
},
|
},
|
||||||
} satisfies JsonLd;
|
} satisfies JsonLd;
|
||||||
|
|
||||||
|
const context_iris = [
|
||||||
|
'https://www.w3.org/ns/activitystreams',
|
||||||
|
'https://w3id.org/security/v1',
|
||||||
|
];
|
||||||
|
|
||||||
|
const extension_context_definition = {
|
||||||
|
Key: 'sec:Key',
|
||||||
|
// as non-standards
|
||||||
|
manuallyApprovesFollowers: 'as:manuallyApprovesFollowers',
|
||||||
|
sensitive: 'as:sensitive',
|
||||||
|
Hashtag: 'as:Hashtag',
|
||||||
|
quoteUrl: 'as:quoteUrl',
|
||||||
|
fedibird: 'http://fedibird.com/ns#',
|
||||||
|
quoteUri: 'fedibird:quoteUri',
|
||||||
|
// Mastodon
|
||||||
|
toot: 'http://joinmastodon.org/ns#',
|
||||||
|
Emoji: 'toot:Emoji',
|
||||||
|
featured: 'toot:featured',
|
||||||
|
discoverable: 'toot:discoverable',
|
||||||
|
// schema
|
||||||
|
schema: 'http://schema.org#',
|
||||||
|
PropertyValue: 'schema:PropertyValue',
|
||||||
|
value: 'schema:value',
|
||||||
|
// Misskey
|
||||||
|
misskey: 'https://misskey-hub.net/ns#',
|
||||||
|
'_misskey_content': 'misskey:_misskey_content',
|
||||||
|
'_misskey_quote': 'misskey:_misskey_quote',
|
||||||
|
'_misskey_reaction': 'misskey:_misskey_reaction',
|
||||||
|
'_misskey_votes': 'misskey:_misskey_votes',
|
||||||
|
'_misskey_summary': 'misskey:_misskey_summary',
|
||||||
|
'isCat': 'misskey:isCat',
|
||||||
|
// Firefish
|
||||||
|
firefish: 'https://joinfirefish.org/ns#',
|
||||||
|
speakAsCat: 'firefish:speakAsCat',
|
||||||
|
// Sharkey
|
||||||
|
sharkey: 'https://joinsharkey.org/ns#',
|
||||||
|
backgroundUrl: 'sharkey:backgroundUrl',
|
||||||
|
listenbrainz: 'sharkey:listenbrainz',
|
||||||
|
// vcard
|
||||||
|
vcard: 'http://www.w3.org/2006/vcard/ns#',
|
||||||
|
} satisfies Context;
|
||||||
|
|
||||||
|
export const CONTEXT: (string | Context)[] = [...context_iris, extension_context_definition];
|
||||||
|
|
||||||
export const CONTEXTS: Record<string, JsonLd> = {
|
export const CONTEXTS: Record<string, JsonLd> = {
|
||||||
'https://w3id.org/identity/v1': id_v1,
|
'https://w3id.org/identity/v1': id_v1,
|
||||||
'https://w3id.org/security/v1': security_v1,
|
'https://w3id.org/security/v1': security_v1,
|
||||||
|
|
|
@ -15,6 +15,7 @@ import InstanceChart from '@/core/chart/charts/instance.js';
|
||||||
import ApRequestChart from '@/core/chart/charts/ap-request.js';
|
import ApRequestChart from '@/core/chart/charts/ap-request.js';
|
||||||
import FederationChart from '@/core/chart/charts/federation.js';
|
import FederationChart from '@/core/chart/charts/federation.js';
|
||||||
import { getApId } from '@/core/activitypub/type.js';
|
import { getApId } from '@/core/activitypub/type.js';
|
||||||
|
import type { IActivity } from '@/core/activitypub/type.js';
|
||||||
import type { MiRemoteUser } from '@/models/User.js';
|
import type { MiRemoteUser } from '@/models/User.js';
|
||||||
import type { MiUserPublickey } from '@/models/UserPublickey.js';
|
import type { MiUserPublickey } from '@/models/UserPublickey.js';
|
||||||
import { ApDbResolverService } from '@/core/activitypub/ApDbResolverService.js';
|
import { ApDbResolverService } from '@/core/activitypub/ApDbResolverService.js';
|
||||||
|
@ -52,7 +53,7 @@ export class InboxProcessorService {
|
||||||
@bindThis
|
@bindThis
|
||||||
public async process(job: Bull.Job<InboxJobData>): Promise<string> {
|
public async process(job: Bull.Job<InboxJobData>): Promise<string> {
|
||||||
const signature = job.data.signature; // HTTP-signature
|
const signature = job.data.signature; // HTTP-signature
|
||||||
const activity = job.data.activity;
|
let activity = job.data.activity;
|
||||||
|
|
||||||
//#region Log
|
//#region Log
|
||||||
const info = Object.assign({}, activity);
|
const info = Object.assign({}, activity);
|
||||||
|
@ -150,6 +151,17 @@ export class InboxProcessorService {
|
||||||
throw new Bull.UnrecoverableError('skip: LD-Signatureの検証に失敗しました');
|
throw new Bull.UnrecoverableError('skip: LD-Signatureの検証に失敗しました');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// アクティビティを正規化
|
||||||
|
delete activity.signature;
|
||||||
|
try {
|
||||||
|
activity = await ldSignature.compact(activity) as IActivity;
|
||||||
|
} catch (e) {
|
||||||
|
throw new Bull.UnrecoverableError(`skip: failed to compact activity: ${e}`);
|
||||||
|
}
|
||||||
|
// TODO: 元のアクティビティと非互換な形に正規化される場合は転送をスキップする
|
||||||
|
// https://github.com/mastodon/mastodon/blob/664b0ca/app/services/activitypub/process_collection_service.rb#L24-L29
|
||||||
|
activity.signature = ldSignature;
|
||||||
|
|
||||||
// もう一度actorチェック
|
// もう一度actorチェック
|
||||||
if (authUser.user.uri !== activity.actor) {
|
if (authUser.user.uri !== activity.actor) {
|
||||||
throw new Bull.UnrecoverableError(`skip: LD-Signature user(${authUser.user.uri}) !== activity.actor(${activity.actor})`);
|
throw new Bull.UnrecoverableError(`skip: LD-Signature user(${authUser.user.uri}) !== activity.actor(${activity.actor})`);
|
||||||
|
|
Loading…
Reference in New Issue