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

Sign up to get free protection for your applications and to get access to all the features.

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"}