summaryrefslogtreecommitdiff
path: root/src/remote
diff options
context:
space:
mode:
authorMeiMei <30769358+mei23@users.noreply.github.com>2020-10-18 01:46:40 +0900
committerGitHub <noreply@github.com>2020-10-18 01:46:40 +0900
commit85a0f696bcea779b02749dae596fff94a1df2467 (patch)
tree747ee51f96c7ada22a835dfc7faee0b13cdfd0e4 /src/remote
parentFix lint (#6732) (diff)
downloadsharkey-85a0f696bcea779b02749dae596fff94a1df2467.tar.gz
sharkey-85a0f696bcea779b02749dae596fff94a1df2467.tar.bz2
sharkey-85a0f696bcea779b02749dae596fff94a1df2467.zip
ActivityPubでリモートのオブジェクトをGETするときのリクエストをHTTP Signatureで署名するオプション (#6731)
* Sign ActivityPub GET * Fix v12, v12.48.0 UI bug
Diffstat (limited to 'src/remote')
-rw-r--r--src/remote/activitypub/request.ts97
-rw-r--r--src/remote/activitypub/resolver.ts13
2 files changed, 109 insertions, 1 deletions
diff --git a/src/remote/activitypub/request.ts b/src/remote/activitypub/request.ts
index ab51fdd93c..0edfcee1e3 100644
--- a/src/remote/activitypub/request.ts
+++ b/src/remote/activitypub/request.ts
@@ -1,3 +1,4 @@
+import * as http from 'http';
import * as https from 'https';
import { sign } from 'http-signature';
import * as crypto from 'crypto';
@@ -7,6 +8,9 @@ import { ILocalUser } from '../../models/entities/user';
import { UserKeypairs } from '../../models';
import { ensure } from '../../prelude/ensure';
import { getAgentByUrl } from '../../misc/fetch';
+import { URL } from 'url';
+import got from 'got';
+import * as Got from 'got';
export default async (user: ILocalUser, url: string, object: any) => {
const timeout = 10 * 1000;
@@ -62,3 +66,96 @@ export default async (user: ILocalUser, url: string, object: any) => {
req.end(data);
});
};
+
+/**
+ * Get AP object with http-signature
+ * @param user http-signature user
+ * @param url URL to fetch
+ */
+export async function signedGet(url: string, user: ILocalUser) {
+ const timeout = 10 * 1000;
+
+ const keypair = await UserKeypairs.findOne({
+ userId: user.id
+ }).then(ensure);
+
+ const req = got.get<any>(url, {
+ headers: {
+ 'Accept': 'application/activity+json, application/ld+json',
+ 'User-Agent': config.userAgent,
+ },
+ responseType: 'json',
+ timeout,
+ hooks: {
+ beforeRequest: [
+ options => {
+ options.request = (url: URL, opt: http.RequestOptions, callback?: (response: any) => void) => {
+ // Select custom agent by URL
+ opt.agent = getAgentByUrl(url, false);
+
+ // Wrap original https?.request
+ const requestFunc = url.protocol === 'http:' ? http.request : https.request;
+ const clientRequest = requestFunc(url, opt, callback) as http.ClientRequest;
+
+ // HTTP-Signature
+ sign(clientRequest, {
+ authorizationHeaderName: 'Signature',
+ key: keypair.privateKey,
+ keyId: `${config.url}/users/${user.id}#main-key`,
+ headers: ['(request-target)', 'host', 'date', 'accept']
+ });
+
+ return clientRequest;
+ };
+ },
+ ],
+ },
+ retry: 0,
+ });
+
+ const res = await receiveResponce(req, 10 * 1024 * 1024);
+
+ return res.body;
+}
+
+/**
+ * Receive response (with size limit)
+ * @param req Request
+ * @param maxSize size limit
+ */
+export async function receiveResponce<T>(req: Got.CancelableRequest<Got.Response<T>>, maxSize: number) {
+ // 応答ヘッダでサイズチェック
+ req.on('response', (res: Got.Response) => {
+ const contentLength = res.headers['content-length'];
+ if (contentLength != null) {
+ const size = Number(contentLength);
+ if (size > maxSize) {
+ req.cancel();
+ }
+ }
+ });
+
+ // 受信中のデータでサイズチェック
+ req.on('downloadProgress', (progress: Got.Progress) => {
+ if (progress.transferred > maxSize) {
+ req.cancel();
+ }
+ });
+
+ // 応答取得 with ステータスコードエラーの整形
+ const res = await req.catch(e => {
+ if (e.name === 'HTTPError') {
+ const statusCode = (e as Got.HTTPError).response.statusCode;
+ const statusMessage = (e as Got.HTTPError).response.statusMessage;
+ throw {
+ name: `StatusError`,
+ statusCode,
+ message: `${statusCode} ${statusMessage}`,
+ };
+ } else {
+ throw e;
+ }
+ });
+
+ return res;
+}
diff --git a/src/remote/activitypub/resolver.ts b/src/remote/activitypub/resolver.ts
index f4bf8f94f9..2871c1cb41 100644
--- a/src/remote/activitypub/resolver.ts
+++ b/src/remote/activitypub/resolver.ts
@@ -1,8 +1,13 @@
+import config from '../../config';
import { getJson } from '../../misc/fetch';
+import { ILocalUser } from '../../models/entities/user';
+import { getInstanceActor } from '../../services/instance-actor';
+import { signedGet } from './request';
import { IObject, isCollectionOrOrderedCollection, ICollection, IOrderedCollection } from './type';
export default class Resolver {
private history: Set<string>;
+ private user?: ILocalUser;
constructor() {
this.history = new Set();
@@ -39,7 +44,13 @@ export default class Resolver {
this.history.add(value);
- const object = await getJson(value, 'application/activity+json, application/ld+json');
+ if (config.signToActivityPubGet && !this.user) {
+ this.user = await getInstanceActor();
+ }
+
+ const object = this.user
+ ? await signedGet(value, this.user)
+ : await getJson(value, 'application/activity+json, application/ld+json');
if (object == null || (
Array.isArray(object['@context']) ?