was-teaching-server 0.9.1
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.
- package/CHANGELOG.md +1432 -0
- package/LICENSE +661 -0
- package/README.md +305 -0
- package/common/css/materialize.1.0.0.min.css +13 -0
- package/dist/auth-header-hooks.d.ts +84 -0
- package/dist/auth-header-hooks.d.ts.map +1 -0
- package/dist/auth-header-hooks.js +136 -0
- package/dist/auth-header-hooks.js.map +1 -0
- package/dist/authorize.d.ts +45 -0
- package/dist/authorize.d.ts.map +1 -0
- package/dist/authorize.js +76 -0
- package/dist/authorize.js.map +1 -0
- package/dist/backends/filesystem.d.ts +1179 -0
- package/dist/backends/filesystem.d.ts.map +1 -0
- package/dist/backends/filesystem.js +2727 -0
- package/dist/backends/filesystem.js.map +1 -0
- package/dist/backends/postgres.d.ts +833 -0
- package/dist/backends/postgres.d.ts.map +1 -0
- package/dist/backends/postgres.js +2164 -0
- package/dist/backends/postgres.js.map +1 -0
- package/dist/backends/postgresSchema.d.ts +36 -0
- package/dist/backends/postgresSchema.d.ts.map +1 -0
- package/dist/backends/postgresSchema.js +203 -0
- package/dist/backends/postgresSchema.js.map +1 -0
- package/dist/config.default.d.ts +360 -0
- package/dist/config.default.d.ts.map +1 -0
- package/dist/config.default.js +493 -0
- package/dist/config.default.js.map +1 -0
- package/dist/corsProxy.d.ts +45 -0
- package/dist/corsProxy.d.ts.map +1 -0
- package/dist/corsProxy.js +278 -0
- package/dist/corsProxy.js.map +1 -0
- package/dist/digest.d.ts +45 -0
- package/dist/digest.d.ts.map +1 -0
- package/dist/digest.js +174 -0
- package/dist/digest.js.map +1 -0
- package/dist/errors.d.ts +654 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +960 -0
- package/dist/errors.js.map +1 -0
- package/dist/index.d.ts +22 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +21 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/atomicFile.d.ts +61 -0
- package/dist/lib/atomicFile.d.ts.map +1 -0
- package/dist/lib/atomicFile.js +150 -0
- package/dist/lib/atomicFile.js.map +1 -0
- package/dist/lib/backendRegistry.d.ts +59 -0
- package/dist/lib/backendRegistry.d.ts.map +1 -0
- package/dist/lib/backendRegistry.js +92 -0
- package/dist/lib/backendRegistry.js.map +1 -0
- package/dist/lib/backendUsage.d.ts +28 -0
- package/dist/lib/backendUsage.d.ts.map +1 -0
- package/dist/lib/backendUsage.js +55 -0
- package/dist/lib/backendUsage.js.map +1 -0
- package/dist/lib/backends.d.ts +142 -0
- package/dist/lib/backends.d.ts.map +1 -0
- package/dist/lib/backends.js +272 -0
- package/dist/lib/backends.js.map +1 -0
- package/dist/lib/blindedIndex.d.ts +156 -0
- package/dist/lib/blindedIndex.d.ts.map +1 -0
- package/dist/lib/blindedIndex.js +338 -0
- package/dist/lib/blindedIndex.js.map +1 -0
- package/dist/lib/collectionDescription.d.ts +33 -0
- package/dist/lib/collectionDescription.d.ts.map +1 -0
- package/dist/lib/collectionDescription.js +21 -0
- package/dist/lib/collectionDescription.js.map +1 -0
- package/dist/lib/cursor.d.ts +18 -0
- package/dist/lib/cursor.d.ts.map +1 -0
- package/dist/lib/cursor.js +50 -0
- package/dist/lib/cursor.js.map +1 -0
- package/dist/lib/edvEnvelope.d.ts +50 -0
- package/dist/lib/edvEnvelope.d.ts.map +1 -0
- package/dist/lib/edvEnvelope.js +106 -0
- package/dist/lib/edvEnvelope.js.map +1 -0
- package/dist/lib/encryption.d.ts +192 -0
- package/dist/lib/encryption.d.ts.map +1 -0
- package/dist/lib/encryption.js +383 -0
- package/dist/lib/encryption.js.map +1 -0
- package/dist/lib/etag.d.ts +33 -0
- package/dist/lib/etag.d.ts.map +1 -0
- package/dist/lib/etag.js +36 -0
- package/dist/lib/etag.js.map +1 -0
- package/dist/lib/exportManifest.d.ts +29 -0
- package/dist/lib/exportManifest.d.ts.map +1 -0
- package/dist/lib/exportManifest.js +77 -0
- package/dist/lib/exportManifest.js.map +1 -0
- package/dist/lib/importTar.d.ts +81 -0
- package/dist/lib/importTar.d.ts.map +1 -0
- package/dist/lib/importTar.js +301 -0
- package/dist/lib/importTar.js.map +1 -0
- package/dist/lib/isJson.d.ts +16 -0
- package/dist/lib/isJson.d.ts.map +1 -0
- package/dist/lib/isJson.js +17 -0
- package/dist/lib/isJson.js.map +1 -0
- package/dist/lib/keyEpoch.d.ts +38 -0
- package/dist/lib/keyEpoch.d.ts.map +1 -0
- package/dist/lib/keyEpoch.js +75 -0
- package/dist/lib/keyEpoch.js.map +1 -0
- package/dist/lib/keyedMutex.d.ts +28 -0
- package/dist/lib/keyedMutex.d.ts.map +1 -0
- package/dist/lib/keyedMutex.js +45 -0
- package/dist/lib/keyedMutex.js.map +1 -0
- package/dist/lib/kmsModule.d.ts +62 -0
- package/dist/lib/kmsModule.d.ts.map +1 -0
- package/dist/lib/kmsModule.js +327 -0
- package/dist/lib/kmsModule.js.map +1 -0
- package/dist/lib/kmsRecordCipher.d.ts +82 -0
- package/dist/lib/kmsRecordCipher.d.ts.map +1 -0
- package/dist/lib/kmsRecordCipher.js +226 -0
- package/dist/lib/kmsRecordCipher.js.map +1 -0
- package/dist/lib/pagination.d.ts +19 -0
- package/dist/lib/pagination.d.ts.map +1 -0
- package/dist/lib/pagination.js +21 -0
- package/dist/lib/pagination.js.map +1 -0
- package/dist/lib/paths.d.ts +273 -0
- package/dist/lib/paths.d.ts.map +1 -0
- package/dist/lib/paths.js +270 -0
- package/dist/lib/paths.js.map +1 -0
- package/dist/lib/preconditions.d.ts +65 -0
- package/dist/lib/preconditions.d.ts.map +1 -0
- package/dist/lib/preconditions.js +112 -0
- package/dist/lib/preconditions.js.map +1 -0
- package/dist/lib/resourceFileName.d.ts +41 -0
- package/dist/lib/resourceFileName.d.ts.map +1 -0
- package/dist/lib/resourceFileName.js +58 -0
- package/dist/lib/resourceFileName.js.map +1 -0
- package/dist/lib/revocations.d.ts +49 -0
- package/dist/lib/revocations.d.ts.map +1 -0
- package/dist/lib/revocations.js +86 -0
- package/dist/lib/revocations.js.map +1 -0
- package/dist/lib/validateDid.d.ts +19 -0
- package/dist/lib/validateDid.d.ts.map +1 -0
- package/dist/lib/validateDid.js +35 -0
- package/dist/lib/validateDid.js.map +1 -0
- package/dist/lib/validateId.d.ts +66 -0
- package/dist/lib/validateId.d.ts.map +1 -0
- package/dist/lib/validateId.js +126 -0
- package/dist/lib/validateId.js.map +1 -0
- package/dist/plugin.d.ts +115 -0
- package/dist/plugin.d.ts.map +1 -0
- package/dist/plugin.js +134 -0
- package/dist/plugin.js.map +1 -0
- package/dist/policy.d.ts +78 -0
- package/dist/policy.d.ts.map +1 -0
- package/dist/policy.js +115 -0
- package/dist/policy.js.map +1 -0
- package/dist/provisioning.d.ts +38 -0
- package/dist/provisioning.d.ts.map +1 -0
- package/dist/provisioning.js +72 -0
- package/dist/provisioning.js.map +1 -0
- package/dist/requests/BackendRequest.d.ts +61 -0
- package/dist/requests/BackendRequest.d.ts.map +1 -0
- package/dist/requests/BackendRequest.js +152 -0
- package/dist/requests/BackendRequest.js.map +1 -0
- package/dist/requests/CollectionRequest.d.ts +234 -0
- package/dist/requests/CollectionRequest.d.ts.map +1 -0
- package/dist/requests/CollectionRequest.js +705 -0
- package/dist/requests/CollectionRequest.js.map +1 -0
- package/dist/requests/KeyRequest.d.ts +115 -0
- package/dist/requests/KeyRequest.d.ts.map +1 -0
- package/dist/requests/KeyRequest.js +468 -0
- package/dist/requests/KeyRequest.js.map +1 -0
- package/dist/requests/KeystoreRequest.d.ts +99 -0
- package/dist/requests/KeystoreRequest.d.ts.map +1 -0
- package/dist/requests/KeystoreRequest.js +247 -0
- package/dist/requests/KeystoreRequest.js.map +1 -0
- package/dist/requests/PolicyRequest.d.ts +53 -0
- package/dist/requests/PolicyRequest.d.ts.map +1 -0
- package/dist/requests/PolicyRequest.js +124 -0
- package/dist/requests/PolicyRequest.js.map +1 -0
- package/dist/requests/ResourceRequest.d.ts +134 -0
- package/dist/requests/ResourceRequest.d.ts.map +1 -0
- package/dist/requests/ResourceRequest.js +548 -0
- package/dist/requests/ResourceRequest.js.map +1 -0
- package/dist/requests/RevocationRequest.d.ts +67 -0
- package/dist/requests/RevocationRequest.d.ts.map +1 -0
- package/dist/requests/RevocationRequest.js +187 -0
- package/dist/requests/RevocationRequest.js.map +1 -0
- package/dist/requests/SpaceRequest.d.ts +204 -0
- package/dist/requests/SpaceRequest.d.ts.map +1 -0
- package/dist/requests/SpaceRequest.js +520 -0
- package/dist/requests/SpaceRequest.js.map +1 -0
- package/dist/requests/SpacesRepositoryRequest.d.ts +50 -0
- package/dist/requests/SpacesRepositoryRequest.d.ts.map +1 -0
- package/dist/requests/SpacesRepositoryRequest.js +159 -0
- package/dist/requests/SpacesRepositoryRequest.js.map +1 -0
- package/dist/requests/collectionContext.d.ts +21 -0
- package/dist/requests/collectionContext.d.ts.map +1 -0
- package/dist/requests/collectionContext.js +30 -0
- package/dist/requests/collectionContext.js.map +1 -0
- package/dist/requests/controllerConsent.d.ts +58 -0
- package/dist/requests/controllerConsent.d.ts.map +1 -0
- package/dist/requests/controllerConsent.js +73 -0
- package/dist/requests/controllerConsent.js.map +1 -0
- package/dist/requests/keystoreContext.d.ts +68 -0
- package/dist/requests/keystoreContext.d.ts.map +1 -0
- package/dist/requests/keystoreContext.js +87 -0
- package/dist/requests/keystoreContext.js.map +1 -0
- package/dist/requests/resourceInput.d.ts +19 -0
- package/dist/requests/resourceInput.d.ts.map +1 -0
- package/dist/requests/resourceInput.js +102 -0
- package/dist/requests/resourceInput.js.map +1 -0
- package/dist/requests/spaceContext.d.ts +127 -0
- package/dist/requests/spaceContext.d.ts.map +1 -0
- package/dist/requests/spaceContext.js +201 -0
- package/dist/requests/spaceContext.js.map +1 -0
- package/dist/routes.d.ts +55 -0
- package/dist/routes.d.ts.map +1 -0
- package/dist/routes.js +306 -0
- package/dist/routes.js.map +1 -0
- package/dist/server.d.ts +20 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +60 -0
- package/dist/server.js.map +1 -0
- package/dist/start.d.ts +7 -0
- package/dist/start.d.ts.map +1 -0
- package/dist/start.js +61 -0
- package/dist/start.js.map +1 -0
- package/dist/storage.d.ts +29 -0
- package/dist/storage.d.ts.map +1 -0
- package/dist/storage.js +47 -0
- package/dist/storage.js.map +1 -0
- package/dist/types.d.ts +915 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist/views/home.hbs +9 -0
- package/dist/views/templates/main.hbs +12 -0
- package/dist/zcap.d.ts +267 -0
- package/dist/zcap.d.ts.map +1 -0
- package/dist/zcap.js +478 -0
- package/dist/zcap.js.map +1 -0
- package/package.json +109 -0
- package/src/auth-header-hooks.ts +163 -0
- package/src/authorize.ts +117 -0
- package/src/backends/filesystem.ts +3626 -0
- package/src/backends/postgres.ts +3073 -0
- package/src/backends/postgresSchema.ts +231 -0
- package/src/config.default.ts +657 -0
- package/src/corsProxy.ts +326 -0
- package/src/declarations.d.ts +28 -0
- package/src/digest.ts +223 -0
- package/src/errors.ts +1122 -0
- package/src/index.ts +21 -0
- package/src/lib/atomicFile.ts +176 -0
- package/src/lib/backendRegistry.ts +139 -0
- package/src/lib/backendUsage.ts +79 -0
- package/src/lib/backends.ts +351 -0
- package/src/lib/blindedIndex.ts +466 -0
- package/src/lib/collectionDescription.ts +43 -0
- package/src/lib/cursor.ts +52 -0
- package/src/lib/edvEnvelope.ts +120 -0
- package/src/lib/encryption.ts +472 -0
- package/src/lib/etag.ts +40 -0
- package/src/lib/exportManifest.ts +115 -0
- package/src/lib/importTar.ts +381 -0
- package/src/lib/isJson.ts +18 -0
- package/src/lib/keyEpoch.ts +89 -0
- package/src/lib/keyedMutex.ts +45 -0
- package/src/lib/kmsModule.ts +451 -0
- package/src/lib/kmsRecordCipher.ts +292 -0
- package/src/lib/pagination.ts +22 -0
- package/src/lib/paths.ts +382 -0
- package/src/lib/preconditions.ts +145 -0
- package/src/lib/resourceFileName.ts +69 -0
- package/src/lib/revocations.ts +116 -0
- package/src/lib/validateDid.ts +41 -0
- package/src/lib/validateId.ts +148 -0
- package/src/plugin.ts +288 -0
- package/src/policy.ts +176 -0
- package/src/provisioning.ts +98 -0
- package/src/requests/BackendRequest.ts +219 -0
- package/src/requests/CollectionRequest.ts +899 -0
- package/src/requests/KeyRequest.ts +617 -0
- package/src/requests/KeystoreRequest.ts +325 -0
- package/src/requests/PolicyRequest.ts +169 -0
- package/src/requests/ResourceRequest.ts +649 -0
- package/src/requests/RevocationRequest.ts +267 -0
- package/src/requests/SpaceRequest.ts +686 -0
- package/src/requests/SpacesRepositoryRequest.ts +194 -0
- package/src/requests/collectionContext.ts +41 -0
- package/src/requests/controllerConsent.ts +108 -0
- package/src/requests/keystoreContext.ts +124 -0
- package/src/requests/resourceInput.ts +118 -0
- package/src/requests/spaceContext.ts +311 -0
- package/src/routes.ts +420 -0
- package/src/server.ts +69 -0
- package/src/start.ts +71 -0
- package/src/storage.ts +60 -0
- package/src/types.ts +1016 -0
- package/src/views/home.hbs +9 -0
- package/src/views/templates/main.hbs +12 -0
- package/src/zcap.ts +657 -0
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
6
|
+
<title>WAS Teaching Server: {{title}}</title>
|
|
7
|
+
<link rel="stylesheet" href="/common/css/materialize.1.0.0.min.css" />
|
|
8
|
+
</head>
|
|
9
|
+
<body>
|
|
10
|
+
{{{body}}}
|
|
11
|
+
</body>
|
|
12
|
+
</html>
|
package/src/zcap.ts
ADDED
|
@@ -0,0 +1,657 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ZCap verification: handleZcapVerify() checks the capability-invocation
|
|
3
|
+
* signature against the Space controller's Ed25519 key, synthesizing the root
|
|
4
|
+
* capability via the document loader. Also home to the zcap *revocation*
|
|
5
|
+
* verification pair, shared by the `/kms` and WAS route families:
|
|
6
|
+
* verifyRevocationChain() validates a to-be-revoked capability's delegation
|
|
7
|
+
* chain, and handleRevocationInvocationVerify() authorizes the submission
|
|
8
|
+
* under the dual-root rule (the scope's root -- a keystore or a Space -- or
|
|
9
|
+
* the revocation URL's own root controlled by any chain participant --
|
|
10
|
+
* ezcap-express's `authorizeZcapRevocation` convention).
|
|
11
|
+
*/
|
|
12
|
+
import type { IncomingHttpHeaders } from 'node:http'
|
|
13
|
+
import { securityLoader } from '@interop/security-document-loader'
|
|
14
|
+
import {
|
|
15
|
+
verifyCapabilityInvocation,
|
|
16
|
+
type VerifyCapabilityInvocationResult
|
|
17
|
+
} from '@interop/http-signature-zcap-verify'
|
|
18
|
+
import jsigs from '@interop/jsonld-signatures'
|
|
19
|
+
import {
|
|
20
|
+
CapabilityDelegation,
|
|
21
|
+
type InspectCapabilityChain
|
|
22
|
+
} from '@interop/zcap'
|
|
23
|
+
import { Ed25519VerificationKey } from '@interop/ed25519-verification-key'
|
|
24
|
+
import { Ed25519Signature2020 } from '@interop/ed25519-signature'
|
|
25
|
+
import * as didKey from '@interop/did-method-key'
|
|
26
|
+
import type { IDocumentLoader, IPublicKey } from '@interop/data-integrity-core'
|
|
27
|
+
import {
|
|
28
|
+
AuthVerificationError,
|
|
29
|
+
InvalidRevocationError,
|
|
30
|
+
UnauthorizedError
|
|
31
|
+
} from './errors.js'
|
|
32
|
+
import {
|
|
33
|
+
capabilitySummaries,
|
|
34
|
+
revocationChainInspector
|
|
35
|
+
} from './lib/revocations.js'
|
|
36
|
+
import type {
|
|
37
|
+
CapabilitySummary,
|
|
38
|
+
IDID,
|
|
39
|
+
IVerificationMethod,
|
|
40
|
+
RevocationScope,
|
|
41
|
+
StorageBackend
|
|
42
|
+
} from './types.js'
|
|
43
|
+
|
|
44
|
+
const didKeyDriver = didKey.driver()
|
|
45
|
+
didKeyDriver.use({
|
|
46
|
+
multibaseMultikeyHeader: 'z6Mk',
|
|
47
|
+
fromMultibase: Ed25519VerificationKey.from
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* The root capability id convention: `urn:zcap:root:` + the url-encoded
|
|
52
|
+
* invocation target (shared by WAS and webkms).
|
|
53
|
+
* @param target {string} the root invocation target (full URL)
|
|
54
|
+
* @returns {string}
|
|
55
|
+
*/
|
|
56
|
+
function rootCapabilityId(target: string): string {
|
|
57
|
+
return `urn:zcap:root:${encodeURIComponent(target)}`
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Builds a document loader whose `urn` protocol handler synthesizes
|
|
62
|
+
* `urn:zcap:root:<target>` capabilities on demand, with the controller chosen
|
|
63
|
+
* per target. The zcap library only dereferences a root capability it already
|
|
64
|
+
* expects (per `expectedRootCapability`), so `controllerFor` sees expected
|
|
65
|
+
* targets only -- it may still throw to refuse one outright.
|
|
66
|
+
*
|
|
67
|
+
* @param options {object}
|
|
68
|
+
* @param options.controllerFor {(target: string) => IDID | string[]} maps a
|
|
69
|
+
* decoded root invocation target to the controller(s) of its synthesized
|
|
70
|
+
* root capability
|
|
71
|
+
* @returns {IDocumentLoader}
|
|
72
|
+
*/
|
|
73
|
+
function rootCapabilityLoader({
|
|
74
|
+
controllerFor
|
|
75
|
+
}: {
|
|
76
|
+
controllerFor: (target: string) => IDID | string[]
|
|
77
|
+
}): IDocumentLoader {
|
|
78
|
+
const loader = securityLoader()
|
|
79
|
+
loader.setProtocolHandler({
|
|
80
|
+
protocol: 'urn',
|
|
81
|
+
handler: {
|
|
82
|
+
get: async ({ id, url }: { id: string; url?: string }) => {
|
|
83
|
+
const resolvedUrl = url || id
|
|
84
|
+
const rootZcapTarget = decodeURIComponent(
|
|
85
|
+
resolvedUrl.split('urn:zcap:root:')[1]!
|
|
86
|
+
)
|
|
87
|
+
return {
|
|
88
|
+
'@context': 'https://w3id.org/zcap/v1',
|
|
89
|
+
id: resolvedUrl,
|
|
90
|
+
invocationTarget: rootZcapTarget,
|
|
91
|
+
controller: controllerFor(rootZcapTarget)
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
})
|
|
96
|
+
return loader.build()
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Resolves an invocation's keyId to an Ed25519 verifier (the
|
|
101
|
+
* `verifyCapabilityInvocation` HTTP-signature key hook).
|
|
102
|
+
* @param options {object}
|
|
103
|
+
* @param options.keyId {string} the did:key verification method URL
|
|
104
|
+
* @returns {Promise<{ verifier: object, verificationMethod: IVerificationMethod }>}
|
|
105
|
+
*/
|
|
106
|
+
async function getVerifier({ keyId }: { keyId: string }) {
|
|
107
|
+
const verificationMethod = await didKeyDriver.get({ url: keyId })
|
|
108
|
+
const key = await Ed25519VerificationKey.from(
|
|
109
|
+
verificationMethod as IPublicKey
|
|
110
|
+
)
|
|
111
|
+
const verifier = key.verifier()
|
|
112
|
+
return {
|
|
113
|
+
verifier,
|
|
114
|
+
verificationMethod: verificationMethod as IVerificationMethod
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/** Minimal logger surface used during verification (console / request.log). */
|
|
119
|
+
interface ZcapLogger {
|
|
120
|
+
error: (...args: any[]) => void
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Returns true when a `Capability-Invocation` header value is the bare root
|
|
125
|
+
* form (`zcap id="urn:zcap:root:..."` -- the signer invokes the root capability
|
|
126
|
+
* directly), false when it embeds a delegated capability
|
|
127
|
+
* (`zcap capability="<base64url(gzip(json))>"`). The check is safe on the raw
|
|
128
|
+
* header: a `capability=` substring cannot occur inside the root form's
|
|
129
|
+
* url-encoded `id` (where `=` is percent-encoded).
|
|
130
|
+
*
|
|
131
|
+
* @param options {object}
|
|
132
|
+
* @param options.invocation {string} the raw `Capability-Invocation` header
|
|
133
|
+
* @returns {boolean}
|
|
134
|
+
*/
|
|
135
|
+
export function isRootInvocation({
|
|
136
|
+
invocation
|
|
137
|
+
}: {
|
|
138
|
+
invocation: string
|
|
139
|
+
}): boolean {
|
|
140
|
+
return !invocation.includes('capability=')
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Verifies the capability-invocation signature on a request against the Space
|
|
145
|
+
* controller's key. Throws AuthVerificationError if verification itself errors,
|
|
146
|
+
* or UnauthorizedError if the capability does not verify.
|
|
147
|
+
*
|
|
148
|
+
* @param options {object}
|
|
149
|
+
* @param options.url {string} request URL (path), resolved against serverUrl
|
|
150
|
+
* @param options.allowedTarget {string} the capability's expected
|
|
151
|
+
* invocationTarget (full URL, including host and port)
|
|
152
|
+
* @param options.allowedAction {string} expected action, e.g. an HTTP verb
|
|
153
|
+
* @param options.method {string} the HTTP method of the request
|
|
154
|
+
* @param options.headers {IncomingHttpHeaders} the request headers (including
|
|
155
|
+
* `authorization`, `capability-invocation`, and `digest`)
|
|
156
|
+
* @param options.serverUrl {string} this server's base URL
|
|
157
|
+
* @param options.spaceController {IDID} the did:key that controls the Space
|
|
158
|
+
* @param [options.requestName] {string} human-readable request name, used in
|
|
159
|
+
* error titles
|
|
160
|
+
* @param [options.logger] {ZcapLogger} logger for verification errors;
|
|
161
|
+
* defaults to `console`
|
|
162
|
+
* @param [options.allowTargetQuery] {boolean} tolerate query parameters that
|
|
163
|
+
* extend `allowedTarget` on the request URL (see `verifyZcap`)
|
|
164
|
+
* @param [options.allowTargetAttenuation] {boolean} accept a request URL
|
|
165
|
+
* that path-extends `allowedTarget` under a capability rooted at
|
|
166
|
+
* `allowedTarget` (see `verifyZcap`)
|
|
167
|
+
* @param [options.attenuatedRootTarget] {string} an ancestor target (e.g.
|
|
168
|
+
* the Space URL) whose root capability is also accepted as the root of a
|
|
169
|
+
* delegated chain that attenuates down to the request URL (see `verifyZcap`)
|
|
170
|
+
* @param options.revocation {object|string} the revocation-store check, run
|
|
171
|
+
* against the dereferenced chain after signature verification. REQUIRED so
|
|
172
|
+
* that skipping revocation is a stated decision, never an omission: pass
|
|
173
|
+
* `{ storage, scope }` -- the scope (keystore or Space) the chain roots
|
|
174
|
+
* in -- or the literal `'no-revocation-scope'` when the verified target has
|
|
175
|
+
* no scope a revocation could be stored under (a create/consent
|
|
176
|
+
* verification for a not-yet-existing resource, or a collection-level root
|
|
177
|
+
* like `/kms/keystores`).
|
|
178
|
+
* @param [options.maxChainLength] {number} max delegation chain length,
|
|
179
|
+
* root included (see `verifyZcap`)
|
|
180
|
+
* @param [options.maxDelegationTtl] {number} max delegated-zcap TTL in
|
|
181
|
+
* milliseconds (see `verifyZcap`)
|
|
182
|
+
* @returns {Promise<VerifyCapabilityInvocationResult>} the successful
|
|
183
|
+
* verification result (callers needing the dereferenced chain, e.g. the
|
|
184
|
+
* per-key `maxCapabilityChainLength` gate, read it from here)
|
|
185
|
+
*/
|
|
186
|
+
export async function handleZcapVerify({
|
|
187
|
+
url,
|
|
188
|
+
allowedTarget,
|
|
189
|
+
allowedAction,
|
|
190
|
+
method,
|
|
191
|
+
headers,
|
|
192
|
+
serverUrl,
|
|
193
|
+
spaceController,
|
|
194
|
+
requestName = '',
|
|
195
|
+
logger = console,
|
|
196
|
+
allowTargetQuery = false,
|
|
197
|
+
allowTargetAttenuation = false,
|
|
198
|
+
attenuatedRootTarget,
|
|
199
|
+
revocation,
|
|
200
|
+
maxChainLength,
|
|
201
|
+
maxDelegationTtl
|
|
202
|
+
}: {
|
|
203
|
+
url: string
|
|
204
|
+
allowedTarget: string
|
|
205
|
+
allowedAction: string
|
|
206
|
+
method: string
|
|
207
|
+
headers: IncomingHttpHeaders
|
|
208
|
+
serverUrl: string
|
|
209
|
+
spaceController: IDID
|
|
210
|
+
requestName?: string
|
|
211
|
+
logger?: ZcapLogger
|
|
212
|
+
allowTargetQuery?: boolean
|
|
213
|
+
allowTargetAttenuation?: boolean
|
|
214
|
+
attenuatedRootTarget?: string
|
|
215
|
+
revocation:
|
|
216
|
+
{ storage: StorageBackend; scope: RevocationScope } | 'no-revocation-scope'
|
|
217
|
+
maxChainLength?: number
|
|
218
|
+
maxDelegationTtl?: number
|
|
219
|
+
}): Promise<VerifyCapabilityInvocationResult> {
|
|
220
|
+
const inspectCapabilityChain =
|
|
221
|
+
revocation === 'no-revocation-scope'
|
|
222
|
+
? undefined
|
|
223
|
+
: revocationChainInspector(revocation)
|
|
224
|
+
let zcapVerifyResult: VerifyCapabilityInvocationResult
|
|
225
|
+
try {
|
|
226
|
+
zcapVerifyResult = await verifyZcap({
|
|
227
|
+
url,
|
|
228
|
+
allowedTarget,
|
|
229
|
+
allowedAction,
|
|
230
|
+
method,
|
|
231
|
+
headers,
|
|
232
|
+
serverUrl,
|
|
233
|
+
spaceController,
|
|
234
|
+
allowTargetQuery,
|
|
235
|
+
allowTargetAttenuation,
|
|
236
|
+
attenuatedRootTarget,
|
|
237
|
+
inspectCapabilityChain,
|
|
238
|
+
maxChainLength,
|
|
239
|
+
maxDelegationTtl
|
|
240
|
+
})
|
|
241
|
+
} catch (err) {
|
|
242
|
+
logger.error({ err }, 'ZCAP verification failed')
|
|
243
|
+
throw new AuthVerificationError({ requestName, cause: err as Error })
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
if (!zcapVerifyResult.verified) {
|
|
247
|
+
throw new UnauthorizedError({ requestName })
|
|
248
|
+
}
|
|
249
|
+
return zcapVerifyResult
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* Performs the underlying capability-invocation verification: builds a document
|
|
254
|
+
* loader whose `urn` protocol handler synthesizes the root capability on demand
|
|
255
|
+
* (its controller is the Space controller), then calls
|
|
256
|
+
* verifyCapabilityInvocation().
|
|
257
|
+
*
|
|
258
|
+
* @param options {object}
|
|
259
|
+
* @param options.url {string} request URL (path), resolved against serverUrl
|
|
260
|
+
* @param options.allowedTarget {string} expected invocationTarget (full URL)
|
|
261
|
+
* @param options.allowedAction {string} expected action, e.g. an HTTP verb
|
|
262
|
+
* @param options.method {string} the HTTP method of the request
|
|
263
|
+
* @param options.headers {IncomingHttpHeaders} the request headers
|
|
264
|
+
* @param options.serverUrl {string} this server's base URL
|
|
265
|
+
* @param options.spaceController {IDID} the did:key that controls the Space
|
|
266
|
+
* @param [options.allowTargetQuery] {boolean} when set, accept a request URL
|
|
267
|
+
* that adds query parameters to `allowedTarget` (e.g. List Collection's
|
|
268
|
+
* `?limit`/`cursor`) as authorized by a capability for the bare target. The
|
|
269
|
+
* spec requires that pagination parameters select a page within an
|
|
270
|
+
* already-authorized target without changing the target a capability must
|
|
271
|
+
* match. The zcap library otherwise requires the capability's
|
|
272
|
+
* `invocationTarget` to equal the full request URL exactly, so this enables
|
|
273
|
+
* target attenuation (the library treats a `?`-query suffix as a valid RESTful
|
|
274
|
+
* attenuation) and admits both the bare-target root capability (a delegate
|
|
275
|
+
* following `next`) and the query-bearing one (a controller invoking the URL
|
|
276
|
+
* directly). The actual gate -- the bare-target root capability -- is
|
|
277
|
+
* unchanged. (TODO: the `/quotas` endpoint should adopt this too, so its
|
|
278
|
+
* per-Collection breakdown can return to the spec's `?include=collections`
|
|
279
|
+
* opt-in.)
|
|
280
|
+
* @param [options.allowTargetAttenuation] {boolean} when set, accept a
|
|
281
|
+
* request URL that *path*-extends `allowedTarget` (e.g. a WebKMS key
|
|
282
|
+
* operation posted to `<keystoreId>/keys/<keyId>` under a capability rooted
|
|
283
|
+
* at the keystore). The root capability is `allowedTarget`'s alone -- unlike
|
|
284
|
+
* `allowTargetQuery`, the extended URL is never itself an acceptable root --
|
|
285
|
+
* so both a root invocation by the controller and a delegated zcap whose
|
|
286
|
+
* `invocationTarget` narrows down to the request URL verify against the
|
|
287
|
+
* `allowedTarget` root (the webkms authorization model, which roots the
|
|
288
|
+
* invocation target at the keystore id).
|
|
289
|
+
* @param [options.attenuatedRootTarget] {string} when set, an *ancestor*
|
|
290
|
+
* invocation target (the Space URL for the WAS route families) whose root
|
|
291
|
+
* capability is accepted -- in addition to `allowedTarget`'s own -- as the
|
|
292
|
+
* root of the invocation. This is what lets a controller delegate one
|
|
293
|
+
* capability for a whole Space (or a Collection under it, by attenuating
|
|
294
|
+
* the `invocationTarget` down at delegation time) and have the delegate
|
|
295
|
+
* invoke it against any URL underneath: the chain roots at the ancestor's
|
|
296
|
+
* root capability and narrows toward the request URL (RESTful attenuation,
|
|
297
|
+
* the same shape `allowTargetAttenuation` gives the WebKMS keystore).
|
|
298
|
+
* Root invocations of `allowedTarget`'s own root capability verify
|
|
299
|
+
* unchanged, so this only widens what the Space controller can delegate,
|
|
300
|
+
* never who can access.
|
|
301
|
+
* @param [options.inspectCapabilityChain] {InspectCapabilityChain} hook run
|
|
302
|
+
* against the dereferenced chain after signature verification -- the
|
|
303
|
+
* revocation-check extension point (a stored revocation of any capability
|
|
304
|
+
* in the chain fails the verification). Both route families pass one, scoped
|
|
305
|
+
* to the keystore or the Space the request roots in.
|
|
306
|
+
* @param [options.maxChainLength] {number} max delegation chain length,
|
|
307
|
+
* root included (the `/kms` families pass `KMS_MAX_CHAIN_LENGTH`; absent,
|
|
308
|
+
* the zcap library's own default applies)
|
|
309
|
+
* @param [options.maxDelegationTtl] {number} max delegated-zcap TTL in
|
|
310
|
+
* milliseconds, measured `expires` minus the delegation proof's `created`
|
|
311
|
+
* (the `/kms` families pass `KMS_MAX_DELEGATION_TTL`; absent, unbounded)
|
|
312
|
+
* @returns {Promise<VerifyCapabilityInvocationResult>}
|
|
313
|
+
*/
|
|
314
|
+
export async function verifyZcap({
|
|
315
|
+
url,
|
|
316
|
+
allowedTarget,
|
|
317
|
+
allowedAction,
|
|
318
|
+
method,
|
|
319
|
+
headers,
|
|
320
|
+
serverUrl,
|
|
321
|
+
spaceController,
|
|
322
|
+
allowTargetQuery = false,
|
|
323
|
+
allowTargetAttenuation = false,
|
|
324
|
+
attenuatedRootTarget,
|
|
325
|
+
inspectCapabilityChain,
|
|
326
|
+
maxChainLength,
|
|
327
|
+
maxDelegationTtl
|
|
328
|
+
}: {
|
|
329
|
+
url: string
|
|
330
|
+
allowedTarget: string
|
|
331
|
+
allowedAction: string
|
|
332
|
+
method: string
|
|
333
|
+
headers: IncomingHttpHeaders
|
|
334
|
+
serverUrl: string
|
|
335
|
+
spaceController: IDID
|
|
336
|
+
allowTargetQuery?: boolean
|
|
337
|
+
allowTargetAttenuation?: boolean
|
|
338
|
+
attenuatedRootTarget?: string
|
|
339
|
+
inspectCapabilityChain?: InspectCapabilityChain
|
|
340
|
+
maxChainLength?: number
|
|
341
|
+
maxDelegationTtl?: number
|
|
342
|
+
}): Promise<VerifyCapabilityInvocationResult> {
|
|
343
|
+
const fullRequestUrl = new URL(url, serverUrl).toString()
|
|
344
|
+
let expected
|
|
345
|
+
if (allowTargetQuery || attenuatedRootTarget) {
|
|
346
|
+
// The acceptable roots: the ancestor's root capability (a delegated chain
|
|
347
|
+
// rooted at e.g. the Space URL, narrowing to the request URL), the
|
|
348
|
+
// `allowedTarget`'s own (a root invocation, or a delegated chain for the
|
|
349
|
+
// exact target -- the pre-existing shapes, unchanged), and, under
|
|
350
|
+
// `allowTargetQuery`, the query-bearing request URL's own (a controller
|
|
351
|
+
// invoking the query URL directly).
|
|
352
|
+
const rootTargets = [
|
|
353
|
+
...(attenuatedRootTarget ? [attenuatedRootTarget] : []),
|
|
354
|
+
allowedTarget,
|
|
355
|
+
...(allowTargetQuery ? [fullRequestUrl] : [])
|
|
356
|
+
]
|
|
357
|
+
expected = {
|
|
358
|
+
expectedAction: allowedAction,
|
|
359
|
+
expectedHost: new URL(serverUrl).host,
|
|
360
|
+
expectedRootCapability: [...new Set(rootTargets.map(rootCapabilityId))],
|
|
361
|
+
// The proof's invocationTarget is the invoked URL: `allowedTarget`
|
|
362
|
+
// itself, or (under `allowTargetQuery`) the query-bearing request URL.
|
|
363
|
+
// The array form is narrowed to `string` by the verify fork's option
|
|
364
|
+
// type, but the underlying `@interop/zcap` CapabilityInvocation
|
|
365
|
+
// accepts `string | string[]` -- hence the cast.
|
|
366
|
+
expectedTarget: [
|
|
367
|
+
...new Set([allowedTarget, fullRequestUrl])
|
|
368
|
+
] as unknown as string,
|
|
369
|
+
allowTargetAttenuation: true
|
|
370
|
+
}
|
|
371
|
+
} else if (allowTargetAttenuation) {
|
|
372
|
+
expected = {
|
|
373
|
+
expectedAction: allowedAction,
|
|
374
|
+
expectedHost: new URL(serverUrl).host,
|
|
375
|
+
// The proof's invocationTarget is the invoked URL: `allowedTarget`
|
|
376
|
+
// itself, or a path under it (accepted as a RESTful attenuation).
|
|
377
|
+
// The only acceptable root capability is `allowedTarget`'s. (Same
|
|
378
|
+
// array-form cast as above.)
|
|
379
|
+
expectedRootCapability: rootCapabilityId(allowedTarget),
|
|
380
|
+
expectedTarget: [
|
|
381
|
+
...new Set([allowedTarget, fullRequestUrl])
|
|
382
|
+
] as unknown as string,
|
|
383
|
+
allowTargetAttenuation: true
|
|
384
|
+
}
|
|
385
|
+
} else {
|
|
386
|
+
expected = {
|
|
387
|
+
expectedAction: allowedAction,
|
|
388
|
+
expectedHost: new URL(serverUrl).host,
|
|
389
|
+
rootInvocationTarget: allowedTarget,
|
|
390
|
+
expectedRootCapability: rootCapabilityId(allowedTarget),
|
|
391
|
+
expectedTarget: allowedTarget
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
const documentLoader = rootCapabilityLoader({
|
|
396
|
+
controllerFor: () => spaceController
|
|
397
|
+
})
|
|
398
|
+
|
|
399
|
+
// Returns the following object:
|
|
400
|
+
// {
|
|
401
|
+
// capability, capabilityAction, controller,
|
|
402
|
+
// dereferencedChain,
|
|
403
|
+
// invoker: controller,
|
|
404
|
+
// verificationMethod,
|
|
405
|
+
// verified: true
|
|
406
|
+
// }
|
|
407
|
+
return await verifyCapabilityInvocation({
|
|
408
|
+
url: fullRequestUrl,
|
|
409
|
+
method,
|
|
410
|
+
headers: headers as Record<string, string>,
|
|
411
|
+
...expected,
|
|
412
|
+
documentLoader,
|
|
413
|
+
getVerifier,
|
|
414
|
+
inspectCapabilityChain,
|
|
415
|
+
maxChainLength,
|
|
416
|
+
maxDelegationTtl,
|
|
417
|
+
suite: new Ed25519Signature2020()
|
|
418
|
+
})
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
/**
|
|
422
|
+
* Extracts the controller DIDs of one capability (`controller` may be a
|
|
423
|
+
* single value or an array on a synthesized root).
|
|
424
|
+
* @param capability {object} a capability from a dereferenced chain
|
|
425
|
+
* @returns {string[]}
|
|
426
|
+
*/
|
|
427
|
+
function capabilityControllers(capability: {
|
|
428
|
+
controller?: string | string[]
|
|
429
|
+
}): string[] {
|
|
430
|
+
const { controller } = capability
|
|
431
|
+
if (controller === undefined) {
|
|
432
|
+
return []
|
|
433
|
+
}
|
|
434
|
+
return Array.isArray(controller) ? controller : [controller]
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
/**
|
|
438
|
+
* Verifies the delegation chain of a capability submitted for revocation
|
|
439
|
+
* (`CapabilityDelegation` proof purpose over the embedded chain), throwing
|
|
440
|
+
* `InvalidRevocationError` (400) when it does not verify. The chain must root
|
|
441
|
+
* in the revocation's scope: its root capability's invocation target must be
|
|
442
|
+
* `rootTarget` -- the keystore URL, or the Space URL for a WAS-route
|
|
443
|
+
* revocation -- or a path under it (enforced where the root is synthesized,
|
|
444
|
+
* so a chain aimed at another keystore or Space -- or another service --
|
|
445
|
+
* cannot be submitted here, per ezcap-express `authorizeZcapRevocation`).
|
|
446
|
+
* Deliberately structural only -- it does NOT consult the revocation store:
|
|
447
|
+
* this runs before the invocation is authorized, and a store-dependent
|
|
448
|
+
* failure here would disclose revocation state to unauthorized callers
|
|
449
|
+
* (400 already-revoked vs the masked 404). The caller checks the returned
|
|
450
|
+
* `capabilities` against the store after authorization.
|
|
451
|
+
*
|
|
452
|
+
* @param options {object}
|
|
453
|
+
* @param options.capability {object} the delegated capability to be revoked
|
|
454
|
+
* (the request body, verbatim)
|
|
455
|
+
* @param options.rootTarget {string} the scope's full URL -- the keystore or
|
|
456
|
+
* the Space -- which the chain is required to root in
|
|
457
|
+
* @param options.rootController {IDID} the scope's controller (controller of
|
|
458
|
+
* the synthesized root capability)
|
|
459
|
+
* @param [options.maxChainLength] {number} max chain length, root included
|
|
460
|
+
* @param [options.maxDelegationTtl] {number} max delegated-zcap TTL (ms)
|
|
461
|
+
* @returns {Promise<{ delegator: string, chainControllers: string[],
|
|
462
|
+
* capabilities: CapabilitySummary[] }>} the capability's delegator (its
|
|
463
|
+
* delegation proof's controller), every controller in its chain (the
|
|
464
|
+
* parties allowed to submit the revocation), and the chain's
|
|
465
|
+
* `(capabilityId, delegator)` pairs for the caller's post-authorization
|
|
466
|
+
* revocation-store check
|
|
467
|
+
*/
|
|
468
|
+
export async function verifyRevocationChain({
|
|
469
|
+
capability,
|
|
470
|
+
rootTarget,
|
|
471
|
+
rootController,
|
|
472
|
+
maxChainLength,
|
|
473
|
+
maxDelegationTtl
|
|
474
|
+
}: {
|
|
475
|
+
capability: Record<string, unknown>
|
|
476
|
+
rootTarget: string
|
|
477
|
+
rootController: IDID
|
|
478
|
+
maxChainLength?: number
|
|
479
|
+
maxDelegationTtl?: number
|
|
480
|
+
}): Promise<{
|
|
481
|
+
delegator: string
|
|
482
|
+
chainControllers: string[]
|
|
483
|
+
capabilities: CapabilitySummary[]
|
|
484
|
+
}> {
|
|
485
|
+
const chainControllers: string[] = []
|
|
486
|
+
let capabilities: CapabilitySummary[] = []
|
|
487
|
+
const documentLoader = rootCapabilityLoader({
|
|
488
|
+
controllerFor: target => {
|
|
489
|
+
if (target !== rootTarget && !target.startsWith(`${rootTarget}/`)) {
|
|
490
|
+
throw new Error(
|
|
491
|
+
`The root capability from the revocation's delegation chain must` +
|
|
492
|
+
` have an invocation target that starts with "${rootTarget}".`
|
|
493
|
+
)
|
|
494
|
+
}
|
|
495
|
+
return rootController
|
|
496
|
+
}
|
|
497
|
+
})
|
|
498
|
+
const suite = new Ed25519Signature2020()
|
|
499
|
+
const result = (await jsigs.verify(capability, {
|
|
500
|
+
documentLoader,
|
|
501
|
+
suite,
|
|
502
|
+
purpose: new CapabilityDelegation({
|
|
503
|
+
suite,
|
|
504
|
+
expectedRootCapability: rootCapabilityId(rootTarget),
|
|
505
|
+
// Attenuation is always tolerated when judging revocability: a zcap
|
|
506
|
+
// delegated with attenuation rules an invocation endpoint would refuse
|
|
507
|
+
// can still be revoked (ezcap-express `_verifyDelegation`).
|
|
508
|
+
allowTargetAttenuation: true,
|
|
509
|
+
maxChainLength,
|
|
510
|
+
maxDelegationTtl,
|
|
511
|
+
inspectCapabilityChain: async details => {
|
|
512
|
+
// Capture every controller in the dereferenced chain -- these are the
|
|
513
|
+
// parties the dual-root rule lets submit this revocation -- and the
|
|
514
|
+
// chain's lookup pairs for the caller's post-authorization store check.
|
|
515
|
+
for (const chainCapability of details.capabilityChain) {
|
|
516
|
+
chainControllers.push(...capabilityControllers(chainCapability))
|
|
517
|
+
}
|
|
518
|
+
capabilities = capabilitySummaries(details)
|
|
519
|
+
return { valid: true }
|
|
520
|
+
}
|
|
521
|
+
})
|
|
522
|
+
})) as {
|
|
523
|
+
verified: boolean
|
|
524
|
+
error?: Error
|
|
525
|
+
results?: Array<{
|
|
526
|
+
purposeResult?: { delegator?: { id?: string } | string }
|
|
527
|
+
}>
|
|
528
|
+
}
|
|
529
|
+
if (!result.verified) {
|
|
530
|
+
throw new InvalidRevocationError({
|
|
531
|
+
detail: 'The provided capability delegation is invalid.',
|
|
532
|
+
cause: result.error
|
|
533
|
+
})
|
|
534
|
+
}
|
|
535
|
+
const rawDelegator = result.results?.[0]?.purposeResult?.delegator
|
|
536
|
+
const delegator =
|
|
537
|
+
typeof rawDelegator === 'string' ? rawDelegator : rawDelegator?.id
|
|
538
|
+
if (!delegator) {
|
|
539
|
+
throw new InvalidRevocationError({
|
|
540
|
+
detail: 'The capability delegation has no identifiable delegator.'
|
|
541
|
+
})
|
|
542
|
+
}
|
|
543
|
+
return { delegator, chainControllers, capabilities }
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
/**
|
|
547
|
+
* Verifies the capability invocation on a revocation submission under the
|
|
548
|
+
* dual-root rule: the invocation may root in the scope -- the keystore or the
|
|
549
|
+
* Space -- (whose controller may revoke anything delegated from it, delegates
|
|
550
|
+
* of a revocation capability included, via target attenuation), or in the
|
|
551
|
+
* revocation URL itself, whose
|
|
552
|
+
* synthesized root capability is controlled by *every controller in the
|
|
553
|
+
* to-be-revoked capability's chain* -- so a delegee can revoke its own zcap
|
|
554
|
+
* without holding a separate capability (ezcap-express
|
|
555
|
+
* `authorizeZcapRevocation`). Throws like `handleZcapVerify`:
|
|
556
|
+
* `AuthVerificationError` (400) when verification errors, the 404-masked
|
|
557
|
+
* `UnauthorizedError` when the invocation does not verify.
|
|
558
|
+
*
|
|
559
|
+
* @param options {object}
|
|
560
|
+
* @param options.url {string} request URL (path), resolved against serverUrl
|
|
561
|
+
* @param options.method {string} the HTTP method of the request
|
|
562
|
+
* @param options.headers {IncomingHttpHeaders} the request headers
|
|
563
|
+
* @param options.serverUrl {string} this server's base URL
|
|
564
|
+
* @param options.rootTarget {string} the scope's full URL (the keystore or
|
|
565
|
+
* the Space)
|
|
566
|
+
* @param options.rootController {IDID} the scope's controller
|
|
567
|
+
* @param options.chainControllers {string[]} every controller in the
|
|
568
|
+
* to-be-revoked capability's (already verified) chain
|
|
569
|
+
* @param options.expectedAction {string} the action the invocation must
|
|
570
|
+
* carry: the webkms `write` on `/kms`, the HTTP verb (`POST`) on the WAS
|
|
571
|
+
* route families, whose capabilities are scoped by HTTP method
|
|
572
|
+
* @param [options.inspectCapabilityChain] {InspectCapabilityChain} the
|
|
573
|
+
* revocation-store hook, run against the *invoking* chain
|
|
574
|
+
* @param [options.maxChainLength] {number} max chain length, root included
|
|
575
|
+
* @param [options.maxDelegationTtl] {number} max delegated-zcap TTL (ms)
|
|
576
|
+
* @param [options.requestName] {string} request name used in error titles
|
|
577
|
+
* @param [options.logger] {ZcapLogger} logger for verification errors
|
|
578
|
+
* @returns {Promise<void>}
|
|
579
|
+
*/
|
|
580
|
+
export async function handleRevocationInvocationVerify({
|
|
581
|
+
url,
|
|
582
|
+
method,
|
|
583
|
+
headers,
|
|
584
|
+
serverUrl,
|
|
585
|
+
rootTarget,
|
|
586
|
+
rootController,
|
|
587
|
+
chainControllers,
|
|
588
|
+
expectedAction,
|
|
589
|
+
inspectCapabilityChain,
|
|
590
|
+
maxChainLength,
|
|
591
|
+
maxDelegationTtl,
|
|
592
|
+
requestName = '',
|
|
593
|
+
logger = console
|
|
594
|
+
}: {
|
|
595
|
+
url: string
|
|
596
|
+
method: string
|
|
597
|
+
headers: IncomingHttpHeaders
|
|
598
|
+
serverUrl: string
|
|
599
|
+
rootTarget: string
|
|
600
|
+
rootController: IDID
|
|
601
|
+
chainControllers: string[]
|
|
602
|
+
expectedAction: string
|
|
603
|
+
inspectCapabilityChain?: InspectCapabilityChain
|
|
604
|
+
maxChainLength?: number
|
|
605
|
+
maxDelegationTtl?: number
|
|
606
|
+
requestName?: string
|
|
607
|
+
logger?: ZcapLogger
|
|
608
|
+
}): Promise<void> {
|
|
609
|
+
const fullRequestUrl = new URL(url, serverUrl).toString()
|
|
610
|
+
const documentLoader = rootCapabilityLoader({
|
|
611
|
+
controllerFor: target => {
|
|
612
|
+
if (target === rootTarget) {
|
|
613
|
+
return rootController
|
|
614
|
+
}
|
|
615
|
+
if (target === fullRequestUrl) {
|
|
616
|
+
return chainControllers
|
|
617
|
+
}
|
|
618
|
+
throw new Error(
|
|
619
|
+
`Unexpected root capability target "${target}" on a revocation.`
|
|
620
|
+
)
|
|
621
|
+
}
|
|
622
|
+
})
|
|
623
|
+
|
|
624
|
+
let zcapVerifyResult: VerifyCapabilityInvocationResult
|
|
625
|
+
try {
|
|
626
|
+
zcapVerifyResult = await verifyCapabilityInvocation({
|
|
627
|
+
url: fullRequestUrl,
|
|
628
|
+
method,
|
|
629
|
+
headers: headers as Record<string, string>,
|
|
630
|
+
expectedAction,
|
|
631
|
+
expectedHost: new URL(serverUrl).host,
|
|
632
|
+
expectedRootCapability: [
|
|
633
|
+
rootCapabilityId(rootTarget),
|
|
634
|
+
rootCapabilityId(fullRequestUrl)
|
|
635
|
+
],
|
|
636
|
+
// The invoked target is the revocation URL, a path under the scope's
|
|
637
|
+
// root; accept either as a delegated zcap's (attenuated) target. The
|
|
638
|
+
// array form is narrowed to `string` by the verify fork's option type
|
|
639
|
+
// (see the same cast in `verifyZcap`'s attenuation branch).
|
|
640
|
+
expectedTarget: [rootTarget, fullRequestUrl] as unknown as string,
|
|
641
|
+
allowTargetAttenuation: true,
|
|
642
|
+
documentLoader,
|
|
643
|
+
getVerifier,
|
|
644
|
+
inspectCapabilityChain,
|
|
645
|
+
maxChainLength,
|
|
646
|
+
maxDelegationTtl,
|
|
647
|
+
suite: new Ed25519Signature2020()
|
|
648
|
+
})
|
|
649
|
+
} catch (err) {
|
|
650
|
+
logger.error({ err }, 'ZCAP revocation invocation verification failed')
|
|
651
|
+
throw new AuthVerificationError({ requestName, cause: err as Error })
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
if (!zcapVerifyResult.verified) {
|
|
655
|
+
throw new UnauthorizedError({ requestName })
|
|
656
|
+
}
|
|
657
|
+
}
|