@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 +1 -0
- package/esm/federation/send.js +7 -1
- package/esm/httpsig/mod.js +72 -15
- package/esm/runtime/docloader.js +1 -1
- package/package.json +1 -1
- package/types/federation/send.d.ts.map +1 -1
- package/types/httpsig/mod.d.ts.map +1 -1
package/CHANGES.md
CHANGED
package/esm/federation/send.js
CHANGED
@@ -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
|
-
|
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,
|
package/esm/httpsig/mod.js
CHANGED
@@ -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
|
-
|
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
|
-
|
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
|
118
|
-
"
|
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
|
-
|
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
|
-
|
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
|
-
|
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.
|
package/esm/runtime/docloader.js
CHANGED
@@ -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.
|
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 +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,
|
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;
|
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"}
|