Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,14 @@ To be released.

### @fedify/fedify

- Fixed `CryptoKey` and `CryptoKeyPair` values obtained from `node:crypto`
being rejected by `ActorKeyPairsDispatcher`, `SharedInboxKeyDispatcher`,
`SenderKeyPair`, `signRequest()`, `createProof()`, `signObject()`,
`createSignature()`, `signJsonLd()`, `exportJwk()`, and the authenticated
document loader APIs when `@types/node` 24.7.0 or later is installed.
Those APIs now accept a structural key type that both Node.js and
DOM/Web Worker keys satisfy, while the keys returned by Fedify keep the
ambient `CryptoKey` types. [[#554], [#1391]]
- Fixed Linked Data Signatures being emitted with a JSON-LD context that no
longer resolves. The attached `signature` object now uses
`https://w3id.org/security/v1` instead of
Expand Down Expand Up @@ -43,14 +51,21 @@ To be released.
accepted with `closed` discarded.
[[#1339], [#1347] by ChanHaeng Lee\]

[#554]: https://github.com/fedify-dev/fedify/issues/554
[#664]: https://github.com/fedify-dev/fedify/issues/664
[#1339]: https://github.com/fedify-dev/fedify/issues/1339
[#1347]: https://github.com/fedify-dev/fedify/pull/1347
[#1381]: https://github.com/fedify-dev/fedify/issues/1381
[#1390]: https://github.com/fedify-dev/fedify/pull/1390
[#1391]: https://github.com/fedify-dev/fedify/pull/1391

### @fedify/vocab

- Fixed `CryptographicKey` and `Multikey` rejecting a `publicKey` obtained
from `node:crypto` in their constructors and `clone()` when `@types/node`
24.7.0 or later is installed. The property accepts a structural key type
now, while the getters still return the ambient `CryptoKey` type.
[[#554], [#1391]]
- Fixed parsing of date-times without seconds, such as `2024-01-01T10:00Z`,
`2024-01-01T10:00+09:00`, or `2024-01-01T10:00.5Z`, which Activity Streams
2.0 allows. They raised a `RangeError`, or were ignored in
Expand All @@ -72,9 +87,20 @@ To be released.
literals the same way. It treats a literal without a UTC offset as UTC and
handles literals without seconds, including those with a fraction.
[[#1339], [#1347] by ChanHaeng Lee\]
- Added the `CryptoKeyLike` and `CryptoKeyPairLike` types for the structural
key shape that Fedify APIs accept. They cover keys from Node.js,
DOM/Web Workers, and Cloudflare Workers, so `exportSpki()`,
`exportMultibaseKey()`, and `AuthenticatedDocumentLoaderFactory` no longer
reject keys obtained from `node:crypto` with `@types/node` 24.7.0 or later.
An implementation of `AuthenticatedDocumentLoaderFactory` that annotates
the `identity` parameter with the ambient `CryptoKey` type now has to
annotate it with `CryptoKeyLike` instead. [[#554], [#1391]]

### @fedify/vocab-tools

- Changed the code generated by `@fedify/vocab-tools` to import the
`CryptoKeyLike` type from `@fedify/vocab-runtime`. Regenerated code now
requires `@fedify/vocab-runtime` 2.0.36 or later. [[#554], [#1391]]
- Fixed generated `xsd:dateTime` decoders failing on date-times without
seconds, such as `2024-01-01T10:00Z` or `2024-01-01T10:00.5Z`. Regenerate
your vocabulary code to get the fix. Generated code now imports
Expand Down
13 changes: 13 additions & 0 deletions changes.d/fedify/node-webcrypto-keys.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
---
links:
'#1391': https://github.com/fedify-dev/fedify/pull/1391
'#554': https://github.com/fedify-dev/fedify/issues/554
---
- Fixed `CryptoKey` and `CryptoKeyPair` values obtained from `node:crypto`
being rejected by `ActorKeyPairsDispatcher`, `SharedInboxKeyDispatcher`,
`SenderKeyPair`, `signRequest()`, `createProof()`, `signObject()`,
`createSignature()`, `signJsonLd()`, `exportJwk()`, and the authenticated
document loader APIs when `@types/node` 24.7.0 or later is installed.
Those APIs now accept a structural key type that both Node.js and
DOM/Web Worker keys satisfy, while the keys returned by Fedify keep the
ambient `CryptoKey` types. [[#554], [#1391]]
13 changes: 13 additions & 0 deletions changes.d/vocab-runtime/node-webcrypto-keys.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
---
links:
'#1391': https://github.com/fedify-dev/fedify/pull/1391
'#554': https://github.com/fedify-dev/fedify/issues/554
---
- Added the `CryptoKeyLike` and `CryptoKeyPairLike` types for the structural
key shape that Fedify APIs accept. They cover keys from Node.js,
DOM/Web Workers, and Cloudflare Workers, so `exportSpki()`,
`exportMultibaseKey()`, and `AuthenticatedDocumentLoaderFactory` no longer
reject keys obtained from `node:crypto` with `@types/node` 24.7.0 or later.
An implementation of `AuthenticatedDocumentLoaderFactory` that annotates
the `identity` parameter with the ambient `CryptoKey` type now has to
annotate it with `CryptoKeyLike` instead. [[#554], [#1391]]
8 changes: 8 additions & 0 deletions changes.d/vocab-tools/node-webcrypto-keys.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
links:
'#1391': https://github.com/fedify-dev/fedify/pull/1391
'#554': https://github.com/fedify-dev/fedify/issues/554
---
- Changed the code generated by `@fedify/vocab-tools` to import the
`CryptoKeyLike` type from `@fedify/vocab-runtime`. Regenerated code now
requires `@fedify/vocab-runtime` 2.0.36 or later. [[#554], [#1391]]
10 changes: 10 additions & 0 deletions changes.d/vocab/node-webcrypto-keys.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
---
links:
'#1391': https://github.com/fedify-dev/fedify/pull/1391
'#554': https://github.com/fedify-dev/fedify/issues/554
---
- Fixed `CryptographicKey` and `Multikey` rejecting a `publicKey` obtained
from `node:crypto` in their constructors and `clone()` when `@types/node`
24.7.0 or later is installed. The property accepts a structural key type
now, while the getters still return the ambient `CryptoKey` type.
[[#554], [#1391]]
5 changes: 3 additions & 2 deletions packages/fedify/src/federation/builder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import type {
Recipient,
} from "@fedify/vocab";
import { getTypeId } from "@fedify/vocab";
import type { CryptoKeyPairLike } from "@fedify/vocab-runtime";
import { getLogger } from "@logtape/logtape";
import { SpanKind, SpanStatusCode, trace } from "@opentelemetry/api";
import type { Tracer } from "@opentelemetry/api";
Expand Down Expand Up @@ -445,7 +446,7 @@ export class FederationBuilderImpl<TContextData>
this.actorCallbacks = callbacks;
const setters: ActorCallbackSetters<TContextData> = {
setKeyPairsDispatcher: (
dispatcher: ActorKeyPairsDispatcher<TContextData>,
dispatcher: ActorKeyPairsDispatcher<TContextData, CryptoKeyPairLike>,
) => {
callbacks.keyPairsDispatcher = (ctx, identifier) =>
this._getTracer().startActiveSpan(
Expand Down Expand Up @@ -1535,7 +1536,7 @@ export function createFederationBuilder<TContextData>(): FederationBuilder<

interface ActorCallbacks<TContextData> {
dispatcher?: ActorDispatcher<TContextData>;
keyPairsDispatcher?: ActorKeyPairsDispatcher<TContextData>;
keyPairsDispatcher?: ActorKeyPairsDispatcher<TContextData, CryptoKeyPairLike>;
handleMapper?: ActorHandleMapper<TContextData>;
aliasMapper?: ActorAliasMapper<TContextData>;
authorizePredicate?: AuthorizePredicate<TContextData>;
Expand Down
14 changes: 12 additions & 2 deletions packages/fedify/src/federation/callback.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type { Activity, Actor, Object } from "@fedify/vocab";
import type { CryptoKeyPairLike } from "@fedify/vocab-runtime";
import type { Link } from "@fedify/webfinger";
import type { NodeInfo } from "../nodeinfo/types.ts";
import type { PageItems } from "./collection.ts";
Expand Down Expand Up @@ -41,16 +42,25 @@ export type ActorDispatcher<TContextData> = (
/**
* A callback that dispatches key pairs for an actor.
*
* The returned key pairs may come from any supported runtime, including keys
* obtained from `node:crypto`'s Web Crypto API, so the key pair type is
* parameterized. It defaults to the ambient `CryptoKeyPair` type for
* compatibility with code that calls a dispatcher directly.
*
* @template TContextData The context data to pass to the {@link Context}.
* @template TKeyPair The type of the key pairs the dispatcher returns.
* @param context The context.
* @param identifier The actor's internal identifier or username.
* @returns The key pairs.
* @since 0.10.0
*/
export type ActorKeyPairsDispatcher<TContextData> = (
export type ActorKeyPairsDispatcher<
TContextData,
TKeyPair extends CryptoKeyPairLike = CryptoKeyPair,
> = (
context: Context<TContextData>,
identifier: string,
) => CryptoKeyPair[] | Promise<CryptoKeyPair[]>;
) => TKeyPair[] | Promise<TKeyPair[]>;

/**
* A callback that maps a WebFinger username to the corresponding actor's
Expand Down
4 changes: 2 additions & 2 deletions packages/fedify/src/federation/context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import type {
Recipient,
TraverseCollectionOptions,
} from "@fedify/vocab";
import type { DocumentLoader } from "@fedify/vocab-runtime";
import type { CryptoKeyLike, DocumentLoader } from "@fedify/vocab-runtime";
import type {
LookupWebFingerOptions,
ResourceDescriptor,
Expand Down Expand Up @@ -239,7 +239,7 @@ export interface Context<TContextData> {
* @since 0.4.0
*/
getDocumentLoader(
identity: { keyId: URL; privateKey: CryptoKey },
identity: { keyId: URL; privateKey: CryptoKeyLike },
): DocumentLoader;

/**
Expand Down
3 changes: 2 additions & 1 deletion packages/fedify/src/federation/federation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import type {
} from "@fedify/vocab";
import type {
AuthenticatedDocumentLoaderFactory,
CryptoKeyPairLike,
DocumentLoaderFactory,
GetUserAgentOptions,
} from "@fedify/vocab-runtime";
Expand Down Expand Up @@ -1001,7 +1002,7 @@ export interface ActorCallbackSetters<TContextData> {
* @since 0.10.0
*/
setKeyPairsDispatcher(
dispatcher: ActorKeyPairsDispatcher<TContextData>,
dispatcher: ActorKeyPairsDispatcher<TContextData, CryptoKeyPairLike>,
): ActorCallbackSetters<TContextData>;

/**
Expand Down
12 changes: 8 additions & 4 deletions packages/fedify/src/federation/middleware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ import {
Multikey,
traverseCollection,
} from "@fedify/vocab";
import type { CryptoKeyLike, CryptoKeyPairLike } from "@fedify/vocab-runtime";
import { asCryptoKey } from "@fedify/vocab-runtime/internal";
import type {
AuthenticatedDocumentLoaderFactory,
DocumentLoader,
Expand Down Expand Up @@ -1385,7 +1387,7 @@ export class FederationImpl<TContextData>
);
const activityId = activity.id.href;
let proofCreated = false;
let rsaKey: { keyId: URL; privateKey: CryptoKey } | null = null;
let rsaKey: { keyId: URL; privateKey: CryptoKeyLike } | null = null;
for (const { keyId, privateKey } of keys) {
validateCryptoKey(privateKey, "private");
if (rsaKey == null && privateKey.algorithm.name === "RSASSA-PKCS1-v1_5") {
Expand Down Expand Up @@ -2292,7 +2294,7 @@ export class ContextImpl<TContextData> implements Context<TContextData> {
},
);
}
let keyPairs: (CryptoKeyPair & { keyId: URL })[];
let keyPairs: (CryptoKeyPairLike & { keyId: URL })[];
try {
keyPairs = await this.getKeyPairsFromIdentifier(identifier);
} catch (_) {
Expand All @@ -2305,6 +2307,8 @@ export class ContextImpl<TContextData> implements Context<TContextData> {
for (const keyPair of keyPairs) {
const newPair: ActorKeyPair = {
...keyPair,
privateKey: asCryptoKey(keyPair.privateKey),
publicKey: asCryptoKey(keyPair.publicKey),
cryptographicKey: new CryptographicKey({
id: keyPair.keyId,
owner,
Expand All @@ -2324,7 +2328,7 @@ export class ContextImpl<TContextData> implements Context<TContextData> {

protected async getKeyPairsFromIdentifier(
identifier: string,
): Promise<(CryptoKeyPair & { keyId: URL })[]> {
): Promise<(CryptoKeyPairLike & { keyId: URL })[]> {
const logger = getLogger(["fedify", "federation", "actor"]);
if (this.federation.actorCallbacks?.keyPairsDispatcher == null) {
throw new Error("No actor key pairs dispatcher registered.");
Expand Down Expand Up @@ -2367,7 +2371,7 @@ export class ContextImpl<TContextData> implements Context<TContextData> {

protected async getRsaKeyPairFromIdentifier(
identifier: string,
): Promise<CryptoKeyPair & { keyId: URL } | null> {
): Promise<CryptoKeyPairLike & { keyId: URL } | null> {
const keyPairs = await this.getKeyPairsFromIdentifier(identifier);
for (const keyPair of keyPairs) {
const { privateKey } = keyPair;
Expand Down
8 changes: 6 additions & 2 deletions packages/fedify/src/federation/send.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
import type { Recipient } from "@fedify/vocab";
import { FetchError, UrlError } from "@fedify/vocab-runtime";
import {
type CryptoKeyLike,
FetchError,
UrlError,
} from "@fedify/vocab-runtime";
import {
openDirectFetch,
openPublicFetch,
Expand Down Expand Up @@ -90,7 +94,7 @@ export interface SenderKeyPair {
/**
* The actor's private key to sign the request.
*/
readonly privateKey: CryptoKey;
readonly privateKey: CryptoKeyLike;

/**
* The public key ID that corresponds to the private key.
Expand Down
24 changes: 16 additions & 8 deletions packages/fedify/src/sig/http.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
import { CryptographicKey } from "@fedify/vocab";
import { type DocumentLoader, FetchError } from "@fedify/vocab-runtime";
import type { PublicFetchSession } from "@fedify/vocab-runtime/internal";
import {
type CryptoKeyLike,
type DocumentLoader,
FetchError,
} from "@fedify/vocab-runtime";
import {
asCryptoKey,
type PublicFetchSession,
} from "@fedify/vocab-runtime/internal";
import { getLogger } from "@logtape/logtape";
import {
type Span,
Expand Down Expand Up @@ -86,11 +93,12 @@ export interface SignRequestOptions {
*/
export async function signRequest(
request: Request,
privateKey: CryptoKey,
privateKey: CryptoKeyLike,
keyId: URL,
options: SignRequestOptions = {},
): Promise<Request> {
validateCryptoKey(privateKey, "private");
const signingKey = asCryptoKey(privateKey);
const tracerProvider = options.tracerProvider ?? trace.getTracerProvider();
const tracer = tracerProvider.getTracer(
metadata.name,
Expand All @@ -108,7 +116,7 @@ export async function signRequest(
// Pass through test options if provided
signed = await signRequestRfc9421(
request,
privateKey,
signingKey,
keyId,
span,
options.currentTime,
Expand All @@ -118,7 +126,7 @@ export async function signRequest(
// Default to draft-cavage
signed = await signRequestDraft(
request,
privateKey,
signingKey,
keyId,
span,
options.currentTime,
Expand Down Expand Up @@ -1478,7 +1486,7 @@ interface DoubleKnockHop {
*/
export async function doubleKnock(
request: Request,
identity: { keyId: URL; privateKey: CryptoKey },
identity: { keyId: URL; privateKey: CryptoKeyLike },
options: DoubleKnockOptions = {},
): Promise<Response> {
const { response } = await doubleKnockCore(
Expand Down Expand Up @@ -1506,7 +1514,7 @@ export async function doubleKnock(
*/
export async function doubleKnockScoped<T>(
request: Request,
identity: { keyId: URL; privateKey: CryptoKey },
identity: { keyId: URL; privateKey: CryptoKeyLike },
options: DoubleKnockOptions,
openHop: DoubleKnockOpenHop,
consume: (response: Response) => Promise<T>,
Expand Down Expand Up @@ -1570,7 +1578,7 @@ function cancelResponseBody(response: Response, url: string): void {

async function doubleKnockCore(
request: Request,
identity: { keyId: URL; privateKey: CryptoKey },
identity: { keyId: URL; privateKey: CryptoKeyLike },
options: DoubleKnockOptions,
openHop: DoubleKnockOpenHop,
): Promise<DoubleKnockHop> {
Expand Down
Loading
Loading