@interop/zcap 9.0.3
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/LICENSE +27 -0
- package/README.md +65 -0
- package/lib/CapabilityDelegation.js +312 -0
- package/lib/CapabilityInvocation.js +343 -0
- package/lib/CapabilityProofPurpose.js +538 -0
- package/lib/constants.js +32 -0
- package/lib/index.js +60 -0
- package/lib/utils.js +674 -0
- package/lib/zcap-types.d.ts +72 -0
- package/package.json +81 -0
- package/types/lib/CapabilityDelegation.d.ts +101 -0
- package/types/lib/CapabilityDelegation.d.ts.map +1 -0
- package/types/lib/CapabilityInvocation.d.ts +100 -0
- package/types/lib/CapabilityInvocation.d.ts.map +1 -0
- package/types/lib/CapabilityProofPurpose.d.ts +126 -0
- package/types/lib/CapabilityProofPurpose.d.ts.map +1 -0
- package/types/lib/constants.d.ts +15 -0
- package/types/lib/constants.d.ts.map +1 -0
- package/types/lib/index.d.ts +50 -0
- package/types/lib/index.d.ts.map +1 -0
- package/types/lib/utils.d.ts +312 -0
- package/types/lib/utils.d.ts.map +1 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
Copyright (c) 2018-2021, Digital Bazaar, Inc.
|
|
2
|
+
All rights reserved.
|
|
3
|
+
|
|
4
|
+
Redistribution and use in source and binary forms, with or without
|
|
5
|
+
modification, are permitted provided that the following conditions are met:
|
|
6
|
+
|
|
7
|
+
* Redistributions of source code must retain the above copyright notice, this
|
|
8
|
+
list of conditions and the following disclaimer.
|
|
9
|
+
|
|
10
|
+
* Redistributions in binary form must reproduce the above copyright notice,
|
|
11
|
+
this list of conditions and the following disclaimer in the documentation
|
|
12
|
+
and/or other materials provided with the distribution.
|
|
13
|
+
|
|
14
|
+
* Neither the name of the copyright holder nor the names of its
|
|
15
|
+
contributors may be used to endorse or promote products derived from
|
|
16
|
+
this software without specific prior written permission.
|
|
17
|
+
|
|
18
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
19
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
20
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
21
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
22
|
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
23
|
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
24
|
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
25
|
+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
26
|
+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
27
|
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
package/README.md
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# zcap _(@interop/zcap)_
|
|
2
|
+
|
|
3
|
+
[](https://github.com/interop-alliance/zcap/actions?query=workflow%3A%22CI%22)
|
|
4
|
+
[](https://npm.im/@interop/zcap)
|
|
5
|
+
|
|
6
|
+
> JavaScript reference implementation for
|
|
7
|
+
[Authorization Capabilities](https://w3c-ccg.github.io/zcap-spec/).
|
|
8
|
+
|
|
9
|
+
## Table of Contents
|
|
10
|
+
|
|
11
|
+
- [Background](#background)
|
|
12
|
+
- [Security](#security)
|
|
13
|
+
- [Install](#install)
|
|
14
|
+
- [Usage](#usage)
|
|
15
|
+
- [Contribute](#contribute)
|
|
16
|
+
- [Commercial Support](#commercial-support)
|
|
17
|
+
- [License](#license)
|
|
18
|
+
|
|
19
|
+
## Background
|
|
20
|
+
|
|
21
|
+
TODO
|
|
22
|
+
|
|
23
|
+
## Security
|
|
24
|
+
|
|
25
|
+
TBD
|
|
26
|
+
|
|
27
|
+
## Install
|
|
28
|
+
|
|
29
|
+
- Browsers and Node.js 14+ are supported.
|
|
30
|
+
|
|
31
|
+
To install from NPM:
|
|
32
|
+
|
|
33
|
+
```sh
|
|
34
|
+
npm install @interop/zcap
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
To install locally (for development):
|
|
38
|
+
|
|
39
|
+
```sh
|
|
40
|
+
git clone https://github.com/interop-alliance/zcap.git
|
|
41
|
+
cd zcap
|
|
42
|
+
npm install
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Usage
|
|
46
|
+
|
|
47
|
+
TBD
|
|
48
|
+
|
|
49
|
+
## Contribute
|
|
50
|
+
|
|
51
|
+
See [the contribute file](https://github.com/interop-alliance/bedrock/blob/master/CONTRIBUTING.md)!
|
|
52
|
+
|
|
53
|
+
PRs accepted.
|
|
54
|
+
|
|
55
|
+
If editing the Readme, please conform to the
|
|
56
|
+
[standard-readme](https://github.com/RichardLitt/standard-readme) specification.
|
|
57
|
+
|
|
58
|
+
## Commercial Support
|
|
59
|
+
|
|
60
|
+
Commercial support for this library is available upon request from
|
|
61
|
+
Digital Bazaar: support@interop.com
|
|
62
|
+
|
|
63
|
+
## License
|
|
64
|
+
|
|
65
|
+
[New BSD License (3-clause)](LICENSE) © Digital Bazaar
|
|
@@ -0,0 +1,312 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* Copyright (c) 2018-2024 Digital Bazaar, Inc. All rights reserved.
|
|
3
|
+
*/
|
|
4
|
+
import * as utils from './utils.js';
|
|
5
|
+
import {CapabilityProofPurpose} from './CapabilityProofPurpose.js';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* @typedef {import('./utils.js').InspectCapabilityChain} InspectCapabilityChain
|
|
9
|
+
* @typedef {import('./utils.js').Zcap} Zcap
|
|
10
|
+
* @typedef {import('./utils.js').DelegatedZcap} DelegatedZcap
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
export class CapabilityDelegation extends CapabilityProofPurpose {
|
|
14
|
+
/**
|
|
15
|
+
* @param {object} options - The options.
|
|
16
|
+
* @param {string|Zcap} [options.parentCapability] - An alternative to
|
|
17
|
+
* passing `capabilityChain` when creating a proof; passing
|
|
18
|
+
* `parentCapability` will enable the capability chain to be auto-computed.
|
|
19
|
+
* Pass a root zcap ID string, or a full root or delegated zcap object.
|
|
20
|
+
* @param {boolean} [options.allowTargetAttenuation=false] - Allow the
|
|
21
|
+
* invocationTarget of a delegation chain to be increasingly restrictive
|
|
22
|
+
* based on a hierarchical RESTful URL structure.
|
|
23
|
+
* @param {string|Date|number} [options.date] - Used during proof
|
|
24
|
+
* verification as the expected date for the creation of the proof
|
|
25
|
+
* (within a maximum timestamp delta) and for checking to see if a
|
|
26
|
+
* capability has expired; if not passed the current date will be used.
|
|
27
|
+
* @param {string|string[]} [options.expectedRootCapability] - The expected
|
|
28
|
+
* root capability for the delegation chain (a single root capability ID
|
|
29
|
+
* string, or an array of acceptable root capability ID strings).
|
|
30
|
+
* @param {object} [options.controller] - The description of the controller,
|
|
31
|
+
* if it is not to be dereferenced via a `documentLoader`.
|
|
32
|
+
* @param {InspectCapabilityChain} [options.inspectCapabilityChain] - An
|
|
33
|
+
* async function that can be used to check for revocations related to any
|
|
34
|
+
* of verified capabilities.
|
|
35
|
+
* @param {number} [options.maxChainLength=10] - The maximum length of the
|
|
36
|
+
* capability delegation chain.
|
|
37
|
+
* @param {number} [options.maxClockSkew=300] - A maximum number of seconds
|
|
38
|
+
* that clocks may be skewed when checking capability expiration date-times
|
|
39
|
+
* against `date`.
|
|
40
|
+
* @param {number} [options.maxDelegationTtl=Infinity] - The maximum
|
|
41
|
+
* milliseconds to live for a delegated zcap as measured by the time
|
|
42
|
+
* difference between `expires` and `created` on the delegation proof.
|
|
43
|
+
* @param {object|object[]} [options.suite] - The jsonld-signature suite(s) to
|
|
44
|
+
* use to verify the capability chain. Required only in verify-proof mode;
|
|
45
|
+
* unused (and omitted) when creating a delegation proof.
|
|
46
|
+
* @param {Zcap} [options._verifiedParentCapability] - Private.
|
|
47
|
+
* @param {Array<string|DelegatedZcap>} [options._capabilityChain] - Private.
|
|
48
|
+
* @param {boolean} [options._skipLocalValidationForTesting] - Private.
|
|
49
|
+
*/
|
|
50
|
+
constructor({
|
|
51
|
+
// proof creation params
|
|
52
|
+
parentCapability,
|
|
53
|
+
// proof verification params
|
|
54
|
+
allowTargetAttenuation,
|
|
55
|
+
controller,
|
|
56
|
+
date,
|
|
57
|
+
expectedRootCapability,
|
|
58
|
+
inspectCapabilityChain,
|
|
59
|
+
maxChainLength,
|
|
60
|
+
maxClockSkew,
|
|
61
|
+
maxDelegationTtl,
|
|
62
|
+
suite,
|
|
63
|
+
_verifiedParentCapability,
|
|
64
|
+
// for testing purposes only, not documented intentionally
|
|
65
|
+
_capabilityChain,
|
|
66
|
+
_skipLocalValidationForTesting = false
|
|
67
|
+
} = {}) {
|
|
68
|
+
// parameters used to create a proof
|
|
69
|
+
const hasCreateProofParams = parentCapability || _capabilityChain;
|
|
70
|
+
// params used to verify a proof
|
|
71
|
+
const hasVerifyProofParams = controller || date ||
|
|
72
|
+
expectedRootCapability ||
|
|
73
|
+
inspectCapabilityChain || suite ||
|
|
74
|
+
_verifiedParentCapability;
|
|
75
|
+
|
|
76
|
+
if(hasCreateProofParams && hasVerifyProofParams) {
|
|
77
|
+
// cannot provide both create and verify params
|
|
78
|
+
throw new Error(
|
|
79
|
+
'Parameters for both creating and verifying a proof must not be ' +
|
|
80
|
+
'provided together.');
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
super({
|
|
84
|
+
allowTargetAttenuation,
|
|
85
|
+
controller, date,
|
|
86
|
+
expectedRootCapability, inspectCapabilityChain,
|
|
87
|
+
maxChainLength, maxClockSkew, maxDelegationTtl,
|
|
88
|
+
// always `Infinity` for capability delegation proofs, as their "created"
|
|
89
|
+
// values are not checked for liveness, rather "expires" is used instead
|
|
90
|
+
maxTimestampDelta: Infinity,
|
|
91
|
+
suite,
|
|
92
|
+
term: 'capabilityDelegation'
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
// validate `CapabilityDelegation` specific params, the base class will
|
|
96
|
+
// have already handled validating common ones...
|
|
97
|
+
|
|
98
|
+
// use negative conditional to cover case where neither create nor
|
|
99
|
+
// verify params were provided and default to proof creation case to
|
|
100
|
+
// avoid creating bad proofs
|
|
101
|
+
if(!hasVerifyProofParams) {
|
|
102
|
+
if(!(typeof parentCapability === 'string' ||
|
|
103
|
+
(typeof parentCapability === 'object' &&
|
|
104
|
+
typeof parentCapability.id === 'string'))) {
|
|
105
|
+
throw new TypeError(
|
|
106
|
+
'"parentCapability" must be a string expressing the ID of a root ' +
|
|
107
|
+
'capability or an object expressing the full parent capability.');
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
this.parentCapability = parentCapability;
|
|
111
|
+
if(_capabilityChain) {
|
|
112
|
+
if(!Array.isArray(_capabilityChain)) {
|
|
113
|
+
throw new TypeError('"_capabilityChain" must be an array.');
|
|
114
|
+
}
|
|
115
|
+
this._capabilityChain = _capabilityChain;
|
|
116
|
+
}
|
|
117
|
+
if(_skipLocalValidationForTesting !== undefined) {
|
|
118
|
+
this._skipLocalValidationForTesting = _skipLocalValidationForTesting;
|
|
119
|
+
}
|
|
120
|
+
} else {
|
|
121
|
+
this._verifiedParentCapability = _verifiedParentCapability;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
async update(proof, {document}) {
|
|
126
|
+
// if no capability chain given (*for testing purposes only*), then
|
|
127
|
+
// compute from parent
|
|
128
|
+
let capabilityChain;
|
|
129
|
+
const {
|
|
130
|
+
parentCapability, term,
|
|
131
|
+
_capabilityChain, _skipLocalValidationForTesting
|
|
132
|
+
} = this;
|
|
133
|
+
if(_capabilityChain) {
|
|
134
|
+
// use chain override from tests
|
|
135
|
+
capabilityChain = _capabilityChain;
|
|
136
|
+
} else {
|
|
137
|
+
capabilityChain = utils.computeCapabilityChain({
|
|
138
|
+
parentCapability, _skipLocalValidationForTesting
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
proof.proofPurpose = term;
|
|
143
|
+
proof.capabilityChain = capabilityChain;
|
|
144
|
+
|
|
145
|
+
if(!_skipLocalValidationForTesting) {
|
|
146
|
+
// check capability data model
|
|
147
|
+
const capability = {...document, proof};
|
|
148
|
+
utils.checkCapability({capability, expectRoot: false});
|
|
149
|
+
|
|
150
|
+
// ensure proof will not be created after it expires
|
|
151
|
+
const created = Date.parse(proof.created);
|
|
152
|
+
const expires = Date.parse(capability.expires);
|
|
153
|
+
/* Note: Intentionally do not use `utils.compareTime` as there is no
|
|
154
|
+
clock drift issue here. We are not comparing against any live values
|
|
155
|
+
but against date-time values expressed in the chain. */
|
|
156
|
+
if(created > expires) {
|
|
157
|
+
throw new Error('Cannot delegate an expired capability.');
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// ensure `allowedAction`, if present, is not less restrictive
|
|
161
|
+
const {allowedAction: parentAllowedAction} = parentCapability;
|
|
162
|
+
const {allowedAction} = document;
|
|
163
|
+
if(!utils.hasValidAllowedAction({allowedAction, parentAllowedAction})) {
|
|
164
|
+
throw new Error(
|
|
165
|
+
'The "allowedAction" in a delegated capability ' +
|
|
166
|
+
'must not be less restrictive than its parent.');
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// ensure `expires` is not less restrictive
|
|
170
|
+
const {expires: parentExpires} = parentCapability;
|
|
171
|
+
if(parentExpires !== undefined) {
|
|
172
|
+
// handle case where `expires` is set in the parent, but the child
|
|
173
|
+
// has an expiration date greater than the parent;
|
|
174
|
+
/* Note: Intentionally do not use `utils.compareTime` as there is no
|
|
175
|
+
clock drift issue here. We are not comparing against any live values
|
|
176
|
+
but against date-time values expressed in the chain. Additionally,
|
|
177
|
+
allowing skew here could introduce vulnerabilities where the expires
|
|
178
|
+
time drift could aggregate with each new capability in the chain. */
|
|
179
|
+
if(expires > Date.parse(parentExpires)) {
|
|
180
|
+
throw new Error(
|
|
181
|
+
'The `expires` property in a delegated capability must not be ' +
|
|
182
|
+
'less restrictive than its parent.');
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// ensure capability won't be delegated before its parent was delegated
|
|
187
|
+
// (if that parent is non-root)
|
|
188
|
+
if(capabilityChain.length > 1) {
|
|
189
|
+
// get delegated date-time (note: `computeCapabilityChain` has already
|
|
190
|
+
// validated that there is a single delegation proof in
|
|
191
|
+
// `parentCapability`)
|
|
192
|
+
const [parentProof] = utils.getDelegationProofs(
|
|
193
|
+
{capability: parentCapability});
|
|
194
|
+
const parentDelegationTime = Date.parse(parentProof.created);
|
|
195
|
+
const childDelegationTime = Date.parse(proof.created);
|
|
196
|
+
// verify parent capability was not delegated after child
|
|
197
|
+
if(parentDelegationTime > childDelegationTime) {
|
|
198
|
+
throw new Error(
|
|
199
|
+
'A capability in the delegation chain was delegated before ' +
|
|
200
|
+
'its parent.');
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
return proof;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
async match(proof, {document, documentLoader}) {
|
|
209
|
+
try {
|
|
210
|
+
// check the `proof` context before using its terms
|
|
211
|
+
utils.checkProofContext({proof});
|
|
212
|
+
} catch(e) {
|
|
213
|
+
// context does not match, so proof does not match
|
|
214
|
+
return false;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
return super.match(proof, {document, documentLoader});
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
_getCapabilityDelegationClass() {
|
|
221
|
+
return CapabilityDelegation;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
_getTailCapability({document, proof}) {
|
|
225
|
+
// `proof` must be reattached to the capability because it contains
|
|
226
|
+
// the `capabilityChain` that must be dereferenced and verified
|
|
227
|
+
return {capability: {...document, proof}};
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
async _runChecksBeforeChainVerification() {
|
|
231
|
+
/* Note: Here we create a signal to be sent to `_verifyCapabilityChain`
|
|
232
|
+
that the capability delegation proof for the tail has already been
|
|
233
|
+
verified (to avoid it being reverified). We will compute the full
|
|
234
|
+
`verifyResult` in `_runChecksAfterChainVerification` once we have verified
|
|
235
|
+
the parent capability. */
|
|
236
|
+
return {capabilityChainMeta: [{verifyResult: {}}]};
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
async _runChecksAfterChainVerification({
|
|
240
|
+
capabilityChainMeta, dereferencedChain, proof, validateOptions
|
|
241
|
+
}) {
|
|
242
|
+
// verified parent is second to last in the chain (i.e., it is the parent
|
|
243
|
+
// of the last in the chain)
|
|
244
|
+
const verifiedParentCapability = dereferencedChain[
|
|
245
|
+
dereferencedChain.length - 2];
|
|
246
|
+
|
|
247
|
+
// get purpose result which needs to be used to build `verifyResult`
|
|
248
|
+
const purposeResult = await this._validateAgainstParent({
|
|
249
|
+
proof, verifiedParentCapability, validateOptions
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
// build verify result
|
|
253
|
+
const {verificationMethod} = validateOptions;
|
|
254
|
+
const {verifyResult} = capabilityChainMeta[capabilityChainMeta.length - 1];
|
|
255
|
+
verifyResult.verified = purposeResult.valid;
|
|
256
|
+
verifyResult.results = [{
|
|
257
|
+
proof, verified: true, verificationMethod, purposeResult
|
|
258
|
+
}];
|
|
259
|
+
|
|
260
|
+
return purposeResult;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
async _shortCircuitValidate({proof, validateOptions}) {
|
|
264
|
+
// see if the parent capability has already been verified
|
|
265
|
+
const {
|
|
266
|
+
_verifiedParentCapability: verifiedParentCapability
|
|
267
|
+
} = this;
|
|
268
|
+
if(verifiedParentCapability) {
|
|
269
|
+
// simple case, just validate against parent and return, we have been
|
|
270
|
+
// called from within a chain verification and can short circuit proof
|
|
271
|
+
// validation
|
|
272
|
+
return this._validateAgainstParent({
|
|
273
|
+
proof, verifiedParentCapability, validateOptions
|
|
274
|
+
});
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// no short-circuit possible, we've just started validating the proof
|
|
278
|
+
// from root => tail
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
async _validateAgainstParent({
|
|
282
|
+
proof, verifiedParentCapability, validateOptions
|
|
283
|
+
}) {
|
|
284
|
+
// ensure proof created by authorized delegator...
|
|
285
|
+
// parent zcap controller must match the delegating verification method
|
|
286
|
+
// (or its controller)
|
|
287
|
+
const {verificationMethod} = validateOptions;
|
|
288
|
+
if(!utils.isController(
|
|
289
|
+
{capability: verifiedParentCapability, verificationMethod})) {
|
|
290
|
+
const error = new Error(
|
|
291
|
+
'The capability controller does not match the verification ' +
|
|
292
|
+
'method (or its controller) used to delegate.');
|
|
293
|
+
error.details = {
|
|
294
|
+
capability: verifiedParentCapability,
|
|
295
|
+
verificationMethod
|
|
296
|
+
};
|
|
297
|
+
throw error;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
// run base level validation checks
|
|
301
|
+
const result = await this._runBaseProofValidation({proof, validateOptions});
|
|
302
|
+
if(!result.valid) {
|
|
303
|
+
throw result.error;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
// the controller of the proof is the delegator of the capability
|
|
307
|
+
result.delegator = result.controller;
|
|
308
|
+
|
|
309
|
+
// `result` includes meta data about the proof controller
|
|
310
|
+
return result;
|
|
311
|
+
}
|
|
312
|
+
}
|