more work towards actor key proxy
Signed-off-by: eternal-flame-AD <yume@yumechi.jp>
This commit is contained in:
parent
6b0cc0d725
commit
46e64aecb2
6 changed files with 60 additions and 16 deletions
packages/backend/src
yume-mods/misskey-auto-deploy-entrypoint
32
packages/backend/src/core/ActorKeySignerService.ts
Normal file
32
packages/backend/src/core/ActorKeySignerService.ts
Normal 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');
|
||||
}
|
||||
}
|
|
@ -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`,
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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"]
|
||||
|
|
|
@ -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(),
|
||||
|
|
Loading…
Add table
Reference in a new issue