Unverified Commit 737e0809 authored by Hong Minhee's avatar Hong Minhee
Browse files

getKeyOwner() function

parent e78b836d
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -19,6 +19,7 @@ To be released.
     -  Added `RequestContext.getSignedKey()` method.
     -  Added `FederationFetchOptions.onUnauthorized` option for handling
        unauthorized fetches.
     -  Added `getKeyOwner()` function.

 -  The default implementation of `FederationFetchOptions.onNotAcceptable`
    option now responds with `Vary: Accept, Signature` header.
+38 −1
Original line number Diff line number Diff line
import { Temporal } from "@js-temporal/polyfill";
import { assert, assertEquals, assertFalse } from "@std/assert";
import { doesActorOwnKey, sign, verify } from "../mod.ts";
import { doesActorOwnKey, getKeyOwner, sign, verify } from "../mod.ts";
import { mockDocumentLoader } from "../testing/docloader.ts";
import { privateKey2, publicKey1, publicKey2 } from "../testing/keys.ts";
import { lookupObject } from "../vocab/lookup.ts";
import { Create } from "../vocab/vocab.ts";

Deno.test("sign()", async () => {
@@ -143,3 +144,39 @@ Deno.test("doesActorOwnKey()", async () => {
  assertFalse(await doesActorOwnKey(activity2, publicKey1, mockDocumentLoader));
  assertFalse(await doesActorOwnKey(activity2, publicKey2, mockDocumentLoader));
});

Deno.test("getKeyOwner()", async () => {
  const owner = await getKeyOwner(
    new URL("https://example.com/users/handle#main-key"),
    mockDocumentLoader,
  );
  assertEquals(
    owner,
    await lookupObject("https://example.com/users/handle", {
      documentLoader: mockDocumentLoader,
    }),
  );

  const owner2 = await getKeyOwner(
    new URL("https://example.com/key"),
    mockDocumentLoader,
  );
  assertEquals(
    owner2,
    await lookupObject("https://example.com/person", {
      documentLoader: mockDocumentLoader,
    }),
  );

  const noOwner = await getKeyOwner(
    new URL("https://example.com/key2"),
    mockDocumentLoader,
  );
  assertEquals(noOwner, null);

  const noOwner2 = await getKeyOwner(
    new URL("https://example.com/object"),
    mockDocumentLoader,
  );
  assertEquals(noOwner2, null);
});
+48 −1
Original line number Diff line number Diff line
@@ -8,7 +8,7 @@ import { Temporal } from "@js-temporal/polyfill";
import { equals } from "@std/bytes";
import { decodeBase64, encodeBase64 } from "@std/encoding/base64";
import type { DocumentLoader } from "../runtime/docloader.ts";
import { isActor } from "../vocab/actor.ts";
import { type Actor, isActor } from "../vocab/actor.ts";
import {
  type Activity,
  CryptographicKey,
@@ -224,3 +224,50 @@ export async function doesActorOwnKey(
  }
  return false;
}

/**
 * Gets the actor that owns the specified key.  Returns `null` if the key has no known owner.
 *
 * @param keyId The ID of the key to check.
 * @param documentLoader The document loader to use for fetching the key and its owner.
 * @returns The actor that owns the key, or `null` if the key has no known owner.
 * @sicne 0.7.0
 */
export async function getKeyOwner(
  keyId: URL,
  documentLoader: DocumentLoader,
): Promise<Actor | null> {
  let keyDoc: unknown;
  try {
    const { document } = await documentLoader(keyId.href);
    keyDoc = document;
  } catch (_) {
    return null;
  }
  let object: ASObject | CryptographicKey;
  try {
    object = await ASObject.fromJsonLd(keyDoc, { documentLoader });
  } catch (e) {
    if (!(e instanceof TypeError)) throw e;
    try {
      object = await CryptographicKey.fromJsonLd(keyDoc, { documentLoader });
    } catch (e) {
      if (e instanceof TypeError) return null;
      throw e;
    }
  }
  let owner: Actor | null = null;
  if (object instanceof CryptographicKey) {
    if (object.ownerId == null) return null;
    owner = await object.getOwner({ documentLoader });
  } else if (isActor(object)) {
    owner = object;
  } else {
    return null;
  }
  if (owner == null) return null;
  for (const kid of owner.publicKeyIds) {
    if (kid.href === keyId.href) return owner;
  }
  return null;
}