Unverified Commit 42bdce80 authored by Hong Minhee's avatar Hong Minhee
Browse files

Merge tag '1.3.22' into 1.4-maintenance

Fedify 1.3.22
parents 32e56aa7 ef6b7c47
Loading
Loading
Loading
Loading
+59 −0
Original line number Diff line number Diff line
@@ -8,6 +8,13 @@ Version 1.4.15

To be released.

 -  Fixed a bug where `verifyRequest()` function threw a `TypeError` when
    verifying HTTP Signatures with `created` or `expires` fields in
    the `Signature` header as defined in draft-cavage-http-signatures-12,
    causing `500 Internal Server Error` responses in inbox handlers.
    Now it correctly handles these fields as unquoted integers according
    to the specification.


Version 1.4.14
--------------
@@ -282,6 +289,19 @@ Released on February 5, 2025.
[#195]: https://github.com/fedify-dev/fedify/issues/195


Version 1.3.22
--------------

Released on August 25, 2025.

 -  Fixed a bug where `verifyRequest()` function threw a `TypeError` when
    verifying HTTP Signatures with `created` or `expires` fields in
    the `Signature` header as defined in draft-cavage-http-signatures-12,
    causing `500 Internal Server Error` responses in inbox handlers.
    Now it correctly handles these fields as unquoted integers according
    to the specification.


Version 1.3.21
--------------

@@ -673,6 +693,19 @@ Released on November 30, 2024.
[#193]: https://github.com/fedify-dev/fedify/issues/193


Version 1.2.25
--------------

Released on August 25, 2025.

 -  Fixed a bug where `verifyRequest()` function threw a `TypeError` when
    verifying HTTP Signatures with `created` or `expires` fields in
    the `Signature` header as defined in draft-cavage-http-signatures-12,
    causing `500 Internal Server Error` responses in inbox handlers.
    Now it correctly handles these fields as unquoted integers according
    to the specification.


Version 1.2.24
--------------

@@ -1097,6 +1130,19 @@ Released on October 31, 2024.
[#118]: https://github.com/fedify-dev/fedify/issues/118


Version 1.1.25
--------------

Released on August 25, 2025.

 -  Fixed a bug where `verifyRequest()` function threw a `TypeError` when
    verifying HTTP Signatures with `created` or `expires` fields in
    the `Signature` header as defined in draft-cavage-http-signatures-12,
    causing `500 Internal Server Error` responses in inbox handlers.
    Now it correctly handles these fields as unquoted integers according
    to the specification.


Version 1.1.24
--------------

@@ -1562,6 +1608,19 @@ Released on October 20, 2024.
[#150]: https://github.com/fedify-dev/fedify/issues/150


Version 1.0.28
--------------

Released on August 25, 2025.

 -  Fixed a bug where `verifyRequest()` function threw a `TypeError` when
    verifying HTTP Signatures with `created` or `expires` fields in
    the `Signature` header as defined in draft-cavage-http-signatures-12,
    causing `500 Internal Server Error` responses in inbox handlers.
    Now it correctly handles these fields as unquoted integers according
    to the specification.


Version 1.0.27
--------------

+20 −1
Original line number Diff line number Diff line
import { assertEquals } from "@std/assert";
import { assert, assertEquals } from "@std/assert";
import { mockDocumentLoader } from "../testing/docloader.ts";
import {
  rsaPrivateKey2,
@@ -212,4 +212,23 @@ test("verifyRequest()", async () => {
    ),
    rsaPublicKey1,
  );

  const request2 = new Request("https://c27a97f98d5f.ngrok.app/i/inbox", {
    method: "POST",
    body:
      '{"@context":["https://www.w3.org/ns/activitystreams","https://w3id.org/security/v1"],"actor":"https://oeee.cafe/ap/users/3609fd4e-d51d-4db8-9f04-4189815864dd","object":{"actor":"https://c27a97f98d5f.ngrok.app/i","object":"https://oeee.cafe/ap/users/3609fd4e-d51d-4db8-9f04-4189815864dd","type":"Follow","id":"https://c27a97f98d5f.ngrok.app/i#follows/https://oeee.cafe/ap/users/3609fd4e-d51d-4db8-9f04-4189815864dd"},"type":"Accept","id":"https://oeee.cafe/objects/0fc2608f-5660-4b91-b8c7-63c0c2ac2e20"}',
    headers: {
      Host: "c27a97f98d5f.ngrok.app",
      "Content-Type": "application/activity+json",
      Date: "Mon, 25 Aug 2025 12:58:14 GMT",
      Digest: "SHA-256=YZyjeVQW5GwliJowASkteBJhFBTq3eQk/AMqRETc//A=",
      Signature:
        'keyId="https://oeee.cafe/ap/users/3609fd4e-d51d-4db8-9f04-4189815864dd#main-key",algorithm="hs2019",created="1756126694",expires="1756130294",headers="(request-target) (created) (expires) content-type date digest host",signature="XFb0jl2uMhE7RhbneE9sK9Zls2qZec8iy6+9O8UgDQeBGJThORFLjXKlps4QO1WAf1YSVB/i5aV6yF+h73Lm3ZiuAJDx1h+00iLsxoYuIw1CZvF0V2jELoo3sQ2/ZzqeoO6H5TbK7tKnU+ulFAPTuJgjIvPwYl11OMRouVS34NiaHP9Yx9pU813TLv37thG/hUKanyq8kk0IJWtDWteY/zxDvzoe7VOkBXVBHslMyrNAI/5JGulVQAQp/E61dJAhTHHIyGxkc/7iutWFZuqFXIiPJ9KR2OuKDj/B32hEzlsf5xH/CjqOJPIg1qMK8FzDiALCq6zjiKIBEnW8HQc/hQ=="',
    },
  });
  const options2: VerifyRequestOptions = {
    ...options,
    currentTime: Temporal.Instant.from("2025-08-25T12:58:14Z"),
  };
  assert(await verifyRequest(request2, options2) != null);
});
+63 −4
Original line number Diff line number Diff line
@@ -365,8 +365,10 @@ async function verifyRequestInternal(
  }
  const sigValues = Object.fromEntries(
    sigHeader.split(",").map((pair) =>
      pair.match(/^\s*([A-Za-z]+)="([^"]*)"\s*$/)
    ).filter((m) => m != null).map((m) => m!.slice(1, 3) as [string, string]),
      pair.match(/^\s*([A-Za-z]+)=(?:"([^"]*)"|(\d+))\s*$/)
    ).filter((m) => m != null).map((m) =>
      [m![1], m![2] ?? m![3]] as [string, string]
    ),
  );
  if (!("keyId" in sigValues)) {
    logger.debug(
@@ -387,6 +389,59 @@ async function verifyRequestInternal(
    );
    return null;
  }
  if ("expires" in sigValues) {
    const expiresSeconds = parseInt(sigValues.expires);
    if (!Number.isInteger(expiresSeconds)) {
      logger.debug(
        "Failed to verify; invalid expires field in the Signature header: {expires}.",
        { expires: sigValues.expires, signature: sigHeader },
      );
      return null;
    }
    const expires = Temporal.Instant.fromEpochMilliseconds(
      expiresSeconds * 1000,
    );
    if (Temporal.Instant.compare(now, expires) > 0) {
      logger.debug(
        "Failed to verify; signature expired at {expires} (now: {now}).",
        {
          expires: expires.toString(),
          now: now.toString(),
          signature: sigHeader,
        },
      );
      return null;
    }
  }
  if ("created" in sigValues) {
    const createdSeconds = parseInt(sigValues.created);
    if (!Number.isInteger(createdSeconds)) {
      logger.debug(
        "Failed to verify; invalid created field in the Signature header: {created}.",
        { created: sigValues.created, signature: sigHeader },
      );
      return null;
    }
    if (timeWindow !== false) {
      const created = Temporal.Instant.fromEpochMilliseconds(
        createdSeconds * 1000,
      );
      const tw: Temporal.DurationLike = timeWindow ?? { minutes: 1 };
      if (Temporal.Instant.compare(created, now.add(tw)) > 0) {
        logger.debug(
          "Failed to verify; created is too far in the future.",
          { created: created.toString(), now: now.toString() },
        );
        return null;
      } else if (Temporal.Instant.compare(created, now.subtract(tw)) < 0) {
        logger.debug(
          "Failed to verify; created is too far in the past.",
          { created: created.toString(), now: now.toString() },
        );
        return null;
      }
    }
  }
  const { keyId, headers, signature } = sigValues;
  span?.setAttribute("http_signatures.key_id", keyId);
  if ("algorithm" in sigValues) {
@@ -420,9 +475,13 @@ async function verifyRequestInternal(
  }
  const message = headerNames.map((name) =>
    `${name}: ` +
    (name == "(request-target)"
    (name === "(request-target)"
      ? `${request.method.toLowerCase()} ${new URL(request.url).pathname}`
      : name == "host"
      : name === "(created)"
      ? (sigValues.created ?? "")
      : name === "(expires)"
      ? (sigValues.expires ?? "")
      : name === "host"
      ? request.headers.get("host") ?? new URL(request.url).host
      : request.headers.get(name))
  ).join("\n");
+24 −0
Original line number Diff line number Diff line
{
  "@context": [
    "https://www.w3.org/ns/activitystreams",
    "https://w3id.org/security/v1"
  ],
  "id": "https://oeee.cafe/ap/users/3609fd4e-d51d-4db8-9f04-4189815864dd",
  "type": "Person",
  "preferredUsername": "hongminhee",
  "name": "洪兔",
  "inbox": "https://oeee.cafe/ap/users/3609fd4e-d51d-4db8-9f04-4189815864dd/inbox",
  "outbox": "https://oeee.cafe/ap/users/3609fd4e-d51d-4db8-9f04-4189815864dd/outbox",
  "publicKey": {
    "id": "https://oeee.cafe/ap/users/3609fd4e-d51d-4db8-9f04-4189815864dd#main-key",
    "owner": "https://oeee.cafe/ap/users/3609fd4e-d51d-4db8-9f04-4189815864dd",
    "publicKeyPem": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAowJfOzpA/nAYyL0bVDTm\niCAOlhFCIBnqwk1jvGrbkDhMzxlsgyoDqUSlmcJdKaPwu24YdFajDtJIgto27Ju7\nIC3hB7OFchnZ4JZrdYFo7CJABOzK58o12sdmmkCdY5hXWf1604E+mzyIdBAJ1FFJ\nL8vP07VEUsZ7yo9x0iVNg7HpCOK+y6BqI2GHS2dq9qkqQEIhC2TKHXn/RQVXwYB6\nG+YQmVUtcsbCVKdcWyTKhItLRGnepu3BqBSbieLxV27B1O9NFSoPu8xiBUnYwMoe\nsUQCE5tGcqxc75HzcVCbq7PqVqHZ1NW9RYssaSUqi4FYcjXxQrR08DrAl8rR4eXT\n4QIDAQAB\n-----END PUBLIC KEY-----\n"
  },
  "endpoints": {
    "type": "as:Endpoints",
    "sharedInbox": "https://oeee.cafe/inbox"
  },
  "followers": "https://oeee.cafe/ap/users/3609fd4e-d51d-4db8-9f04-4189815864dd/followers",
  "manuallyApprovesFollowers": false,
  "url": "https://oeee.cafe/@hongminhee"
}