Skip to content

Commit

Permalink
getKeyOwner() function
Browse files Browse the repository at this point in the history
  • Loading branch information
dahlia committed Apr 11, 2024
1 parent e78b836 commit 737e080
Show file tree
Hide file tree
Showing 3 changed files with 87 additions and 2 deletions.
1 change: 1 addition & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
39 changes: 38 additions & 1 deletion httpsig/mod.test.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
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 () => {
Expand Down Expand Up @@ -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);
});
49 changes: 48 additions & 1 deletion httpsig/mod.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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;
}

0 comments on commit 737e080

Please sign in to comment.