@fedify/fedify 0.8.0-dev.152 → 0.8.0-dev.153

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of @fedify/fedify might be problematic. Click here for more details.

package/CHANGES.md CHANGED
@@ -47,6 +47,7 @@ To be released.
47
47
  logger categories are used:
48
48
 
49
49
  - `["fedify", "federation", "collection"]`
50
+ - `["fedify", "httpsig", "verify"]`
50
51
  - `["fedify", "runtime", "docloader"]`
51
52
 
52
53
  [@fedify/cli]: https://jsr.io/@fedify/cli
@@ -42,7 +42,13 @@ export async function sendActivity({ activity, privateKey, keyId, inbox, documen
42
42
  request = await sign(request, privateKey, keyId);
43
43
  const response = await fetch(request);
44
44
  if (!response.ok) {
45
- const error = await response.text();
45
+ let error;
46
+ try {
47
+ error = await response.text();
48
+ }
49
+ catch (_) {
50
+ error = "";
51
+ }
46
52
  logger.error("Failed to send activity {activityId} to {inbox} ({status} " +
47
53
  "{statusText}):\n{error}", {
48
54
  activityId: activity.id?.href,
@@ -5,6 +5,7 @@
5
5
  * @module
6
6
  */
7
7
  import * as dntShim from "../_dnt.shims.js";
8
+ import { getLogger } from "@logtape/logtape";
8
9
  import { equals } from "../deps/jsr.io/@std/bytes/0.220.1/mod.js";
9
10
  import { decodeBase64, encodeBase64 } from "../deps/jsr.io/@std/encoding/0.220.1/base64.js";
10
11
  import { isActor } from "../vocab/actor.js";
@@ -73,16 +74,22 @@ const supportedHashAlgorithms = {
73
74
  * could not be verified.
74
75
  */
75
76
  export async function verify(request, documentLoader, currentTime) {
77
+ const logger = getLogger(["fedify", "httpsig", "verify"]);
76
78
  request = request.clone();
77
79
  const dateHeader = request.headers.get("Date");
78
- if (dateHeader == null)
80
+ if (dateHeader == null) {
81
+ logger.debug("Failed to verify; no Date header found.", { headers: Object.fromEntries(request.headers.entries()) });
79
82
  return null;
83
+ }
80
84
  const sigHeader = request.headers.get("Signature");
81
- if (sigHeader == null)
85
+ if (sigHeader == null) {
86
+ logger.debug("Failed to verify; no Signature header found.", { headers: Object.fromEntries(request.headers.entries()) });
82
87
  return null;
88
+ }
83
89
  const digestHeader = request.headers.get("Digest");
84
90
  if (request.method !== "GET" && request.method !== "HEAD" &&
85
91
  digestHeader == null) {
92
+ logger.debug("Failed to verify; no Digest header found.", { headers: Object.fromEntries(request.headers.entries()) });
86
93
  return null;
87
94
  }
88
95
  let body = null;
@@ -96,30 +103,60 @@ export async function verify(request, documentLoader, currentTime) {
96
103
  continue;
97
104
  const digest = decodeBase64(digestBase64);
98
105
  const expectedDigest = await dntShim.crypto.subtle.digest(supportedHashAlgorithms[algo], body);
99
- if (!equals(digest, new Uint8Array(expectedDigest)))
106
+ if (!equals(digest, new Uint8Array(expectedDigest))) {
107
+ logger.debug("Failed to verify; digest mismatch ({algorithm}): " +
108
+ "{digest} != {expectedDigest}.", {
109
+ algorithm: algo,
110
+ digest: digestBase64,
111
+ expectedDigest: encodeBase64(expectedDigest),
112
+ });
100
113
  return null;
114
+ }
101
115
  matched = true;
102
116
  }
103
- if (!matched)
117
+ if (!matched) {
118
+ logger.debug("Failed to verify; no supported digest algorithm found. " +
119
+ "Supported: {supportedAlgorithms}; found: {algorithms}.", {
120
+ supportedAlgorithms: Object.keys(supportedHashAlgorithms),
121
+ algorithms: digests.map(([algo]) => algo),
122
+ });
104
123
  return null;
124
+ }
105
125
  }
106
126
  const date = dntShim.Temporal.Instant.from(new Date(dateHeader).toISOString());
107
127
  const now = currentTime ?? dntShim.Temporal.Now.instant();
108
128
  if (dntShim.Temporal.Instant.compare(date, now.add({ seconds: 30 })) > 0) {
109
- // Too far in the future
129
+ logger.debug("Failed to verify; Date is too far in the future.", { date: date.toString(), now: now.toString() });
110
130
  return null;
111
131
  }
112
132
  else if (dntShim.Temporal.Instant.compare(date, now.subtract({ seconds: 30 })) < 0) {
113
- // Too far in the past
133
+ logger.debug("Failed to verify; Date is too far in the past.", { date: date.toString(), now: now.toString() });
114
134
  return null;
115
135
  }
116
136
  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)));
117
- if (!("keyId" in sigValues && "headers" in sigValues &&
118
- "signature" in sigValues)) {
137
+ if (!("keyId" in sigValues)) {
138
+ logger.debug("Failed to verify; no keyId field found in the Signature header.", { signature: sigHeader });
139
+ return null;
140
+ }
141
+ else if (!("headers" in sigValues)) {
142
+ logger.debug("Failed to verify; no headers field found in the Signature header.", { signature: sigHeader });
143
+ return null;
144
+ }
145
+ else if (!("signature" in sigValues)) {
146
+ logger.debug("Failed to verify; no signature field found in the Signature header.", { signature: sigHeader });
119
147
  return null;
120
148
  }
121
149
  const { keyId, headers, signature } = sigValues;
122
- const { document } = await documentLoader(keyId);
150
+ logger.debug("Fetching key {keyId} to verify signature...", { keyId });
151
+ let document;
152
+ try {
153
+ const remoteDocument = await documentLoader(keyId);
154
+ document = remoteDocument.document;
155
+ }
156
+ catch (_) {
157
+ logger.debug("Failed to fetch key {keyId}.", { keyId });
158
+ return null;
159
+ }
123
160
  let object;
124
161
  try {
125
162
  object = await ASObject.fromJsonLd(document, { documentLoader });
@@ -131,8 +168,10 @@ export async function verify(request, documentLoader, currentTime) {
131
168
  object = await CryptographicKey.fromJsonLd(document, { documentLoader });
132
169
  }
133
170
  catch (e) {
134
- if (e instanceof TypeError)
171
+ if (e instanceof TypeError) {
172
+ logger.debug("Failed to verify; key {keyId} returned an invalid object.", { keyId });
135
173
  return null;
174
+ }
136
175
  throw e;
137
176
  }
138
177
  }
@@ -146,19 +185,31 @@ export async function verify(request, documentLoader, currentTime) {
146
185
  break;
147
186
  }
148
187
  }
149
- if (key == null)
188
+ if (key == null) {
189
+ logger.debug("Failed to verify; object {keyId} returned an {actorType}, " +
190
+ "but has no key matching {keyId}.", { keyId, actorType: object.constructor.name });
150
191
  return null;
192
+ }
151
193
  }
152
- else
194
+ else {
195
+ logger.debug("Failed to verify; key {keyId} returned an invalid object.", { keyId });
153
196
  return null;
154
- if (key.publicKey == null)
197
+ }
198
+ if (key.publicKey == null) {
199
+ logger.debug("Failed to verify; key {keyId} has no publicKeyPem field.", { keyId });
155
200
  return null;
201
+ }
156
202
  const headerNames = headers.split(/\s+/g);
157
203
  if (!headerNames.includes("(request-target)") || !headerNames.includes("date")) {
204
+ logger.debug("Failed to verify; required headers missing in the Signature header: " +
205
+ "{headers}.", { headers });
158
206
  return null;
159
207
  }
160
- if (body != null && !headerNames.includes("digest"))
208
+ if (body != null && !headerNames.includes("digest")) {
209
+ logger.debug("Failed to verify; required headers missing in the Signature header: " +
210
+ "{headers}.", { headers });
161
211
  return null;
212
+ }
162
213
  const message = headerNames.map((name) => `${name}: ` +
163
214
  (name == "(request-target)"
164
215
  ? `${request.method.toLowerCase()} ${new URL(request.url).pathname}`
@@ -168,7 +219,13 @@ export async function verify(request, documentLoader, currentTime) {
168
219
  const sig = decodeBase64(signature);
169
220
  // TODO: support other than RSASSA-PKCS1-v1_5:
170
221
  const verified = await dntShim.crypto.subtle.verify("RSASSA-PKCS1-v1_5", key.publicKey, sig, new TextEncoder().encode(message));
171
- return verified ? key : null;
222
+ if (!verified) {
223
+ logger.debug("Failed to verify; signature {signature} is invalid. " +
224
+ "Check if the key is correct or if the signed message is correct. " +
225
+ "The message to sign is:\n{message}", { signature, message });
226
+ return null;
227
+ }
228
+ return key;
172
229
  }
173
230
  /**
174
231
  * Checks if the actor of the given activity owns the specified key.
@@ -48,7 +48,7 @@ async function getRemoteDocument(url, response) {
48
48
  });
49
49
  throw new FetchError(documentUrl, `HTTP ${response.status}: ${documentUrl}`);
50
50
  }
51
- logger.error("Fetched document: {status} {url} {headers}", {
51
+ logger.debug("Fetched document: {status} {url} {headers}", {
52
52
  status: response.status,
53
53
  url: documentUrl,
54
54
  headers: Object.fromEntries(response.headers.entries()),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fedify/fedify",
3
- "version": "0.8.0-dev.152+7dc1eb38",
3
+ "version": "0.8.0-dev.153+42a8bd70",
4
4
  "description": "An ActivityPub server framework",
5
5
  "keywords": [
6
6
  "ActivityPub",
@@ -1 +1 @@
1
- {"version":3,"file":"send.d.ts","sourceRoot":"","sources":["../../src/federation/send.ts"],"names":[],"mappings":";;AAAA,OAAO,KAAK,OAAO,MAAM,kBAAkB,CAAC;AAG5C,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAC;AAC9D,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AACnD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AAEhD;;GAEG;AACH,MAAM,WAAW,wBAAwB;IACvC;;OAEG;IACH,UAAU,EAAE,SAAS,EAAE,CAAC;IAExB;;;OAGG;IACH,iBAAiB,CAAC,EAAE,OAAO,CAAC;CAC7B;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAC5B,EAAE,UAAU,EAAE,iBAAiB,EAAE,EAAE,wBAAwB,GAC1D,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC,CAY7B;AAED;;GAEG;AACH,MAAM,WAAW,sBAAsB;IACrC;;OAEG;IACH,QAAQ,EAAE,QAAQ,CAAC;IAEnB;;OAEG;IACH,UAAU,EAAE,OAAO,CAAC,SAAS,CAAC;IAE9B;;OAEG;IACH,KAAK,EAAE,GAAG,CAAC;IAEX;;OAEG;IACH,KAAK,EAAE,GAAG,CAAC;IAEX;;OAEG;IACH,cAAc,CAAC,EAAE,cAAc,CAAC;IAEhC;;OAEG;IACH,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED;;;;;;GAMG;AACH,wBAAsB,YAAY,CAChC,EACE,QAAQ,EACR,UAAU,EACV,KAAK,EACL,KAAK,EACL,cAAc,EACd,OAAO,GACR,EAAE,sBAAsB,GACxB,OAAO,CAAC,IAAI,CAAC,CAmCf"}
1
+ {"version":3,"file":"send.d.ts","sourceRoot":"","sources":["../../src/federation/send.ts"],"names":[],"mappings":";;AAAA,OAAO,KAAK,OAAO,MAAM,kBAAkB,CAAC;AAG5C,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAC;AAC9D,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AACnD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AAEhD;;GAEG;AACH,MAAM,WAAW,wBAAwB;IACvC;;OAEG;IACH,UAAU,EAAE,SAAS,EAAE,CAAC;IAExB;;;OAGG;IACH,iBAAiB,CAAC,EAAE,OAAO,CAAC;CAC7B;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAC5B,EAAE,UAAU,EAAE,iBAAiB,EAAE,EAAE,wBAAwB,GAC1D,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC,CAY7B;AAED;;GAEG;AACH,MAAM,WAAW,sBAAsB;IACrC;;OAEG;IACH,QAAQ,EAAE,QAAQ,CAAC;IAEnB;;OAEG;IACH,UAAU,EAAE,OAAO,CAAC,SAAS,CAAC;IAE9B;;OAEG;IACH,KAAK,EAAE,GAAG,CAAC;IAEX;;OAEG;IACH,KAAK,EAAE,GAAG,CAAC;IAEX;;OAEG;IACH,cAAc,CAAC,EAAE,cAAc,CAAC;IAEhC;;OAEG;IACH,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED;;;;;;GAMG;AACH,wBAAsB,YAAY,CAChC,EACE,QAAQ,EACR,UAAU,EACV,KAAK,EACL,KAAK,EACL,cAAc,EACd,OAAO,GACR,EAAE,sBAAsB,GACxB,OAAO,CAAC,IAAI,CAAC,CAwCf"}
@@ -1 +1 @@
1
- {"version":3,"file":"mod.d.ts","sourceRoot":"","sources":["../../src/httpsig/mod.ts"],"names":[],"mappings":";;AAAA;;;;;GAKG;AACH,OAAO,KAAK,OAAO,MAAM,kBAAkB,CAAC;AAI5C,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAC;AAC9D,OAAO,EAAE,KAAK,KAAK,EAAW,MAAM,mBAAmB,CAAC;AACxD,OAAO,EACL,KAAK,QAAQ,EACb,gBAAgB,EAEjB,MAAM,mBAAmB,CAAC;AAE3B,OAAO,EAAE,SAAS,EAAE,qBAAqB,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AAEvE;;;;;;;;GAQG;AACH,wBAAsB,IAAI,CACxB,OAAO,EAAE,OAAO,EAChB,UAAU,EAAE,OAAO,CAAC,SAAS,EAC7B,KAAK,EAAE,GAAG,GACT,OAAO,CAAC,OAAO,CAAC,CAuClB;AAQD;;;;;;;;;;;;;GAaG;AACH,wBAAsB,MAAM,CAC1B,OAAO,EAAE,OAAO,EAChB,cAAc,EAAE,cAAc,EAC9B,WAAW,CAAC,EAAE,OAAO,CAAC,QAAQ,CAAC,OAAO,GACrC,OAAO,CAAC,gBAAgB,GAAG,IAAI,CAAC,CAyGlC;AAED;;;;;;GAMG;AACH,wBAAsB,eAAe,CACnC,QAAQ,EAAE,QAAQ,EAClB,GAAG,EAAE,gBAAgB,EACrB,cAAc,EAAE,cAAc,GAC7B,OAAO,CAAC,OAAO,CAAC,CAUlB;AAED;;;;;;;GAOG;AACH,wBAAsB,WAAW,CAC/B,KAAK,EAAE,GAAG,GAAG,gBAAgB,EAC7B,cAAc,EAAE,cAAc,GAC7B,OAAO,CAAC,KAAK,GAAG,IAAI,CAAC,CAwCvB"}
1
+ {"version":3,"file":"mod.d.ts","sourceRoot":"","sources":["../../src/httpsig/mod.ts"],"names":[],"mappings":";;AAAA;;;;;GAKG;AACH,OAAO,KAAK,OAAO,MAAM,kBAAkB,CAAC;AAK5C,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAC;AAC9D,OAAO,EAAE,KAAK,KAAK,EAAW,MAAM,mBAAmB,CAAC;AACxD,OAAO,EACL,KAAK,QAAQ,EACb,gBAAgB,EAEjB,MAAM,mBAAmB,CAAC;AAE3B,OAAO,EAAE,SAAS,EAAE,qBAAqB,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AAEvE;;;;;;;;GAQG;AACH,wBAAsB,IAAI,CACxB,OAAO,EAAE,OAAO,EAChB,UAAU,EAAE,OAAO,CAAC,SAAS,EAC7B,KAAK,EAAE,GAAG,GACT,OAAO,CAAC,OAAO,CAAC,CAuClB;AAQD;;;;;;;;;;;;;GAaG;AACH,wBAAsB,MAAM,CAC1B,OAAO,EAAE,OAAO,EAChB,cAAc,EAAE,cAAc,EAC9B,WAAW,CAAC,EAAE,OAAO,CAAC,QAAQ,CAAC,OAAO,GACrC,OAAO,CAAC,gBAAgB,GAAG,IAAI,CAAC,CAwNlC;AAED;;;;;;GAMG;AACH,wBAAsB,eAAe,CACnC,QAAQ,EAAE,QAAQ,EAClB,GAAG,EAAE,gBAAgB,EACrB,cAAc,EAAE,cAAc,GAC7B,OAAO,CAAC,OAAO,CAAC,CAUlB;AAED;;;;;;;GAOG;AACH,wBAAsB,WAAW,CAC/B,KAAK,EAAE,GAAG,GAAG,gBAAgB,EAC7B,cAAc,EAAE,cAAc,GAC7B,OAAO,CAAC,KAAK,GAAG,IAAI,CAAC,CAwCvB"}