@fedify/fedify 0.10.0-dev.199 → 0.10.0-dev.200

Sign up to get free protection for your applications and to get access to all the features.
package/CHANGES.md CHANGED
@@ -74,7 +74,7 @@ To be released.
74
74
  [[FEP-8b32], [#54]]
75
75
 
76
76
  - Added `proof` property to the `Object` class in the Activity
77
- Vocabulary API. [[FEP-8b32], [#54]]
77
+ Vocabulary API. [[FEP-8b32], [#54]]
78
78
 
79
79
  - Added `Object.getProof()` method.
80
80
  - Added `Object.getProofs()` method.
@@ -83,6 +83,19 @@ To be released.
83
83
  - `Object.clone()` method now accepts `proof` option.
84
84
  - `Object.clone()` method now accepts `proofs` option.
85
85
 
86
+ - Implemented Object Integrity Proofs. [[FEP-8b32], [#54]]
87
+
88
+ - Added `signObject()` function.
89
+ - Added `SignObjectOptions` interface.
90
+ - Added `createProof()` function.
91
+ - Added `CreateProofOptions` interface.
92
+ - Added `verifyObject()` function.
93
+ - Added `VerifyObjectOptions` interface.
94
+ - Added `verifyProof()` function.
95
+ - Added `VerifyProofOptions` interface.
96
+ - Added `fetchKey()` function.
97
+ - Added `FetchKeyOptions` interface.
98
+
86
99
  - Added `context` option to `Object.toJsonLd()` method. This applies to
87
100
  any subclasses of the `Object` class too.
88
101
 
@@ -97,6 +110,12 @@ To be released.
97
110
  `following`, `followers`, `outbox`, `manuallyApprovesFollowers`, and
98
111
  `url`.
99
112
 
113
+ - Added more log messages using the [LogTape] library. Currently the below
114
+ logger categories are used:
115
+
116
+ - `["fedify", "sig", "proof"]`
117
+ - `["fedify", "sig", "key"]`
118
+
100
119
  [#54]: https://github.com/dahlia/fedify/issues/54
101
120
  [#55]: https://github.com/dahlia/fedify/issues/55
102
121
  [FEP-521a]: https://codeberg.org/fediverse/fep/src/branch/main/fep/521a/fep-521a.md
@@ -28,6 +28,11 @@ $defs:
28
28
  Whether to generate singular property accessors. Regardless of
29
29
  this flag, plural property accessors are generated
30
30
  (unless functional is turned on).
31
+ type: boolean
32
+ container:
33
+ description: >-
34
+ The container type of the property values. It can be unspecified.
35
+ const: "graph"
31
36
  required:
32
37
  - pluralName
33
38
  - allOf:
@@ -167,7 +172,7 @@ properties:
167
172
  Marks the type an entity type rather than a value type. Turning on this
168
173
  flag will make property accessors for the type asynchronous, so that they
169
174
  can load the values of the properties from the remote server.
170
-
175
+
171
176
  The extended subtypes must have the consistent value of this flag.
172
177
  type: boolean
173
178
  description:
@@ -78,7 +78,7 @@ export async function importMultibaseKey(key) {
78
78
  return await dntShim.crypto.subtle.importKey("raw", content, "Ed25519", true, ["verify"]);
79
79
  }
80
80
  else {
81
- throw new TypeError("Unsupported key type: " + code);
81
+ throw new TypeError("Unsupported key type: 0x" + code.toString(16));
82
82
  }
83
83
  }
84
84
  /**
package/esm/sig/http.js CHANGED
@@ -2,10 +2,8 @@ import * as dntShim from "../_dnt.shims.js";
2
2
  import { getLogger } from "@logtape/logtape";
3
3
  import { equals } from "../deps/jsr.io/@std/bytes/0.224.0/mod.js";
4
4
  import { decodeBase64, encodeBase64 } from "../deps/jsr.io/@std/encoding/0.224.3/base64.js";
5
- import { fetchDocumentLoader, } from "../runtime/docloader.js";
6
- import { isActor } from "../vocab/actor.js";
7
- import { CryptographicKey, Object as ASObject } from "../vocab/vocab.js";
8
- import { validateCryptoKey } from "./key.js";
5
+ import { CryptographicKey } from "../vocab/vocab.js";
6
+ import { fetchKey, validateCryptoKey } from "./key.js";
9
7
  /**
10
8
  * Signs a request using the given private key.
11
9
  * @param request The request to sign.
@@ -17,6 +15,9 @@ import { validateCryptoKey } from "./key.js";
17
15
  */
18
16
  export async function signRequest(request, privateKey, keyId) {
19
17
  validateCryptoKey(privateKey, "private");
18
+ if (privateKey.algorithm.name !== "RSASSA-PKCS1-v1_5") {
19
+ throw new TypeError("Unsupported algorithm: " + privateKey.algorithm.name);
20
+ }
20
21
  const url = new URL(request.url);
21
22
  const body = request.method !== "GET" && request.method !== "HEAD"
22
23
  ? await request.arrayBuffer()
@@ -141,64 +142,12 @@ export async function verifyRequest(request, { documentLoader, contextLoader, ti
141
142
  return null;
142
143
  }
143
144
  const { keyId, headers, signature } = sigValues;
144
- logger.debug("Fetching key {keyId} to verify signature...", { keyId });
145
- let document;
146
- try {
147
- const remoteDocument = await (documentLoader ?? fetchDocumentLoader)(keyId);
148
- document = remoteDocument.document;
149
- }
150
- catch (_) {
151
- logger.debug("Failed to fetch key {keyId}.", { keyId });
152
- return null;
153
- }
154
- let object;
155
- try {
156
- object = await ASObject.fromJsonLd(document, {
157
- documentLoader,
158
- contextLoader,
159
- });
160
- }
161
- catch (e) {
162
- if (!(e instanceof TypeError))
163
- throw e;
164
- try {
165
- object = await CryptographicKey.fromJsonLd(document, {
166
- documentLoader,
167
- contextLoader,
168
- });
169
- }
170
- catch (e) {
171
- if (e instanceof TypeError) {
172
- logger.debug("Failed to verify; key {keyId} returned an invalid object.", { keyId });
173
- return null;
174
- }
175
- throw e;
176
- }
177
- }
178
- let key = null;
179
- if (object instanceof CryptographicKey)
180
- key = object;
181
- else if (isActor(object)) {
182
- for await (const k of object.getPublicKeys({ documentLoader, contextLoader })) {
183
- if (k.id?.href === keyId) {
184
- key = k;
185
- break;
186
- }
187
- }
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 });
191
- return null;
192
- }
193
- }
194
- else {
195
- logger.debug("Failed to verify; key {keyId} returned an invalid object.", { keyId });
196
- return null;
197
- }
198
- if (key.publicKey == null) {
199
- logger.debug("Failed to verify; key {keyId} has no publicKeyPem field.", { keyId });
145
+ const key = await fetchKey(new URL(keyId), CryptographicKey, {
146
+ documentLoader,
147
+ contextLoader,
148
+ });
149
+ if (key == null)
200
150
  return null;
201
- }
202
151
  const headerNames = headers.split(/\s+/g);
203
152
  if (!headerNames.includes("(request-target)") || !headerNames.includes("date")) {
204
153
  logger.debug("Failed to verify; required headers missing in the Signature header: " +
package/esm/sig/key.js CHANGED
@@ -1,5 +1,8 @@
1
1
  import * as dntShim from "../_dnt.shims.js";
2
2
  import { getLogger } from "@logtape/logtape";
3
+ import { fetchDocumentLoader, } from "../runtime/docloader.js";
4
+ import { isActor } from "../vocab/actor.js";
5
+ import { CryptographicKey, Object } from "../vocab/vocab.js";
3
6
  /**
4
7
  * Checks if the given key is valid and supported. No-op if the key is valid,
5
8
  * otherwise throws an error.
@@ -88,3 +91,85 @@ export async function importJwk(jwk, type) {
88
91
  validateCryptoKey(key, type);
89
92
  return key;
90
93
  }
94
+ /**
95
+ * Fetches a {@link CryptographicKey} or {@link Multikey} from the given URL.
96
+ * If the given URL contains an {@link Actor} object, it tries to find
97
+ * the corresponding key in the `publicKey` or `assertionMethod` property.
98
+ * @typeParam T The type of the key to fetch. Either {@link CryptographicKey}
99
+ * or {@link Multikey}.
100
+ * @param keyId The URL of the key.
101
+ * @param cls The class of the key to fetch. Either {@link CryptographicKey}
102
+ * or {@link Multikey}.
103
+ * @param options Options for fetching the key. See {@link FetchKeyOptions}.
104
+ * @returns The fetched key or `null` if the key is not found.
105
+ * @since 0.10.0
106
+ */
107
+ export async function fetchKey(keyId,
108
+ // deno-lint-ignore no-explicit-any
109
+ cls, { documentLoader, contextLoader } = {}) {
110
+ const logger = getLogger(["fedify", "sig", "key"]);
111
+ keyId = typeof keyId === "string" ? keyId : keyId.href;
112
+ logger.debug("Fetching key {keyId} to verify signature...", { keyId });
113
+ let document;
114
+ try {
115
+ const remoteDocument = await (documentLoader ?? fetchDocumentLoader)(keyId);
116
+ document = remoteDocument.document;
117
+ }
118
+ catch (_) {
119
+ logger.debug("Failed to fetch key {keyId}.", { keyId });
120
+ return null;
121
+ }
122
+ let object;
123
+ try {
124
+ object = await Object.fromJsonLd(document, {
125
+ documentLoader,
126
+ contextLoader,
127
+ });
128
+ }
129
+ catch (e) {
130
+ if (!(e instanceof TypeError))
131
+ throw e;
132
+ try {
133
+ object = await cls.fromJsonLd(document, {
134
+ documentLoader,
135
+ contextLoader,
136
+ });
137
+ }
138
+ catch (e) {
139
+ if (e instanceof TypeError) {
140
+ logger.debug("Failed to verify; key {keyId} returned an invalid object.", { keyId });
141
+ return null;
142
+ }
143
+ throw e;
144
+ }
145
+ }
146
+ let key = null;
147
+ if (object instanceof cls)
148
+ key = object;
149
+ else if (isActor(object)) {
150
+ // @ts-ignore: cls is either CryptographicKey or Multikey
151
+ const keys = cls === CryptographicKey
152
+ ? object.getPublicKeys({ documentLoader, contextLoader })
153
+ : object.getAssertionMethods({ documentLoader, contextLoader });
154
+ for await (const k of keys) {
155
+ if (k.id?.href === keyId) {
156
+ key = k;
157
+ break;
158
+ }
159
+ }
160
+ if (key == null) {
161
+ logger.debug("Failed to verify; object {keyId} returned an {actorType}, " +
162
+ "but has no key matching {keyId}.", { keyId, actorType: object.constructor.name });
163
+ return null;
164
+ }
165
+ }
166
+ else {
167
+ logger.debug("Failed to verify; key {keyId} returned an invalid object.", { keyId });
168
+ return null;
169
+ }
170
+ if (key.publicKey == null) {
171
+ logger.debug("Failed to verify; key {keyId} has no publicKeyPem field.", { keyId });
172
+ return null;
173
+ }
174
+ return key;
175
+ }
@@ -1,23 +1,40 @@
1
1
  {
2
2
  "@context": [
3
3
  "https://www.w3.org/ns/activitystreams",
4
- "https://w3id.org/security/v1"
4
+ "https://w3id.org/security/v1",
5
+ "https://w3id.org/security/multikey/v1",
6
+ "https://w3id.org/security/data-integrity/v1",
7
+ "https://www.w3.org/ns/did/v1"
5
8
  ],
6
9
  "id": "https://example.com/person2",
7
10
  "type": "Person",
8
11
  "name": "Jane Doe",
9
12
  "publicKey": [
10
13
  {
11
- "id": "https://example.com/key3",
14
+ "id": "https://example.com/person2#key3",
12
15
  "type": "CryptographicKey",
13
16
  "owner": "https://example.com/person2",
14
17
  "publicKeyPem": "-----BEGIN PUBLIC KEY-----\nMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA4GUqWgdiYlN3Su5Gr4l6\ni+xRS8gDDVKZ718vpGk6eIpvqs33q430nRbHIzbHRXRaAhc/1++rUBcK0V4/kjZl\nCSzVtRgGU6HMkmjcD+uE56a8XbTczfltbEDj7afoEuB2F3UhQEWrSz+QJ29DPXaL\nMIa1Yv61NR2vxGqNbdtoMjDORMBYtg77CYbcFkiJHw65PDa7+f/yjLxuCRPye5L7\nhncN0UZuuFoRJmHNRLSg5omBad9WTvQXmSyXEhEdk9fHwlI022AqAzlWbT79hldc\nDSKGGLLbQIs1c3JZIG8G5i6Uh5Vy0Z7tSNBcxbhqoI9i9je4f/x/OPIVc19f04BE\n1LgWuHsftZzRgW9Sdqz53W83XxVdxlyHeywXOnstSWT11f8dkLyQUcHKTH+E6urb\nH+aiPLiRpYK8W7D9KTQA9kZ5JXaEuveBd5vJX7wakhbzAn8pWJU7GYIHNY38Ycok\nmivkU5pY8S2cKFMwY0b7ade3MComlir5P3ZYSjF+n6gRVsT96P+9mNfCu9gXt/f8\nXCyjKlH89kGwuJ7HhR8CuVdm0l+jYozVt6GsDy0hHYyn79NCCAEzP7ZbhBMR0T5V\nrkl+TIGXoJH9WFiz4VxO+NnglF6dNQjDS5IzYLoFRXIK1f3cmQiEB4FZmL70l9HL\nrgwR+Xys83xia79OqFDRezMCAwEAAQ==\n-----END PUBLIC KEY-----\n"
15
18
  },
16
19
  {
17
- "id": "https://example.com/key4",
20
+ "id": "https://example.com/person2#key4",
18
21
  "type": "CryptographicKey",
19
22
  "owner": "https://example.com/person2",
20
23
  "publicKeyPem": "-----BEGIN PUBLIC KEY-----\nMCowBQYDK2VwAyEALR8epAGDe+cVq5p2Tx49CCfphpk1rNhkNoY9i+XEUfg=\n-----END PUBLIC KEY-----\n"
21
24
  }
25
+ ],
26
+ "assertionMethod": [
27
+ {
28
+ "id": "https://example.com/peson2#key3",
29
+ "type": "Multikey",
30
+ "controller": "https://example.com/person2",
31
+ "publicKeyMultibase": "zgghBUVkqmWS8e1j4aN2yowLAEkJC6wowB9wWmLRACYCok7UzstWcTBp3waKiDUM7wqL9bbBD9W9FvNaXEK2KPCZ9ffhvd5dxChJL9bdPQSrMwa28FEYMGDtcF1uocrYNmZm2dBBMaWrCu8U3s4PpVVhn4hsWDL8GLuE466pkJs9Hy8xmECoaaVgAZLiYDw2gwrjHDiX2i7aDHKfE7aSZWUWmC8nAGNZ7DX5pXoyXK3pxuaCWxNxXwPmaFwgKDyy9uhtBJ8znp9NZXkXHBTQe5uAi8GFwHY5asvqCmYPrAGWxcT6pdbZaJHdWkM7nw6apBHfakKs42oMqdBoJ2WkkresoT1qHrX2GW7gNP9PLtveF4vfEd6cwgHKQCdYgayG3muGfZiPvML75cyfkNrjkctvuQUfMxY9umbd2TG3V3mPnLrvQnqHpuRMZYtCn3nX1qfZaqFhTwT4NFPqVNLqvgR6k9vcuGXn6Ndaumhd5xtTK64jk3e2gPBit9iq6MrFUSoxNsbTty4kqcHAodtkK8CMSxUxbFP1kK3nyy8ZfeMgDCts1KboBcT2m5FMpQpYxKtNBfvhTuyeDDC34uhbY8itmTAnDwSr5mKrniwwDUGPZFejda51TYs1N9D9Ejzaw5Mvr8qN6wahHmsDBWTbWwV6YKVMD1MjAhJBUopWJWB5x6mEBAX25MssKfAEhJyDtqYWjq63uQHUJCsPJp"
32
+ },
33
+ {
34
+ "id": "https://example.com/person2#key4",
35
+ "type": "Multikey",
36
+ "controller": "https://example.com/person2",
37
+ "publicKeyMultibase": "z6MkhVPuyvgG1RkMv67azDqDCDERPXVrUg1i3qchXY5EACE3"
38
+ }
22
39
  ]
23
40
  }
@@ -0,0 +1,20 @@
1
+ {
2
+ "@context": [
3
+ "https://www.w3.org/ns/activitystreams",
4
+ "https://w3id.org/security/data-integrity/v1",
5
+ "https://w3id.org/security/multikey/v1",
6
+ "https://www.w3.org/ns/did/v1"
7
+ ],
8
+ "type": "Person",
9
+ "id": "https://server.example/users/alice",
10
+ "inbox": "https://server.example/users/alice/inbox",
11
+ "outbox": "https://server.example/users/alice/outbox",
12
+ "assertionMethod": [
13
+ {
14
+ "id": "https://server.example/users/alice#ed25519-key",
15
+ "type": "Multikey",
16
+ "controller": "https://server.example/users/alice",
17
+ "publicKeyMultibase": "z6MkrJVnaZkeFzdQyMZu1cgjg7k1pZZ6pvBQ7XJPt4swbTQ2"
18
+ }
19
+ ]
20
+ }
@@ -0,0 +1,69 @@
1
+ {
2
+ "@context": [
3
+ "https://www.w3.org/ns/activitystreams",
4
+ "https://www.w3.org/ns/did/v1",
5
+ "https://w3id.org/security/v1",
6
+ "https://w3id.org/security/data-integrity/v1",
7
+ "https://w3id.org/security/multikey/v1",
8
+ {
9
+ "MitraJcsEip191Signature2022": "mitra:MitraJcsEip191Signature2022",
10
+ "PropertyValue": "schema:PropertyValue",
11
+ "VerifiableIdentityStatement": "mitra:VerifiableIdentityStatement",
12
+ "featured": "toot:featured",
13
+ "gateways": "mitra:gateways",
14
+ "manuallyApprovesFollowers": "as:manuallyApprovesFollowers",
15
+ "mitra": "http://jsonld.mitra.social#",
16
+ "proofPurpose": "sec:proofPurpose",
17
+ "proofValue": "sec:proofValue",
18
+ "schema": "http://schema.org/",
19
+ "subscribers": "mitra:subscribers",
20
+ "toot": "http://joinmastodon.org/ns#",
21
+ "value": "schema:value"
22
+ }
23
+ ],
24
+ "assertionMethod": [
25
+ {
26
+ "controller": "https://wizard.casa/users/hongminhee",
27
+ "id": "https://wizard.casa/users/hongminhee#main-key",
28
+ "publicKeyMultibase": "z4MXj1wBzi9jUstyPzDJGKjQPQWMi9NPMz1xNE4MeJjMpUcPYR7LfniZmXoXhfQ6motM7AAgWw9nHbFmhnNvAfaZMdQgBo2j1ptQNRogJPGzesYi7Yf81T2Kqju3CTCEJje5brBDdhKMpq56h6SwFAtcbPZBykN527ocN2JfvmiqVYNTqPVwoKGuVGKXNx6h87jVGq1kxzj7Q9LBFQnMHbCnMeUJ3KtqJQcPdGwbMw7LWjz4jpHpi6wb7KXBdVnSyKMwB7KyNJFJYqzauPi1UgtTKRzEMNUdCjAFsYff7v93Rr7Yzy4siE4cRVCdMNSQp3KphqczgLNejPKLjMMT9UC7XLitDdd2t3xaEzuamHVBNrFVBFEJ4",
29
+ "type": "Multikey"
30
+ },
31
+ {
32
+ "controller": "https://wizard.casa/users/hongminhee",
33
+ "id": "https://wizard.casa/users/hongminhee#ed25519-key",
34
+ "publicKeyMultibase": "z6MkweqJajqa5jRAJTBVxxu47oCdB7HzmYbBKN8VGbFJmKkC",
35
+ "type": "Multikey"
36
+ }
37
+ ],
38
+ "authentication": [
39
+ {
40
+ "controller": "https://wizard.casa/users/hongminhee",
41
+ "id": "https://wizard.casa/users/hongminhee#main-key",
42
+ "publicKeyMultibase": "z4MXj1wBzi9jUstyPzDJGKjQPQWMi9NPMz1xNE4MeJjMpUcPYR7LfniZmXoXhfQ6motM7AAgWw9nHbFmhnNvAfaZMdQgBo2j1ptQNRogJPGzesYi7Yf81T2Kqju3CTCEJje5brBDdhKMpq56h6SwFAtcbPZBykN527ocN2JfvmiqVYNTqPVwoKGuVGKXNx6h87jVGq1kxzj7Q9LBFQnMHbCnMeUJ3KtqJQcPdGwbMw7LWjz4jpHpi6wb7KXBdVnSyKMwB7KyNJFJYqzauPi1UgtTKRzEMNUdCjAFsYff7v93Rr7Yzy4siE4cRVCdMNSQp3KphqczgLNejPKLjMMT9UC7XLitDdd2t3xaEzuamHVBNrFVBFEJ4",
43
+ "type": "Multikey"
44
+ },
45
+ {
46
+ "controller": "https://wizard.casa/users/hongminhee",
47
+ "id": "https://wizard.casa/users/hongminhee#ed25519-key",
48
+ "publicKeyMultibase": "z6MkweqJajqa5jRAJTBVxxu47oCdB7HzmYbBKN8VGbFJmKkC",
49
+ "type": "Multikey"
50
+ }
51
+ ],
52
+ "featured": "https://wizard.casa/users/hongminhee/collections/featured",
53
+ "followers": "https://wizard.casa/users/hongminhee/followers",
54
+ "following": "https://wizard.casa/users/hongminhee/following",
55
+ "id": "https://wizard.casa/users/hongminhee",
56
+ "inbox": "https://wizard.casa/users/hongminhee/inbox",
57
+ "manuallyApprovesFollowers": true,
58
+ "name": "洪 民憙 (Hong Minhee)",
59
+ "outbox": "https://wizard.casa/users/hongminhee/outbox",
60
+ "preferredUsername": "hongminhee",
61
+ "publicKey": {
62
+ "id": "https://wizard.casa/users/hongminhee#main-key",
63
+ "owner": "https://wizard.casa/users/hongminhee",
64
+ "publicKeyPem": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAzUNdOf1fXnMDtcw/elvC\nLlw3TbZUAHSQMCD5su7UzYtHsRsSkErPemkJVUvRGvJnHUEVmb700lyNqcz2l3V0\nj35CAOFK77DY95rr1x7/SSJnOLwCDPA0Vff+RBInwck+8jzGigfkea+Z0/BhmfQE\nqhOSnYOsgQ+jWEJMIQBPA1boDW/636GAgbvfvSPkOPnvwMplCvT/ZUrmzcvGT8by\ntRT4QKrW7zjrYv0VaCHVF9aj+LLyOnCWgUKUHm4oKnq49P8UNen7sCsFRq2NRejK\nf9ymm77inWxrS0mNT4bm9J8/+u2mZjvQaiPiNcLUn4a3fvDnBelD4iKogUKaAQ2F\nzwIDAQAB\n-----END PUBLIC KEY-----\n"
65
+ },
66
+ "subscribers": "https://wizard.casa/users/hongminhee/subscribers",
67
+ "type": "Person",
68
+ "url": "https://wizard.casa/users/hongminhee"
69
+ }
@@ -50,7 +50,7 @@ properties:
50
50
 
51
51
  - singularName: created
52
52
  functional: true
53
- uri: "https://www.w3.org/ns/activitystreams#published"
53
+ uri: "http://purl.org/dc/terms/created"
54
54
  description: The date and time the proof was created.
55
55
  range:
56
56
  - "http://www.w3.org/2001/XMLSchema#dateTime"
@@ -306,6 +306,7 @@ properties:
306
306
  pluralName: proofs
307
307
  singularAccessor: true
308
308
  uri: "https://w3id.org/security#proof"
309
+ container: graph
309
310
  description: |
310
311
  A cryptographic proof that can be used to verify the integrity of an object.
311
312
  range: