Unverified Commit 9dcc1e18 authored by Hong Minhee's avatar Hong Minhee
Browse files

Let sig verification time window configurable

parent abb5f620
Loading
Loading
Loading
Loading
+9 −0
Original line number Diff line number Diff line
@@ -29,6 +29,15 @@ To be released.
     -  Added `SendActivityOptions.excludeBaseUris` property.
     -  Added `ExtractInboxesParameters.excludeBaseUris` property.

 -  The time window for signature verification is now configurable.

     -  The default time window for signature verification is now a minute (was
        30 seconds).
     -  Added `signatureTimeWindow` option to `FederationParameters` interface.
     -  Added `VerifyOptions` interface.
     -  The signature of the `verify()` function is revamped; it now optionally
        takes a `VerifyOptions` object as the second parameter.

 -  Added more log messages using the [LogTape] library.  Currently the below
    logger categories are used:

+6 −5
Original line number Diff line number Diff line
@@ -314,6 +314,7 @@ export interface InboxHandlerParameters<TContextData> {
  >;
  inboxErrorHandler?: InboxErrorHandler<TContextData>;
  onNotFound(request: Request): Response | Promise<Response>;
  signatureTimeWindow: Temporal.DurationLike;
}

export async function handleInbox<TContextData>(
@@ -327,6 +328,7 @@ export async function handleInbox<TContextData>(
    inboxListeners,
    inboxErrorHandler,
    onNotFound,
    signatureTimeWindow,
  }: InboxHandlerParameters<TContextData>,
): Promise<Response> {
  const logger = getLogger(["fedify", "federation", "inbox"]);
@@ -341,11 +343,10 @@ export async function handleInbox<TContextData>(
      return await onNotFound(request);
    }
  }
  const key = await verify(
    request,
    context.documentLoader,
    context.contextLoader,
  );
  const key = await verify(request, {
    ...context,
    timeWindow: signatureTimeWindow,
  });
  if (key == null) {
    logger.error("Failed to verify the request signature.", { handle });
    const response = new Response("Failed to verify the request signature.", {
+5 −3
Original line number Diff line number Diff line
@@ -36,9 +36,11 @@ Deno.test("Federation.createContext()", async (t) => {
  mf.mock("GET@/object", async (req) => {
    const v = await verify(
      req,
      mockDocumentLoader,
      mockDocumentLoader,
      Temporal.Now.instant(),
      {
        contextLoader: mockDocumentLoader,
        documentLoader: mockDocumentLoader,
        currentTime: Temporal.Now.instant(),
      },
    );
    return new Response(JSON.stringify(v != null), {
      headers: { "Content-Type": "application/json" },
+15 −5
Original line number Diff line number Diff line
@@ -108,6 +108,15 @@ export interface FederationParameters {
   */
  onOutboxError?: OutboxErrorHandler;

  /**
   * The time window for verifying the signature of incoming requests.  If the
   * request is older or newer than this window, it is rejected.  By default,
   * the window is a minute.
   *
   * @since 0.9.0
   */
  signatureTimeWindow?: Temporal.DurationLike;

  // TODO: The following option should be removed, and exponential backoff
  // should be used instead:
  backoffSchedule?: Temporal.Duration[];
@@ -159,6 +168,7 @@ export class Federation<TContextData> {
  #authenticatedDocumentLoaderFactory: AuthenticatedDocumentLoaderFactory;
  #treatHttps: boolean;
  #onOutboxError?: OutboxErrorHandler;
  #signatureTimeWindow: Temporal.DurationLike;
  #backoffSchedule: Temporal.Duration[];

  /**
@@ -175,6 +185,7 @@ export class Federation<TContextData> {
      authenticatedDocumentLoaderFactory,
      treatHttps,
      onOutboxError,
      signatureTimeWindow,
      backoffSchedule,
    }: FederationParameters,
  ) {
@@ -204,6 +215,7 @@ export class Federation<TContextData> {
        getAuthenticatedDocumentLoader;
    this.#onOutboxError = onOutboxError;
    this.#treatHttps = treatHttps ?? false;
    this.#signatureTimeWindow = signatureTimeWindow ?? { minutes: 1 };
    this.#backoffSchedule = backoffSchedule ?? [
      3_000,
      15_000,
@@ -491,6 +503,7 @@ export class Federation<TContextData> {
    if (request == null) return context;
    let signedKey: CryptographicKey | null | undefined = undefined;
    let signedKeyOwner: Actor | null | undefined = undefined;
    const timeWindow = this.#signatureTimeWindow;
    const reqCtx: RequestContext<TContextData> = {
      ...context,
      request,
@@ -554,11 +567,7 @@ export class Federation<TContextData> {
      },
      async getSignedKey() {
        if (signedKey !== undefined) return signedKey;
        return signedKey = await verify(
          request,
          context.documentLoader,
          context.contextLoader,
        );
        return signedKey = await verify(request, { ...context, timeWindow });
      },
      async getSignedKeyOwner() {
        if (signedKeyOwner !== undefined) return signedKeyOwner;
@@ -1424,6 +1433,7 @@ export class Federation<TContextData> {
          inboxListeners: this.#inboxListeners,
          inboxErrorHandler: this.#inboxErrorHandler,
          onNotFound,
          signatureTimeWindow: this.#signatureTimeWindow,
        });
      case "following":
        return await handleCollection(request, {
+1 −1
Original line number Diff line number Diff line
@@ -141,11 +141,11 @@ Deno.test("sendActivity()", async (t) => {
  let request: Request | null = null;
  mf.mock("POST@/inbox", async (req) => {
    request = req;
    const key = await verify(req, mockDocumentLoader, mockDocumentLoader);
    const options = {
      documentLoader: mockDocumentLoader,
      contextLoader: mockDocumentLoader,
    };
    const key = await verify(req, options);
    const activity = await Activity.fromJsonLd(await req.json(), options);
    if (key != null && await doesActorOwnKey(activity, key, options)) {
      verified = true;
Loading