Unverified Commit 060749dc authored by Hong Minhee's avatar Hong Minhee
Browse files

`KvKeyCache`

parent 23a96c0c
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -98,6 +98,7 @@
    "instanceof",
    "interoperating",
    "jsonld",
    "keycache",
    "keypair",
    "langstr",
    "Lemmy",
+3 −36
Original line number Diff line number Diff line
@@ -11,7 +11,6 @@ import { accepts } from "@std/http/negotiation";
import metadata from "../deno.json" with { type: "json" };
import type { DocumentLoader } from "../runtime/docloader.ts";
import { verifyRequest } from "../sig/http.ts";
import type { KeyCache } from "../sig/key.ts";
import { detachSignature, verifyJsonLd } from "../sig/ld.ts";
import { doesActorOwnKey } from "../sig/owner.ts";
import { verifyObject } from "../sig/proof.ts";
@@ -19,9 +18,8 @@ import type { Recipient } from "../vocab/actor.ts";
import { getTypeId } from "../vocab/type.ts";
import {
  Activity,
  CryptographicKey,
  type CryptographicKey,
  Link,
  Multikey,
  Object,
  OrderedCollection,
  OrderedCollectionPage,
@@ -38,6 +36,7 @@ import type {
} from "./callback.ts";
import type { Context, InboxContext, RequestContext } from "./context.ts";
import type { InboxListenerSet } from "./inbox.ts";
import { KvKeyCache } from "./keycache.ts";
import type { KvKey, KvStore } from "./kv.ts";
import type { MessageQueue } from "./mq.ts";
import type { InboxMessage } from "./queue.ts";
@@ -545,39 +544,7 @@ async function handleInboxInternal<TContextData>(
      headers: { "Content-Type": "text/plain; charset=utf-8" },
    });
  }
  const keyCache: KeyCache & { nullKeys: Set<string> } = {
    nullKeys: new Set(),
    async get(keyId: URL) {
      if (this.nullKeys.has(keyId.href)) return null;
      const serialized = await kv.get([
        ...kvPrefixes.publicKey,
        keyId.href,
      ]);
      if (serialized == null) return undefined;
      let object: Object;
      try {
        object = await Object.fromJsonLd(serialized, ctx);
      } catch {
        await kv.delete([...kvPrefixes.publicKey, keyId.href]);
        return undefined;
      }
      if (object instanceof CryptographicKey || object instanceof Multikey) {
        return object;
      }
      await kv.delete([...kvPrefixes.publicKey, keyId.href]);
      return undefined;
    },
    async set(keyId: URL, key: CryptographicKey | Multikey | null) {
      if (key == null) {
        this.nullKeys.add(keyId.href);
        await kv.delete([...kvPrefixes.publicKey, keyId.href]);
        return;
      }
      this.nullKeys.delete(keyId.href);
      const serialized = await key.toJsonLd(ctx);
      await kv.set([...kvPrefixes.publicKey, keyId.href], serialized);
    },
  };
  const keyCache = new KvKeyCache(kv, kvPrefixes.publicKey, ctx);
  const ldSigVerified = await verifyJsonLd(json, {
    contextLoader: ctx.contextLoader,
    documentLoader: ctx.documentLoader,
+67 −0
Original line number Diff line number Diff line
import { assert } from "@std/assert/assert";
import { assertEquals } from "@std/assert/assert-equals";
import { assertInstanceOf } from "@std/assert/assert-instance-of";
import { test } from "../testing/mod.ts";
import { CryptographicKey, Multikey } from "../vocab/vocab.ts";
import { KvKeyCache } from "./keycache.ts";
import { MemoryKvStore } from "./kv.ts";

test("KvKeyCache.set()", async () => {
  const kv = new MemoryKvStore();
  const cache = new KvKeyCache(kv, ["pk"]);

  await cache.set(
    new URL("https://example.com/key"),
    new CryptographicKey({ id: new URL("https://example.com/key") }),
  );
  assertEquals(
    await kv.get(["pk", "https://example.com/key"]),
    {
      "@context": "https://w3id.org/security/v1",
      id: "https://example.com/key",
      type: "CryptographicKey",
    },
  );

  await cache.set(
    new URL("https://example.com/key2"),
    new Multikey({ id: new URL("https://example.com/key2") }),
  );
  assertEquals(
    await kv.get(["pk", "https://example.com/key2"]),
    {
      "@context": "https://w3id.org/security/multikey/v1",
      id: "https://example.com/key2",
      type: "Multikey",
    },
  );

  await cache.set(new URL("https://example.com/null"), null);
  assert(cache.nullKeys.has("https://example.com/null"));
});

test("KvKeyCache.get()", async () => {
  const kv = new MemoryKvStore();
  const cache = new KvKeyCache(kv, ["pk"]);

  await kv.set(["pk", "https://example.com/key"], {
    "@context": "https://w3id.org/security/v1",
    id: "https://example.com/key",
    type: "CryptographicKey",
  });
  const cryptoKey = await cache.get(new URL("https://example.com/key"));
  assertInstanceOf(cryptoKey, CryptographicKey);
  assertEquals(cryptoKey?.id?.href, "https://example.com/key");

  await kv.set(["pk", "https://example.com/key2"], {
    "@context": "https://w3id.org/security/multikey/v1",
    id: "https://example.com/key2",
    type: "Multikey",
  });
  const multikey = await cache.get(new URL("https://example.com/key2"));
  assertInstanceOf(multikey, Multikey);
  assertEquals(multikey?.id?.href, "https://example.com/key2");

  cache.nullKeys.add("https://example.com/null");
  assertEquals(await cache.get(new URL("https://example.com/null")), null);
});
+55 −0
Original line number Diff line number Diff line
import type { DocumentLoader } from "../runtime/docloader.ts";
import type { KeyCache } from "../sig/key.ts";
import { CryptographicKey, Multikey } from "../vocab/vocab.ts";
import type { KvKey, KvStore } from "./kv.ts";

export interface KvKeyCacheOptions {
  documentLoader?: DocumentLoader;
  contextLoader?: DocumentLoader;
}

export class KvKeyCache implements KeyCache {
  readonly kv: KvStore;
  readonly prefix: KvKey;
  readonly options: KvKeyCacheOptions;
  readonly nullKeys: Set<string>;

  constructor(kv: KvStore, prefix: KvKey, options: KvKeyCacheOptions = {}) {
    this.kv = kv;
    this.prefix = prefix;
    this.nullKeys = new Set();
    this.options = options;
  }

  async get(
    keyId: URL,
  ): Promise<CryptographicKey | Multikey | null | undefined> {
    if (this.nullKeys.has(keyId.href)) return null;
    const serialized = await this.kv.get([...this.prefix, keyId.href]);
    if (serialized == null) return undefined;
    try {
      return await CryptographicKey.fromJsonLd(serialized, this.options);
    } catch {
      try {
        return await Multikey.fromJsonLd(serialized, this.options);
      } catch {
        await this.kv.delete([...this.prefix, keyId.href]);
        return undefined;
      }
    }
  }

  async set(
    keyId: URL,
    key: CryptographicKey | Multikey | null,
  ): Promise<void> {
    if (key == null) {
      this.nullKeys.add(keyId.href);
      await this.kv.delete([...this.prefix, keyId.href]);
      return;
    }
    this.nullKeys.delete(keyId.href);
    const serialized = await key.toJsonLd(this.options);
    await this.kv.set([...this.prefix, keyId.href], serialized);
  }
}