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