more work towards actor key proxy

Signed-off-by: eternal-flame-AD <yume@yumechi.jp>
This commit is contained in:
ゆめ 2025-03-06 12:31:17 -06:00
parent 6b0cc0d725
commit 46e64aecb2
No known key found for this signature in database
6 changed files with 60 additions and 16 deletions
packages/backend/src
yume-mods/misskey-auto-deploy-entrypoint

View file

@ -0,0 +1,32 @@
import crypto from 'node:crypto';
import { Inject, Injectable } from "@nestjs/common";
@Injectable()
export class ActorKeySignerService {
private proxyUri?: string;
constructor() {
this.proxyUri = process.env.MISSKEY_ACTOR_KEY_PROXY_URL;
}
public async sign(id: string, privateKey: string, data: string): Promise<string> {
if (this.proxyUri && privateKey === 'proxy') {
const response = await fetch(`${this.proxyUri}signature/${id}`, {
method: 'POST',
body: data,
});
if (!response.ok) {
throw new Error(`Failed to sign data: ${response.statusText}`);
}
return response.text();
}
const signer = crypto.createSign('sha256');
signer.update(data);
signer.end();
const signature = signer.sign(privateKey);
return signature.toString('base64');
}
}

View file

@ -19,6 +19,7 @@ import type Logger from '@/logger.js';
import { validateContentTypeSetAsActivityPub } from '@/core/activitypub/misc/validator.js';
import { assertActivityMatchesUrls, FetchAllowSoftFailMask as FetchAllowSoftFailMask } from '@/core/activitypub/misc/check-against-url.js';
import type { IObject } from './type.js';
import { ActorKeySignerService } from '../ActorKeySignerService.js';
type Request = {
url: string;
@ -39,7 +40,8 @@ type PrivateKey = {
};
export class ApRequestCreator {
static createSignedPost(args: { key: PrivateKey, url: string, body: string, digest?: string, additionalHeaders: Record<string, string> }): Signed {
static async createSignedPost(signer: ActorKeySignerService, args: { key: PrivateKey, url: string, body: string, digest?: string, additionalHeaders: Record<string, string> }): Promise<Signed> {
const u = new URL(args.url);
const digestHeader = args.digest ?? this.createDigest(args.body);
@ -54,7 +56,7 @@ export class ApRequestCreator {
}, args.additionalHeaders),
};
const result = this.#signToRequest(request, args.key, ['(request-target)', 'date', 'host', 'digest']);
const result = await this.#signToRequest(signer, request, args.key, ['(request-target)', 'date', 'host', 'digest']);
return {
request,
@ -68,7 +70,7 @@ export class ApRequestCreator {
return `SHA-256=${crypto.createHash('sha256').update(body).digest('base64')}`;
}
static createSignedGet(args: { key: PrivateKey, url: string, additionalHeaders: Record<string, string> }): Signed {
static async createSignedGet(signer: ActorKeySignerService, args: { key: PrivateKey, url: string, additionalHeaders: Record<string, string> }): Promise<Signed> {
const u = new URL(args.url);
const request: Request = {
@ -81,7 +83,7 @@ export class ApRequestCreator {
}, args.additionalHeaders),
};
const result = this.#signToRequest(request, args.key, ['(request-target)', 'date', 'host', 'accept']);
const result = await this.#signToRequest(signer, request, args.key, ['(request-target)', 'date', 'host', 'accept']);
return {
request,
@ -91,9 +93,9 @@ export class ApRequestCreator {
};
}
static #signToRequest(request: Request, key: PrivateKey, includeHeaders: string[]): Signed {
static async #signToRequest(signer: ActorKeySignerService, request: Request, key: PrivateKey, includeHeaders: string[]): Promise<Signed> {
const signingString = this.#genSigningString(request, includeHeaders);
const signature = crypto.sign('sha256', Buffer.from(signingString), key.privateKeyPem).toString('base64');
const signature = await signer.sign(key.keyId, key.privateKeyPem, signingString);
const signatureHeader = `keyId="${key.keyId}",algorithm="rsa-sha256",headers="${includeHeaders.join(' ')}",signature="${signature}"`;
request.headers = this.#objectAssignWithLcKey(request.headers, {
@ -149,6 +151,7 @@ export class ApRequestService {
private httpRequestService: HttpRequestService,
private loggerService: LoggerService,
private utilityService: UtilityService,
private actorKeySignerService: ActorKeySignerService,
) {
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
this.logger = this.loggerService?.getLogger('ap-request'); // なぜか TypeError: Cannot read properties of undefined (reading 'getLogger') と言われる
@ -160,7 +163,7 @@ export class ApRequestService {
const keypair = await this.userKeypairService.getUserKeypair(user.id);
const req = ApRequestCreator.createSignedPost({
const req = await ApRequestCreator.createSignedPost(this.actorKeySignerService, {
key: {
privateKeyPem: keypair.privateKey,
keyId: `${this.config.url}/users/${user.id}#main-key`,
@ -189,7 +192,7 @@ export class ApRequestService {
const _followAlternate = followAlternate ?? true;
const keypair = await this.userKeypairService.getUserKeypair(user.id);
const req = ApRequestCreator.createSignedGet({
const req = await ApRequestCreator.createSignedGet(this.actorKeySignerService, {
key: {
privateKeyPem: keypair.privateKey,
keyId: `${this.config.url}/users/${user.id}#main-key`,

View file

@ -11,6 +11,7 @@ import { CONTEXT, PRELOADED_CONTEXTS } from './misc/contexts.js';
import { validateContentTypeSetAsJsonLD } from './misc/validator.js';
import type { JsonLdDocument } from 'jsonld';
import type { JsonLd as JsonLdObject, RemoteDocument } from 'jsonld/jsonld-spec.js';
import { ActorKeySignerService } from '../ActorKeySignerService.js';
// RsaSignature2017 implementation is based on https://github.com/transmute-industries/RsaSignature2017
@ -21,6 +22,7 @@ class JsonLd {
constructor(
private httpRequestService: HttpRequestService,
private actorKeySignerService: ActorKeySignerService,
) {
}
@ -45,17 +47,13 @@ class JsonLd {
const toBeSigned = await this.createVerifyData(data, options);
const signer = crypto.createSign('sha256');
signer.update(toBeSigned);
signer.end();
const signature = signer.sign(privateKey);
const signature = await this.actorKeySignerService.sign(creator, privateKey, toBeSigned);
return {
...data,
signature: {
...options,
signatureValue: signature.toString('base64'),
signatureValue: signature,
},
};
}
@ -169,11 +167,12 @@ class JsonLd {
export class JsonLdService {
constructor(
private httpRequestService: HttpRequestService,
private actorKeySignerService: ActorKeySignerService,
) {
}
@bindThis
public use(): JsonLd {
return new JsonLd(this.httpRequestService);
return new JsonLd(this.httpRequestService, this.actorKeySignerService);
}
}

View file

@ -48,6 +48,7 @@ import { ReversiChannelService } from './api/stream/channels/reversi.js';
import { ReversiGameChannelService } from './api/stream/channels/reversi-game.js';
import { SigninWithPasskeyApiService } from './api/SigninWithPasskeyApiService.js';
import { MetricsService } from './api/MetricsService.js';
import { ActorKeySignerService } from '@/core/ActorKeySignerService.js';
@Module({
imports: [
@ -60,6 +61,7 @@ import { MetricsService } from './api/MetricsService.js';
FeedService,
HealthServerService,
UrlPreviewService,
ActorKeySignerService,
ActivityPubServerService,
FileServerService,
NodeinfoServerService,

View file

@ -17,7 +17,7 @@ yaml-rust2 = "0.10.0"
tokio = { workspace = true, features = ["rt", "net", "time", "sync", "macros", "process", "signal", "io-util"] }
[features]
default = ["apparmor-dynamic", "actor-key-proxy"]
default = ["apparmor-dynamic"]
apparmor-dynamic = []
support-unix = ["dep:tower", "dep:axum", "apparmor-dynamic"]
actor-key-proxy = ["support-unix", "dep:actor-key-proxy"]

View file

@ -543,6 +543,14 @@ fn main() {
child.env("NODE_ENV", "production");
}
#[cfg(feature = "actor-key-proxy")]
{
child.env(
"MISSKEY_ACTOR_KEY_PROXY_URL",
"http://unix:/run/actor-key-proxy.sock:/",
);
}
child.env(
ENV_MISSKEY_CONFIG_PATH,
misskey_config.to_string_lossy().as_ref(),