Unverified Commit a4d2cde1 authored by Hong Minhee's avatar Hong Minhee
Browse files

Instrument `verifyJsonLd()`

parent d4dc3044
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -71,6 +71,7 @@ To be released.
     -  Added `VerifyProofOptions.tracerProvider` option.
     -  Added `VerifyObjectOptions.tracerProvider` option.
     -  Added `SignObjectOptions.tracerProvider` option.
     -  Added `VerifyJsonLdOptions.tracerProvider` option.

 -  Added `@fedify/fedify/x/sveltekit` module for integrating with [SvelteKit]
    hook.  [[#171], [#183] by Jiyu Park]
+4 −0
Original line number Diff line number Diff line
@@ -124,6 +124,7 @@ spans:
| `activitypub.lookup_object`      | Client      | Looks up the Activity Streams object. |
| `http_signatures.sign`           | Internal    | Signs the HTTP request.               |
| `http_signatures.verify`         | Internal    | Verifies the HTTP request signature.  |
| `ld_signatures.verify`           | Internal    | Verifies the Linked Data signature.   |
| `object_integrity_proofs.sign`   | Internal    | Makes the object integrity proof.     |
| `object_integrity_proofs.verify` | Internal    | Verifies the object integrity proof.  |
| `webfinger.handle`               | Server      | Handles the WebFinger request.        |
@@ -160,6 +161,9 @@ for ActivityPub:
| `http_signatures.algorithm`           | string   | The algorithm of the HTTP request signature.                                             | `"rsa-sha256"`                                                       |
| `http_signatures.key_id`              | string   | The public key ID of the HTTP request signature.                                         | `"https://example.com/actor/1#main-key"`                             |
| `http_signatures.digest.{algorithm}`  | string   | The digest of the HTTP request body in hexadecimal.  The `{algorithm}` is the digest algorithm (e.g., `sha`, `sha-256`). | `"d41d8cd98f00b204e9800998ecf8427e"` |
| `ld_signatures.key_id`                | string   | The public key ID of the Linked Data signature.                                          | `"https://example.com/actor/1#main-key"`                             |
| `ld_signatures.signature`             | string   | The signature of the Linked Data in hexadecimal.                                         | `"73a74c990beabe6e59cc68f9c6db7811b59cbb22fd12dcffb3565b651540efe9"` |
| `ld_signatures.type`                  | string   | The algorithm of the Linked Data signature.                                              | `"RsaSignature2017"`                                                 |
| `object_integrity_proofs.cryptosuite` | string   | The cryptographic suite of the object integrity proof.                                   | `"eddsa-jcs-2022"`                                                   |
| `object_integrity_proofs.key_id`      | string   | The public key ID of the object integrity proof.                                         | `"https://example.com/actor/1#main-key"`                             |
| `object_integrity_proofs.signature`   | string   | The integrity proof of the object in hexadecimal.                                        | `"73a74c990beabe6e59cc68f9c6db7811b59cbb22fd12dcffb3565b651540efe9"` |
+1 −0
Original line number Diff line number Diff line
@@ -460,6 +460,7 @@ export async function handleInbox<TContextData>(
    contextLoader: context.contextLoader,
    documentLoader: context.documentLoader,
    keyCache,
    tracerProvider,
  });
  const jsonWithoutSig = detachSignature(json);
  let activity: Activity | null = null;
+83 −21
Original line number Diff line number Diff line
import { getLogger } from "@logtape/logtape";
import { SpanStatusCode, trace, type TracerProvider } from "@opentelemetry/api";
import { decodeBase64, encodeBase64 } from "@std/encoding/base64";
import { encodeHex } from "@std/encoding/hex";
// @ts-ignore TS7016
import jsonld from "jsonld";
import metadata from "../deno.json" with { type: "json" };
import {
  type DocumentLoader,
  getDocumentLoader,
} from "../runtime/docloader.ts";
import { getTypeId } from "../vocab/type.ts";
import { Activity, CryptographicKey, Object } from "../vocab/vocab.ts";
import { fetchKey, type KeyCache, validateCryptoKey } from "./key.ts";

@@ -310,6 +313,12 @@ export async function verifySignature(
 * Options for verifying JSON-LD documents.
 */
export interface VerifyJsonLdOptions extends VerifySignatureOptions {
  /**
   * The OpenTelemetry tracer provider for tracing the verification process.
   * If omitted, the global tracer provider is used.
   * @since 1.3.0
   */
  tracerProvider?: TracerProvider;
}

/**
@@ -325,8 +334,50 @@ export async function verifyJsonLd(
  jsonLd: unknown,
  options: VerifyJsonLdOptions = {},
): Promise<boolean> {
  const tracerProvider = options.tracerProvider ?? trace.getTracerProvider();
  const tracer = tracerProvider.getTracer(metadata.name, metadata.version);
  return await tracer.startActiveSpan(
    "ld_signatures.verify",
    async (span) => {
      try {
        const object = await Object.fromJsonLd(jsonLd, options);
  const attributions = new Set(object.attributionIds.map((uri) => uri.href));
        if (object.id != null) {
          span.setAttribute("activitypub.object.id", object.id.href);
        }
        span.setAttribute("activitypub.object.type", getTypeId(object).href);
        if (
          typeof jsonLd === "object" && jsonLd != null &&
          "signature" in jsonLd && typeof jsonLd.signature === "object" &&
          jsonLd.signature != null
        ) {
          if (
            "creator" in jsonLd.signature &&
            typeof jsonLd.signature.creator === "string"
          ) {
            span.setAttribute(
              "ld_signatures.key_id",
              jsonLd.signature.creator,
            );
          }
          if (
            "signatureValue" in jsonLd.signature &&
            typeof jsonLd.signature.signatureValue === "string"
          ) {
            span.setAttribute(
              "ld_signatures.signature",
              jsonLd.signature.signatureValue,
            );
          }
          if (
            "type" in jsonLd.signature &&
            typeof jsonLd.signature.type === "string"
          ) {
            span.setAttribute("ld_signatures.type", jsonLd.signature.type);
          }
        }
        const attributions = new Set(
          object.attributionIds.map((uri) => uri.href),
        );
        if (object instanceof Activity) {
          for (const uri of object.actorIds) attributions.add(uri.href);
        }
@@ -339,13 +390,24 @@ export async function verifyJsonLd(
        attributions.delete(key.ownerId.href);
        if (attributions.size > 0) {
          logger.debug(
      "Some attributions are not authenticated by the Linked Data Signatures" +
        ": {attributions}.",
            "Some attributions are not authenticated by the Linked Data " +
              "Signatures: {attributions}.",
            { attributions: [...attributions] },
          );
          return false;
        }
        return true;
      } catch (error) {
        span.setStatus({
          code: SpanStatusCode.ERROR,
          message: String(error),
        });
        throw error;
      } finally {
        span.end();
      }
    },
  );
}

async function hashJsonLd(