From f8e1c0c5e26ab149c33fdddb4af5da0952c817f2 Mon Sep 17 00:00:00 2001 From: dholms Date: Mon, 27 Jun 2022 18:36:09 -0500 Subject: [PATCH 01/25] refactor --- package.json | 55 +- .../core/jest.config.js | 0 packages/core/package.json | 81 ++ {src => packages/core/src}/attenuation.ts | 0 packages/core/src/builder.ts | 288 ++++ .../core/src}/capability/ability.ts | 0 .../core/src}/capability/index.ts | 0 .../core/src}/capability/resource-pointer.ts | 0 .../core/src}/capability/super-user.ts | 0 {src => packages/core/src}/compatibility.ts | 0 packages/core/src/index.ts | 10 + {src => packages/core/src}/semver.ts | 0 {src => packages/core/src}/store.ts | 0 packages/core/src/token.ts | 457 +++++++ {src => packages/core/src}/types.ts | 2 +- {src => packages/core/src}/util.ts | 0 packages/core/src/verify.ts | 141 ++ .../core/tests}/attenuation.test.ts | 0 .../core/tests}/builder.test.ts | 0 .../core/tests}/capability.test.ts | 0 .../core/tests}/capability/email.ts | 0 .../core/tests}/capability/wnfs.test.ts | 0 .../core/tests}/capability/wnfs.ts | 0 .../core/tests}/compatibility.test.ts | 0 {tests => packages/core/tests}/did-key.ts | 0 {tests => packages/core/tests}/did.test.ts | 0 {tests => packages/core/tests}/ecdsa.test.ts | 0 .../core/tests}/ed25519.test.ts | 0 {tests => packages/core/tests}/fixtures.ts | 0 {tests => packages/core/tests}/rsa.test.ts | 0 {tests => packages/core/tests}/semver.test.ts | 0 {tests => packages/core/tests}/store.test.ts | 0 {tests => packages/core/tests}/token.test.ts | 0 {tests => packages/core/tests}/verify.test.ts | 0 .../core/tsconfig.eslint.json | 0 tsconfig.json => packages/core/tsconfig.json | 0 {src => packages/default/src}/crypto/ecdsa.ts | 0 {src => packages/default/src}/crypto/index.ts | 0 {src => packages/default/src}/crypto/rsa.ts | 0 {src => packages/default/src}/did/prefix.ts | 0 .../default/src}/did/pubkey-compress.ts | 0 .../default/src}/did/transformers.ts | 0 .../default/src}/did/validation.ts | 0 {src => packages/default/src}/keypair/base.ts | 0 .../default/src}/keypair/ecdsa.ts | 0 .../default/src}/keypair/ed25519.ts | 0 .../default/src}/keypair/index.ts | 0 {src => packages/default/src}/keypair/rsa.ts | 0 packages/ucans/jest.config.js | 21 + packages/ucans/package.json | 87 ++ packages/ucans/src/attenuation.ts | 419 ++++++ {src => packages/ucans/src}/builder.ts | 0 packages/ucans/src/capability/ability.ts | 94 ++ packages/ucans/src/capability/index.ts | 120 ++ .../ucans/src/capability/resource-pointer.ts | 89 ++ packages/ucans/src/capability/super-user.ts | 10 + packages/ucans/src/compatibility.ts | 114 ++ {src => packages/ucans/src}/did.ts | 0 packages/ucans/src/did/prefix.ts | 106 ++ packages/ucans/src/did/pubkey-compress.ts | 78 ++ packages/ucans/src/did/transformers.ts | 105 ++ packages/ucans/src/did/validation.ts | 44 + {src => packages/ucans/src}/index.ts | 0 packages/ucans/src/semver.ts | 117 ++ packages/ucans/src/store.ts | 92 ++ {src => packages/ucans/src}/token.ts | 11 +- packages/ucans/src/types.ts | 143 ++ packages/ucans/src/util.ts | 70 + {src => packages/ucans/src}/verify.ts | 0 packages/ucans/tsconfig.eslint.json | 5 + packages/ucans/tsconfig.json | 12 + yarn.lock | 1202 ++++++----------- 72 files changed, 3153 insertions(+), 820 deletions(-) rename jest.config.js => packages/core/jest.config.js (100%) create mode 100644 packages/core/package.json rename {src => packages/core/src}/attenuation.ts (100%) create mode 100644 packages/core/src/builder.ts rename {src => packages/core/src}/capability/ability.ts (100%) rename {src => packages/core/src}/capability/index.ts (100%) rename {src => packages/core/src}/capability/resource-pointer.ts (100%) rename {src => packages/core/src}/capability/super-user.ts (100%) rename {src => packages/core/src}/compatibility.ts (100%) create mode 100644 packages/core/src/index.ts rename {src => packages/core/src}/semver.ts (100%) rename {src => packages/core/src}/store.ts (100%) create mode 100644 packages/core/src/token.ts rename {src => packages/core/src}/types.ts (97%) rename {src => packages/core/src}/util.ts (100%) create mode 100644 packages/core/src/verify.ts rename {tests => packages/core/tests}/attenuation.test.ts (100%) rename {tests => packages/core/tests}/builder.test.ts (100%) rename {tests => packages/core/tests}/capability.test.ts (100%) rename {tests => packages/core/tests}/capability/email.ts (100%) rename {tests => packages/core/tests}/capability/wnfs.test.ts (100%) rename {tests => packages/core/tests}/capability/wnfs.ts (100%) rename {tests => packages/core/tests}/compatibility.test.ts (100%) rename {tests => packages/core/tests}/did-key.ts (100%) rename {tests => packages/core/tests}/did.test.ts (100%) rename {tests => packages/core/tests}/ecdsa.test.ts (100%) rename {tests => packages/core/tests}/ed25519.test.ts (100%) rename {tests => packages/core/tests}/fixtures.ts (100%) rename {tests => packages/core/tests}/rsa.test.ts (100%) rename {tests => packages/core/tests}/semver.test.ts (100%) rename {tests => packages/core/tests}/store.test.ts (100%) rename {tests => packages/core/tests}/token.test.ts (100%) rename {tests => packages/core/tests}/verify.test.ts (100%) rename tsconfig.eslint.json => packages/core/tsconfig.eslint.json (100%) rename tsconfig.json => packages/core/tsconfig.json (100%) rename {src => packages/default/src}/crypto/ecdsa.ts (100%) rename {src => packages/default/src}/crypto/index.ts (100%) rename {src => packages/default/src}/crypto/rsa.ts (100%) rename {src => packages/default/src}/did/prefix.ts (100%) rename {src => packages/default/src}/did/pubkey-compress.ts (100%) rename {src => packages/default/src}/did/transformers.ts (100%) rename {src => packages/default/src}/did/validation.ts (100%) rename {src => packages/default/src}/keypair/base.ts (100%) rename {src => packages/default/src}/keypair/ecdsa.ts (100%) rename {src => packages/default/src}/keypair/ed25519.ts (100%) rename {src => packages/default/src}/keypair/index.ts (100%) rename {src => packages/default/src}/keypair/rsa.ts (100%) create mode 100644 packages/ucans/jest.config.js create mode 100644 packages/ucans/package.json create mode 100644 packages/ucans/src/attenuation.ts rename {src => packages/ucans/src}/builder.ts (100%) create mode 100644 packages/ucans/src/capability/ability.ts create mode 100644 packages/ucans/src/capability/index.ts create mode 100644 packages/ucans/src/capability/resource-pointer.ts create mode 100644 packages/ucans/src/capability/super-user.ts create mode 100644 packages/ucans/src/compatibility.ts rename {src => packages/ucans/src}/did.ts (100%) create mode 100644 packages/ucans/src/did/prefix.ts create mode 100644 packages/ucans/src/did/pubkey-compress.ts create mode 100644 packages/ucans/src/did/transformers.ts create mode 100644 packages/ucans/src/did/validation.ts rename {src => packages/ucans/src}/index.ts (100%) create mode 100644 packages/ucans/src/semver.ts create mode 100644 packages/ucans/src/store.ts rename {src => packages/ucans/src}/token.ts (97%) create mode 100644 packages/ucans/src/types.ts create mode 100644 packages/ucans/src/util.ts rename {src => packages/ucans/src}/verify.ts (100%) create mode 100644 packages/ucans/tsconfig.eslint.json create mode 100644 packages/ucans/tsconfig.json diff --git a/package.json b/package.json index 8745811..d35aca2 100644 --- a/package.json +++ b/package.json @@ -12,58 +12,11 @@ "engines": { "node": ">=15" }, - "scripts": { - "prebuild": "rimraf dist", - "build": "yarn run dist", - "dev": "tsc --watch --module commonjs --outDir ./dist/cjs/ --sourceMap", - "dist": "yarn run dist:prep && yarn run dist:src && yarn run dist:cjs && yarn run dist:esm && yarn run dist:types && yarn run dist:pkg", - "dist:cjs": "tsc --project ./dist/ --module commonjs --outDir ./dist/cjs/ --sourceMap", - "dist:esm": "tsc --project ./dist/ --module es2020 --outDir ./dist/esm/ --sourceMap", - "dist:pkg": "node ./scripts/package.js", - "dist:prep": "copyfiles --error tsconfig.json ./dist/", - "dist:src": "copyfiles --error --up 1 \"./src/**/*\" ./dist/src/", - "dist:types": "tsc --project ./dist/ --emitDeclarationOnly --declaration --declarationDir ./dist/types/", - "lint": "eslint src/**/*.ts src/*.ts tests/**/*.ts tests/*.ts", - "prepare": "yarn build", - "publish-alpha": "yarn publish --tag alpha", - "publish-stable": "yarn publish --tag latest", - "test": "jest", - "test:watch": "jest --watch" - }, - "exports": { - ".": { - "import": "./dist/esm/index.js", - "require": "./dist/cjs/index.js", - "types": "./dist/types/index.d.ts" - }, - "./*.js": { - "import": "./dist/esm/*.js", - "require": "./dist/cjs/*.js", - "types": "./dist/types/*.d.ts" - }, - "./*": { - "import": "./dist/esm/*.js", - "require": "./dist/cjs/*.js", - "types": "./dist/types/*.d.ts" - } - }, - "typesVersions": { - "*": { - "index.d.ts": [ - "dist/types/index.d.ts" - ], - "*": [ - "dist/types/*" - ] - } - }, - "files": [ - "dist", - "docs", - "CHANGELOG.md", - "LICENSE", - "README.md" + "private": true, + "workspaces": [ + "packages/*" ], + "scripts": { }, "dependencies": { "@stablelib/ed25519": "^1.0.2", "big-integer": "^1.6.51", diff --git a/jest.config.js b/packages/core/jest.config.js similarity index 100% rename from jest.config.js rename to packages/core/jest.config.js diff --git a/packages/core/package.json b/packages/core/package.json new file mode 100644 index 0000000..68a5b60 --- /dev/null +++ b/packages/core/package.json @@ -0,0 +1,81 @@ +{ + "name": "@ucans/core", + "version": "0.9.1", + "description": "Typescript implementation of UCANs", + "author": "Daniel Holmgren ", + "repository": { + "type": "git", + "url": "https://github.com/fission-suite/ucan" + }, + "homepage": "https://guide.fission.codes", + "license": "Apache-2.0", + "engines": { + "node": ">=15" + }, + "scripts": { + "prebuild": "rimraf dist", + "build": "yarn run dist", + "dev": "tsc --watch --module commonjs --outDir ./dist/cjs/ --sourceMap", + "dist": "yarn run dist:prep && yarn run dist:src && yarn run dist:cjs && yarn run dist:esm && yarn run dist:types && yarn run dist:pkg", + "dist:cjs": "tsc --project ./dist/ --module commonjs --outDir ./dist/cjs/ --sourceMap", + "dist:esm": "tsc --project ./dist/ --module es2020 --outDir ./dist/esm/ --sourceMap", + "dist:pkg": "node ./scripts/package.js", + "dist:prep": "copyfiles --error tsconfig.json ./dist/", + "dist:src": "copyfiles --error --up 1 \"./src/**/*\" ./dist/src/", + "dist:types": "tsc --project ./dist/ --emitDeclarationOnly --declaration --declarationDir ./dist/types/", + "lint": "eslint src/**/*.ts src/*.ts tests/**/*.ts tests/*.ts", + "prepare": "yarn build", + "publish-alpha": "yarn publish --tag alpha", + "publish-stable": "yarn publish --tag latest", + "test": "jest", + "test:watch": "jest --watch" + }, + "exports": { + ".": { + "import": "./dist/esm/index.js", + "require": "./dist/cjs/index.js", + "types": "./dist/types/index.d.ts" + }, + "./*.js": { + "import": "./dist/esm/*.js", + "require": "./dist/cjs/*.js", + "types": "./dist/types/*.d.ts" + }, + "./*": { + "import": "./dist/esm/*.js", + "require": "./dist/cjs/*.js", + "types": "./dist/types/*.d.ts" + } + }, + "typesVersions": { + "*": { + "index.d.ts": [ + "dist/types/index.d.ts" + ], + "*": [ + "dist/types/*" + ] + } + }, + "files": [ + "dist", + "docs", + "CHANGELOG.md", + "LICENSE", + "README.md" + ], + "devDependencies": { + "@types/jest": "^27.4.1", + "@types/node": "^17.0.23", + "@typescript-eslint/eslint-plugin": "^5.18.0", + "@typescript-eslint/parser": "^5.18.0", + "copyfiles": "^2.4.1", + "eslint": "^8.12.0", + "fast-check": "^2.24.0", + "jest": "^27.5.1", + "rimraf": "^3.0.2", + "ts-jest": "^27.1.4", + "typescript": "^4.6.3", + "yarn": "^1.22.18" + } +} diff --git a/src/attenuation.ts b/packages/core/src/attenuation.ts similarity index 100% rename from src/attenuation.ts rename to packages/core/src/attenuation.ts diff --git a/packages/core/src/builder.ts b/packages/core/src/builder.ts new file mode 100644 index 0000000..99dcc02 --- /dev/null +++ b/packages/core/src/builder.ts @@ -0,0 +1,288 @@ +import * as token from "./token.js" +import * as util from "./util.js" + +import { Keypair, Fact, UcanPayload, isKeypair, Ucan, Didable } from "./types.js" +import { Capability, isCapability } from "./capability/index.js" +import { capabilityCanBeDelegated, DelegationSemantics, DelegationChain } from "./attenuation.js" +import { Store } from "./store.js" + + +export interface BuildableState { + issuer: Keypair & Didable + audience: string + expiration: number +} + +function isBuildableState(obj: unknown): obj is BuildableState { + return util.isRecord(obj) + && util.hasProp(obj, "issuer") && isKeypair(obj.issuer) + && util.hasProp(obj, "audience") && typeof obj.audience === "string" + && util.hasProp(obj, "expiration") && typeof obj.expiration === "number" +} + +interface DefaultableState { + capabilities: Capability[] + facts: Fact[] + proofs: Ucan[] + addNonce: boolean + notBefore?: number +} + +// the state neccessary for being able to lookup fitting capabilities in the UCAN store +export interface CapabilityLookupCapableState { + issuer: Keypair + expiration: number +} + +function isCapabilityLookupCapableState(obj: unknown): obj is CapabilityLookupCapableState { + return util.isRecord(obj) + && util.hasProp(obj, "issuer") && isKeypair(obj.issuer) + && util.hasProp(obj, "expiration") && typeof obj.expiration === "number" +} + +/** + * A builder API for UCANs. + * + * Supports grabbing UCANs from a UCAN `Store` for proofs (see `delegateCapability`). + * + * Example usage: + * + * ```ts + * const ucan = await Builder.create() + * .issuedBy(aliceKeypair) + * .toAudience(bobDID) + * .withLifetimeInSeconds(30) + * .claimCapability({ email: "my@email.com", cap: "SEND" }) + * .delegateCapability(emailSemantics, { email: "my-friends@email.com", cap: "SEND" }, proof) + * .build() + * ``` + */ +export class Builder> { + + private state: State // portion of the state that's required to be set before building + private defaultable: DefaultableState // portion of the state that has sensible defaults + + private constructor(state: State, defaultable: DefaultableState) { + this.state = state + this.defaultable = defaultable + } + + /** + * Create an empty builder. + * Before finalising the builder, you need to at least call + * - `issuedBy` + * - `toAudience` and + * - `withLifetimeInSeconds` or `withExpiration`. + * To finalise the builder, call its `build` or `buildPayload` method. + */ + static create(): Builder> { + return new Builder({}, { capabilities: [], facts: [], proofs: [], addNonce: false }) + } + + /** + * @param issuer The issuer as a DID string ("did:key:..."). + * + * The UCAN must be signed with the private key of the issuer to be valid. + */ + issuedBy(issuer: Keypair): Builder { + if (!isKeypair(issuer)) { + throw new TypeError(`Expected a Keypair, but got ${issuer}`) + } + return new Builder({ ...this.state, issuer }, this.defaultable) + } + + /** + * @param audience The audience as a DID string ("did:key:..."). + * + * This is the identity this UCAN transfers rights to. + * It could e.g. be the DID of a service you're posting this UCAN as a JWT to, + * or it could be the DID of something that'll use this UCAN as a proof to + * continue the UCAN chain as an issuer. + */ + toAudience(audience: string): Builder { + if (typeof audience !== "string") { + throw new TypeError(`Expected audience DID as string, but got ${audience}`) + } + return new Builder({ ...this.state, audience }, this.defaultable) + } + + /** + * @param seconds The number of seconds from the calltime of this function + * to set the expiry timestamp to. + */ + withLifetimeInSeconds(seconds: number): Builder { + if (typeof seconds !== "number") { + throw new TypeError(`Expected seconds as number, but got ${seconds}`) + } + if (!isFinite(seconds) || seconds <= 0) { + throw new TypeError(`Expected seconds to be a positive number, but got ${seconds}`) + } + return this.withExpiration(Math.floor(Date.now() / 1000) + seconds) + } + + /** + * @param expiration The UTCTime timestamp (in seconds) for when the UCAN should expire. + */ + withExpiration(expiration: number): Builder { + if (typeof expiration !== "number" || !isFinite(expiration)) { + throw new TypeError(`Expected expiration as number, but got ${expiration}`) + } + if (this.defaultable.notBefore != null && expiration < this.defaultable.notBefore) { + throw new Error(`Can't set expiration to ${expiration} which is before 'notBefore': ${this.defaultable.notBefore}`) + } + return new Builder({ ...this.state, expiration }, this.defaultable) + } + + /** + * @param notBeforeTimestamp The UTCTime timestamp (in seconds) of when the UCAN becomes active. + */ + withNotBefore(notBeforeTimestamp: number): Builder { + if (typeof notBeforeTimestamp !== "number" || !isFinite(notBeforeTimestamp)) { + throw new TypeError(`Expected notBeforeTimestamp as number, but got ${notBeforeTimestamp}`) + } + if (util.hasProp(this.state, "expiration") && typeof this.state.expiration === "number" && this.state.expiration < notBeforeTimestamp) { + throw new Error(`Can't set 'notBefore' to ${notBeforeTimestamp} which is after expiration: ${this.state.expiration}`) + } + return new Builder(this.state, { ...this.defaultable, notBefore: notBeforeTimestamp }) + } + + /** + * @param fact Any fact or proof of knowledge in this UCAN as a record. + * @param facts Arbitrary more facts or proofs of knowledge. + */ + withFact(fact: Fact): Builder + withFact(fact: Fact, ...facts: Fact[]): Builder + withFact(fact: Fact, ...facts: Fact[]): Builder { + if (!util.isRecord(fact) || facts.some(fct => !util.isRecord(fct))) { + throw new TypeError(`Expected fact(s) to be a record, but got ${fact}`) + } + return new Builder(this.state, { + ...this.defaultable, + facts: [ ...this.defaultable.facts, fact, ...facts ] + }) + } + + /** + * Will ensure that the built UCAN includes a number used once. + */ + withNonce(): Builder { + return new Builder(this.state, { ...this.defaultable, addNonce: true }) + } + + /** + * Claim capabilities 'by parenthood'. + */ + claimCapability(capability: Capability): Builder + claimCapability(capability: Capability, ...capabilities: Capability[]): Builder + claimCapability(capability: Capability, ...capabilities: Capability[]): Builder { + if (!isCapability(capability)) { + throw new TypeError(`Expected capability, but got ${JSON.stringify(capability, null, " ")}`) + } + return new Builder(this.state, { + ...this.defaultable, + capabilities: [ ...this.defaultable.capabilities, capability, ...capabilities ] + }) + } + + /** + * Delegate capabilities from a given proof to the audience of the UCAN you're building. + * + * @param semantics The rules for which delegations of capabilities are allowed. + * @param requiredCapability The capability you want to delegate. + * + * Then, one of + * @param proof The proof chain that grants the issuer of this UCAN at least the capabilities you want to delegate, or + * @param store The UCAN store in which to try to find a UCAN granting you enough capabilities to delegate given capabilities. + * + * @throws If given store can't provide a UCAN for delegating given capability + * @throws If given proof can't be used to delegate given capability + * @throws If the builder hasn't set an issuer and expiration yet + */ + delegateCapability(requiredCapability: Capability, store: Store): State extends CapabilityLookupCapableState ? Builder : never + delegateCapability(requiredCapability: Capability, proof: DelegationChain, semantics: DelegationSemantics): State extends CapabilityLookupCapableState ? Builder : never + delegateCapability(requiredCapability: Capability, storeOrProof: Store | DelegationChain, semantics?: DelegationSemantics): Builder { + if (!isCapability(requiredCapability)) { + throw new TypeError(`Expected 'requiredCapability' as a second argument, but got ${requiredCapability}`) + } + if (!isCapabilityLookupCapableState(this.state)) { + throw new Error(`Can't delegate capabilities without having these paramenters set in the builder: issuer and expiration.`) + } + + function isProof(proof: Store | DelegationChain): proof is DelegationChain { + return util.hasProp(proof, "capability") || util.hasProp(proof, "ownershipDID") + } + + if (isProof(storeOrProof)) { + if (semantics == null) { + throw new TypeError(`Expected 'semantics' as third argument if a 'proof' DelegationChain was passed as second.`) + } + const proof: DelegationChain = storeOrProof + const ucan = proof.ucan + if (!capabilityCanBeDelegated(semantics, requiredCapability, proof)) { + throw new Error(`Can't add capability to UCAN: Given proof doesn't give required rights to delegate.`) + } + return new Builder(this.state, { + ...this.defaultable, + capabilities: [ ...this.defaultable.capabilities, requiredCapability ], + proofs: this.defaultable.proofs.find(p => token.encode(p) === token.encode(ucan)) == null + ? [ ...this.defaultable.proofs, ucan ] + : this.defaultable.proofs + }) + } else { + const store: Store = storeOrProof + const issuer = this.state.issuer.did() + // we look up a proof that has our issuer as an audience + const result = util.first(store.findWithCapability(issuer, requiredCapability, issuer)) + if (result != null) { + const ucan = result.ucan + const ucanEncoded = token.encode(ucan) + return new Builder(this.state, { + ...this.defaultable, + capabilities: [ ...this.defaultable.capabilities, requiredCapability ], + proofs: this.defaultable.proofs.find(proof => token.encode(proof) === ucanEncoded) == null + ? [ ...this.defaultable.proofs, ucan ] + : this.defaultable.proofs + }) + } else { + throw new Error(`Couldn't add capability to UCAN. Couldn't find anything providing this capability in given store.`) + } + } + } + + /** + * Build the UCAN body. This can be used if you want to sign the UCAN yourself afterwards. + */ + buildPayload(): State extends BuildableState ? UcanPayload : never + buildPayload(): UcanPayload { + if (!isBuildableState(this.state)) { + throw new Error(`Builder is missing one of the required properties before it can be built: issuer, audience and expiration.`) + } + return token.buildPayload({ + issuer: this.state.issuer.did(), + audience: this.state.audience, + + expiration: this.state.expiration, + notBefore: this.defaultable.notBefore, + addNonce: this.defaultable.addNonce, + + capabilities: this.defaultable.capabilities, + facts: this.defaultable.facts, + proofs: this.defaultable.proofs.map(proof => token.encode(proof)), + }) + } + + /** + * Finalize: Build and sign the UCAN. + * + * @throws If the builder hasn't yet been set an issuer, audience and expiration. + */ + async build(): Promise + async build(): Promise { + if (!isBuildableState(this.state)) { + throw new Error(`Builder is missing one of the required properties before it can be built: issuer, audience and expiration.`) + } + const payload = this.buildPayload() + return await token.signWithKeypair(payload, this.state.issuer) + } + +} diff --git a/src/capability/ability.ts b/packages/core/src/capability/ability.ts similarity index 100% rename from src/capability/ability.ts rename to packages/core/src/capability/ability.ts diff --git a/src/capability/index.ts b/packages/core/src/capability/index.ts similarity index 100% rename from src/capability/index.ts rename to packages/core/src/capability/index.ts diff --git a/src/capability/resource-pointer.ts b/packages/core/src/capability/resource-pointer.ts similarity index 100% rename from src/capability/resource-pointer.ts rename to packages/core/src/capability/resource-pointer.ts diff --git a/src/capability/super-user.ts b/packages/core/src/capability/super-user.ts similarity index 100% rename from src/capability/super-user.ts rename to packages/core/src/capability/super-user.ts diff --git a/src/compatibility.ts b/packages/core/src/compatibility.ts similarity index 100% rename from src/compatibility.ts rename to packages/core/src/compatibility.ts diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts new file mode 100644 index 0000000..ffd4721 --- /dev/null +++ b/packages/core/src/index.ts @@ -0,0 +1,10 @@ +export * from "./attenuation.js" +export * from "./builder.js" +export * from "./store.js" +export * from "./token.js" +export * from "./types.js" +export * from "./verify.js" + +export * as capability from "./capability/index.js" + +export { Capability, EncodedCapability, isCapability } from "./capability/index.js" \ No newline at end of file diff --git a/src/semver.ts b/packages/core/src/semver.ts similarity index 100% rename from src/semver.ts rename to packages/core/src/semver.ts diff --git a/src/store.ts b/packages/core/src/store.ts similarity index 100% rename from src/store.ts rename to packages/core/src/store.ts diff --git a/packages/core/src/token.ts b/packages/core/src/token.ts new file mode 100644 index 0000000..ef818d1 --- /dev/null +++ b/packages/core/src/token.ts @@ -0,0 +1,457 @@ +import * as uint8arrays from "uint8arrays" // @IMPORT + +import * as semver from "./semver.js" +import * as capability from "./capability/index.js" +import * as util from "./util.js" + +import { Capability, isCapability, isEncodedCapability } from "./capability/index.js" +import { Fact, KeyType, Keypair, Didable } from "./types.js" +import { Ucan, UcanHeader, UcanParts, UcanPayload } from "./types.js" +import { handleCompatibility } from "./compatibility.js" + + +// CONSTANTS + + +const TYPE = "JWT" +const VERSION = { major: 0, minor: 8, patch: 1 } + + + +// COMPOSING + + +/** + * Create a UCAN, User Controlled Authorization Networks, JWT. + * + * ### Header + * + * `alg`, Algorithm, the type of signature. + * `typ`, Type, the type of this data structure, JWT. + * `ucv`, UCAN version. + * + * ### Payload + * + * `att`, Attenuation, a list of resources and capabilities that the ucan grants. + * `aud`, Audience, the ID of who it's intended for. + * `exp`, Expiry, unix timestamp of when the jwt is no longer valid. + * `fct`, Facts, an array of extra facts or information to attach to the jwt. + * `iss`, Issuer, the ID of who sent this. + * `nbf`, Not Before, unix timestamp of when the jwt becomes valid. + * `nnc`, Nonce, a randomly generated string, used to ensure the uniqueness of the jwt. + * `prf`, Proofs, nested tokens with equal or greater privileges. + * + */ +export async function build(params: { + // from/to + issuer: Keypair & Didable + audience: string + + // capabilities + capabilities?: Array + + // time bounds + lifetimeInSeconds?: number // expiration overrides lifetimeInSeconds + expiration?: number + notBefore?: number + + // proofs / other info + facts?: Array + proofs?: Array + addNonce?: boolean +} +): Promise { + const keypair = params.issuer + const payload = buildPayload({ ...params, issuer: keypair.did() }) + return signWithKeypair(payload, keypair) +} + +/** + * Construct the payload for a UCAN. + */ +export function buildPayload(params: { + // from/to + issuer: string + audience: string + + // capabilities + capabilities?: Array + + // time bounds + lifetimeInSeconds?: number // expiration overrides lifetimeInSeconds + expiration?: number + notBefore?: number + + // proofs / other info + facts?: Array + proofs?: Array + addNonce?: boolean +}): UcanPayload { + const { + issuer, + audience, + capabilities = [], + lifetimeInSeconds = 30, + expiration, + notBefore, + facts, + proofs = [], + addNonce = false + } = params + + // Validate + if (!issuer.startsWith("did:")) throw new Error("The issuer must be a DID") + if (!audience.startsWith("did:")) throw new Error("The audience must be a DID") + + // Timestamps + const currentTimeInSeconds = Math.floor(Date.now() / 1000) + const exp = expiration || (currentTimeInSeconds + lifetimeInSeconds) + + // 📦 + return { + aud: audience, + att: capabilities, + exp, + fct: facts, + iss: issuer, + nbf: notBefore, + nnc: addNonce ? util.generateNonce() : undefined, + prf: proofs, + } +} + +/** + * Encloses a UCAN payload as to form a finalised UCAN. + */ +export async function sign( + payload: UcanPayload, + keyType: KeyType, + signFn: (data: Uint8Array) => Promise +): Promise { + const header: UcanHeader = { + alg: jwtAlgorithm(keyType), + typ: TYPE, + ucv: VERSION, + } + + // @TODO do we need to perform this?? + // Issuer key type must match UCAN algorithm + // if (signPlugins.checkJwtAlg(payload.iss, jwtAlgorithm(keyType))) { + // throw new Error("The issuer's key type must match the given key type.") + // } + + // Encode parts + const encodedHeader = encodeHeader(header) + const encodedPayload = encodePayload(payload) + + // Sign + const signedData = `${encodedHeader}.${encodedPayload}` + const toSign = uint8arrays.fromString(signedData, "utf8") + const sig = await signFn(toSign) + + // 📦 + // we freeze the object to make it more unlikely + // for signedData & header/payload to get out of sync + return Object.freeze({ + header, + payload, + signedData, + signature: uint8arrays.toString(sig, "base64url") + }) +} + +/** + * `sign` with a `Keypair`. + */ +export async function signWithKeypair( + payload: UcanPayload, + keypair: Keypair, +): Promise { + return sign( + payload, + keypair.keyType, + data => keypair.sign(data), + ) +} + + + +// ENCODING + + +/** + * Encode a UCAN. + * + * @param ucan The UCAN to encode + */ +export function encode(ucan: Ucan): string { + return `${ucan.signedData}.${ucan.signature}` +} + +/** + * Encode the header of a UCAN. + * + * @param header The UcanHeader to encode + * @returns The header of a UCAN encoded as url-safe base64 JSON + */ +export function encodeHeader(header: UcanHeader): string { + const headerFormatted = { + ...header, + ucv: semver.format(header.ucv) + } + return uint8arrays.toString( + uint8arrays.fromString(JSON.stringify(headerFormatted), "utf8"), + "base64url" + ) +} + +/** + * Encode the payload of a UCAN. + * + * NOTE: This will encode capabilities as well, so that it matches the UCAN spec. + * In other words, `{ with: { scheme, hierPart }, can: { namespace, segments } }` + * becomes `{ with: "${scheme}:${hierPart}", can: "${namespace}/${segment}" }` + * + * @param payload The UcanPayload to encode + */ +export function encodePayload(payload: UcanPayload): string { + const payloadWithEncodedCaps = { + ...payload, + att: payload.att.map(capability.encode) + } + + return uint8arrays.toString( + uint8arrays.fromString(JSON.stringify(payloadWithEncodedCaps), "utf8"), + "base64url" + ) +} + +/** + * Parse an encoded UCAN. + * + * @param encodedUcan The encoded UCAN. + */ +export function parse(encodedUcan: string): UcanParts { + const [ encodedHeader, encodedPayload, signature ] = encodedUcan.split(".") + + if (encodedHeader == null || encodedPayload == null || signature == null) { + throw new Error(`Can't parse UCAN: ${encodedUcan}: Expected JWT format: 3 dot-separated base64url-encoded values.`) + } + + // Header + let headerJson: string + let headerObject: unknown + + try { + headerJson = uint8arrays.toString( + uint8arrays.fromString(encodedHeader, "base64url"), + "utf8" + ) + } catch { + throw new Error(`Can't parse UCAN header: ${encodedHeader}: Can't parse as base64url.`) + } + + try { + headerObject = JSON.parse(headerJson) + } catch { + throw new Error(`Can't parse UCAN header: ${encodedHeader}: Can't parse encoded JSON inside.`) + } + + // Payload + let payloadJson: string + let payloadObject: unknown + + try { + payloadJson = uint8arrays.toString( + uint8arrays.fromString(encodedPayload, "base64url"), + "utf8" + ) + } catch { + throw new Error(`Can't parse UCAN payload: ${encodedPayload}: Can't parse as base64url.`) + } + + try { + payloadObject = JSON.parse(payloadJson) + } catch { + throw new Error(`Can't parse UCAN payload: ${encodedPayload}: Can't parse encoded JSON inside.`) + } + + // Compatibility layer + const { header, payload } = handleCompatibility(headerObject, payloadObject) + + // Ensure proper types/structure + const parsedAttenuations = payload.att.reduce((acc: Capability[], cap: unknown): Capability[] => { + return isEncodedCapability(cap) + ? [ ...acc, capability.parse(cap) ] + : isCapability(cap) ? [ ...acc, cap ] : acc + }, []) + + // Fin + return { + header: header, + payload: { ...payload, att: parsedAttenuations } + } +} + + + +// VALIDATION + + +/** + * Validation options + */ +export interface ValidateOptions { + checkIssuer: (did: string, jwtAlg: string) => boolean + checkSignature: (did: string, data: Uint8Array, sig: Uint8Array) => Promise + checkIsExpired?: boolean + checkIsTooEarly?: boolean +} + +/** + * Parse & Validate **one layer** of a UCAN. + * This doesn't validate attenutations and doesn't validate the whole UCAN chain. + * + * By default, this will check the signature and time bounds. + * + * @param encodedUcan the JWT-encoded UCAN to validate + * @param options an optional parameter to configure turning off some validation options + * @returns the parsed & validated UCAN (one layer) + * @throws Error if the UCAN is invalid + */ +export async function validate(encodedUcan: string, opts?: Partial): Promise { + const { checkIssuer, checkSignature, checkIsExpired = true, checkIsTooEarly = true } = opts ?? {} + + const { header, payload } = parse(encodedUcan) + const [ encodedHeader, encodedPayload, signature ] = encodedUcan.split(".") + + if (checkIssuer) { + if (checkIssuer(payload.iss, header.alg)) { + throw new Error(`Invalid UCAN: ${encodedUcan}: Issuer key type does not match UCAN's \`alg\` property.`) + } + } + + if (checkSignature) { + const sigBytes = uint8arrays.fromString(signature, "utf8") + const data = uint8arrays.fromString(`${encodedHeader}.${encodedPayload}`, "utf8") + const valid = await checkSignature(payload.iss, data, sigBytes) + if (!valid) { + throw new Error(`Invalid UCAN: ${encodedUcan}: Signature invalid.`) + } + } + + const signedData = `${encodedHeader}.${encodedPayload}` + const ucan: Ucan = { header, payload, signedData, signature } + + if (checkIsExpired && isExpired(ucan)) { + throw new Error(`Invalid UCAN: ${encodedUcan}: Expired.`) + } + + if (checkIsTooEarly && isTooEarly(ucan)) { + throw new Error(`Invalid UCAN: ${encodedUcan}: Not active yet (too early).`) + } + + return ucan +} + +/** + * Proof validation options. + */ +export interface ValidateProofsOptions extends ValidateOptions { + /** + * Whether to check if the ucan's issuer matches its proofs audiences. + */ + checkAddressing?: boolean + /** + * Whether to check if a ucan's time bounds are a subset of its proofs time bounds. + */ + checkTimeBoundsSubset?: boolean + /** + * Whether to check if a ucan's version is bigger or equal to its proofs version. + */ + checkVersionMonotonic?: boolean +} + +/** + * Iterates over all proofs and parses & validates them at the same time. + * + * If there's an audience/issuer mismatch, the iterated item will contain an `Error`. + * Otherwise the iterated out will contain a `Ucan`. + * + * @param ucan a parsed UCAN + * @param options optional ValidateOptions to use for validating each proof + * @return an async iterator of the given ucan's proofs parsed & validated, or an `Error` + * for each proof that couldn't be validated or parsed. + */ +export async function* validateProofs( + ucan: Ucan, + opts?: Partial +): AsyncIterable { + const { checkAddressing = true, checkTimeBoundsSubset = true, checkVersionMonotonic = true } = opts || {} + + for (const prf of ucan.payload.prf) { + try { + const proof = await validate(prf, opts) + + if (checkAddressing && ucan.payload.iss !== proof.payload.aud) { + throw new Error(`Invalid Proof: Issuer ${ucan.payload.iss} doesn't match parent's audience ${proof.payload.aud}`) + } + + if (checkTimeBoundsSubset && proof.payload.nbf != null && ucan.payload.exp > proof.payload.nbf) { + throw new Error(`Invalid Proof: 'Not before' (${proof.payload.nbf}) is after parent's expiration (${ucan.payload.exp})`) + } + + if (checkTimeBoundsSubset && ucan.payload.nbf != null && ucan.payload.nbf > proof.payload.exp) { + throw new Error(`Invalid Proof: Expiration (${proof.payload.exp}) is before parent's 'not before' (${ucan.payload.nbf})`) + } + + if (checkVersionMonotonic && semver.lt(ucan.header.ucv, proof.header.ucv)) { + throw new Error(`Invalid Proof: Version (${proof.header.ucv}) is higher than parent's version (${ucan.header.ucv})`) + } + + yield proof + } catch (e) { + if (e instanceof Error) { + yield e + } else { + yield new Error(`Error when trying to parse UCAN proof: ${e}`) + } + } + } +} + +/** + * Check if a UCAN is expired. + * + * @param ucan The UCAN to validate + */ +export function isExpired(ucan: Ucan): boolean { + return ucan.payload.exp <= Math.floor(Date.now() / 1000) +} + +/** + * Check if a UCAN is not active yet. + * + * @param ucan The UCAN to validate + */ +export const isTooEarly = (ucan: Ucan): boolean => { + if (ucan.payload.nbf == null) return false + return ucan.payload.nbf > Math.floor(Date.now() / 1000) +} + + + +// ㊙️ + + +/** + * JWT algorithm to be used in a JWT header. + */ +function jwtAlgorithm(keyType: KeyType): string { + switch (keyType) { + case "bls12-381": throw new Error(`Unknown KeyType "${keyType}"`) + case "ed25519": return "EdDSA" + case "rsa": return "RS256" + default: throw new Error(`Unknown KeyType "${keyType}"`) + } +} diff --git a/src/types.ts b/packages/core/src/types.ts similarity index 97% rename from src/types.ts rename to packages/core/src/types.ts index e0bd5bf..f59a9cd 100644 --- a/src/types.ts +++ b/packages/core/src/types.ts @@ -1,6 +1,6 @@ import * as semver from "./semver.js" import { SemVer } from "./semver.js" -import { SupportedEncodings } from "uint8arrays/util/bases.js" +import { SupportedEncodings } from "uint8arrays/util/bases.js" // @IMPORT import { Capability, isCapability, isEncodedCapability } from "./capability/index.js" import * as util from "./util.js" diff --git a/src/util.ts b/packages/core/src/util.ts similarity index 100% rename from src/util.ts rename to packages/core/src/util.ts diff --git a/packages/core/src/verify.ts b/packages/core/src/verify.ts new file mode 100644 index 0000000..a8b5c0e --- /dev/null +++ b/packages/core/src/verify.ts @@ -0,0 +1,141 @@ +import * as token from "./token.js" +import { capabilityCanBeDelegated, DelegationSemantics, DelegationChain, delegationChains, equalCanDelegate, rootIssuer } from "./attenuation.js" +import { Capability, isCapability } from "./capability/index.js" +import { Fact, Ucan } from "./types.js" + + +export type Result + = { ok: true; value: Ok } + | { ok: false; error: Err } + +const ok: (k: T) => Result = k => ({ ok: true, value: k }) +const err: (e: E) => Result = e => ({ ok: false, error: e }) + + +export interface VerifyOptions { + /** + * the DID of the callee of this function. The expected audience of the outermost level of the UCAN. + * NOTE: This DID should not be hardcoded in production calls to this function. + */ + audience: string + /** + * a non-empty list of capabilities required for this UCAN invocation. The root issuer and capability + * should be derived from something like your HTTP request parameters. They identify the resource + * that's access-controlled. + */ + requiredCapabilities: { capability: Capability; rootIssuer: string }[] + // @TODO add comment & argument type checks (in code) + checkIssuer: (did: string, jwtAlg: string) => boolean + checkSignature: (did: string, data: Uint8Array, sig: Uint8Array) => Promise + /** + * an optional record of functions that specify what the rules for delegating capabilities are. + * If not provided, the default semantics will be `equalCanDelegate`. + */ + semantics?: DelegationSemantics + /** + * an async predicate on UCANs to figure out whether they've been revoked or not. + * Usually that means checking whether the hash of the UCAN is in a list of revoked UCANs. + * If not provided, it will assume no UCAN to be revoked. + */ + isRevoked?: (ucan: Ucan) => Promise + /** + * an optional function that's given the list of facts in the root UCAN and returns a boolean indicating + * whether the facts include everything you expect for the UCAN invocation to check. + * By default this will ignore all facts in the UCAN and just return true. + */ + checkFacts?: (facts: Fact[]) => boolean +} + + +/** + * Verify a UCAN for an invocation. + * + * @param ucan a UCAN to verify for invocation in JWT format. (starts with 'eyJ...' and has two '.' in it) + * + * @param options required and optional verification options see {@link VerifyOptions} + * + * @throws TypeError if the passed arguments don't match what is expected + */ +export async function verify(ucan: string, options: VerifyOptions): Promise> { + const { audience, requiredCapabilities } = options + const semantics = options.semantics ?? equalCanDelegate + const isRevoked = options.isRevoked ?? (async () => false) + const checkFacts = options.checkFacts ?? (() => true) + // type-check arguments + if (typeof ucan !== "string") { + throw new TypeError(`Expected an encoded UCAN string as first argument, but got ${ucan}`) + } + if (typeof audience !== "string" || !audience.startsWith("did:")) { + throw new TypeError(`Expected a DID string as second argument, but got ${audience}`) + } + if (typeof isRevoked !== "function") { + throw new TypeError(`Expected a function returning a promise as third argument, but got ${isRevoked}`) + } + if (!Array.isArray(requiredCapabilities)) { + throw new TypeError(`Expected an array as fourth argument, but got ${requiredCapabilities}`) + } + if (requiredCapabilities.length < 1) { + throw new TypeError(`Expected a non-empty list of required capabilities as 4th argument.`) + } + if (requiredCapabilities.some(req => !isCapability(req.capability) || typeof req.rootIssuer !== "string" || !req.rootIssuer.startsWith("did:"))) { + throw new TypeError(`Expected an array of records of capabilities and rootIssuers as DID strings as 4th argument, but got ${requiredCapabilities}`) + } + if (typeof semantics.canDelegateResource !== "function" || typeof semantics.canDelegateAbility !== "function") { + throw new TypeError(`Expected a record with two functions 'canDelegateResource' and 'canDelegateAbility' as 5th argument, but got ${semantics}`) + } + if (typeof checkFacts !== "function") { + throw new TypeError(`Expected a function as 6th argument, but got ${checkFacts}`) + } + + try { + // Verify the UCAN + const decoded = await token.validate(ucan, { checkIssuer: options.checkIssuer, checkSignature: options.checkSignature }) + + // Check that it's addressed to us + if (decoded.payload.aud !== audience) { + return err([ new Error(`Invalid UCAN: Expected audience to be ${audience}, but it's ${decoded.payload.aud}`) ]) + } + + const errors: Error[] = [] + const remaining = new Set(requiredCapabilities) + const proven: Verification[] = [] + + // Check that all required capabilities are verified + loop: for await (const delegationChain of delegationChains(semantics, decoded, isRevoked)) { + if (delegationChain instanceof Error) { + errors.push(delegationChain) + continue + } + + // Try to look for capabilities from given delegation chain + for (const expected of remaining) { + if ( + capabilityCanBeDelegated(semantics, expected.capability, delegationChain) + && rootIssuer(delegationChain) === expected.rootIssuer + ) { + remaining.delete(expected) + proven.push({ + ...expected, + proof: delegationChain + }) + } + } + + // If we've already verified all, we don't need to keep looking + if (remaining.size === 0) { + break loop + } + } + + return remaining.size > 0 ? err(errors) : ok(proven) + + } catch (e) { + return err([ e instanceof Error ? e : new Error(`Unknown error during UCAN verification: ${e}`) ]) + } +} + +export interface Verification { + capability: Capability + rootIssuer: string + proof: DelegationChain +} diff --git a/tests/attenuation.test.ts b/packages/core/tests/attenuation.test.ts similarity index 100% rename from tests/attenuation.test.ts rename to packages/core/tests/attenuation.test.ts diff --git a/tests/builder.test.ts b/packages/core/tests/builder.test.ts similarity index 100% rename from tests/builder.test.ts rename to packages/core/tests/builder.test.ts diff --git a/tests/capability.test.ts b/packages/core/tests/capability.test.ts similarity index 100% rename from tests/capability.test.ts rename to packages/core/tests/capability.test.ts diff --git a/tests/capability/email.ts b/packages/core/tests/capability/email.ts similarity index 100% rename from tests/capability/email.ts rename to packages/core/tests/capability/email.ts diff --git a/tests/capability/wnfs.test.ts b/packages/core/tests/capability/wnfs.test.ts similarity index 100% rename from tests/capability/wnfs.test.ts rename to packages/core/tests/capability/wnfs.test.ts diff --git a/tests/capability/wnfs.ts b/packages/core/tests/capability/wnfs.ts similarity index 100% rename from tests/capability/wnfs.ts rename to packages/core/tests/capability/wnfs.ts diff --git a/tests/compatibility.test.ts b/packages/core/tests/compatibility.test.ts similarity index 100% rename from tests/compatibility.test.ts rename to packages/core/tests/compatibility.test.ts diff --git a/tests/did-key.ts b/packages/core/tests/did-key.ts similarity index 100% rename from tests/did-key.ts rename to packages/core/tests/did-key.ts diff --git a/tests/did.test.ts b/packages/core/tests/did.test.ts similarity index 100% rename from tests/did.test.ts rename to packages/core/tests/did.test.ts diff --git a/tests/ecdsa.test.ts b/packages/core/tests/ecdsa.test.ts similarity index 100% rename from tests/ecdsa.test.ts rename to packages/core/tests/ecdsa.test.ts diff --git a/tests/ed25519.test.ts b/packages/core/tests/ed25519.test.ts similarity index 100% rename from tests/ed25519.test.ts rename to packages/core/tests/ed25519.test.ts diff --git a/tests/fixtures.ts b/packages/core/tests/fixtures.ts similarity index 100% rename from tests/fixtures.ts rename to packages/core/tests/fixtures.ts diff --git a/tests/rsa.test.ts b/packages/core/tests/rsa.test.ts similarity index 100% rename from tests/rsa.test.ts rename to packages/core/tests/rsa.test.ts diff --git a/tests/semver.test.ts b/packages/core/tests/semver.test.ts similarity index 100% rename from tests/semver.test.ts rename to packages/core/tests/semver.test.ts diff --git a/tests/store.test.ts b/packages/core/tests/store.test.ts similarity index 100% rename from tests/store.test.ts rename to packages/core/tests/store.test.ts diff --git a/tests/token.test.ts b/packages/core/tests/token.test.ts similarity index 100% rename from tests/token.test.ts rename to packages/core/tests/token.test.ts diff --git a/tests/verify.test.ts b/packages/core/tests/verify.test.ts similarity index 100% rename from tests/verify.test.ts rename to packages/core/tests/verify.test.ts diff --git a/tsconfig.eslint.json b/packages/core/tsconfig.eslint.json similarity index 100% rename from tsconfig.eslint.json rename to packages/core/tsconfig.eslint.json diff --git a/tsconfig.json b/packages/core/tsconfig.json similarity index 100% rename from tsconfig.json rename to packages/core/tsconfig.json diff --git a/src/crypto/ecdsa.ts b/packages/default/src/crypto/ecdsa.ts similarity index 100% rename from src/crypto/ecdsa.ts rename to packages/default/src/crypto/ecdsa.ts diff --git a/src/crypto/index.ts b/packages/default/src/crypto/index.ts similarity index 100% rename from src/crypto/index.ts rename to packages/default/src/crypto/index.ts diff --git a/src/crypto/rsa.ts b/packages/default/src/crypto/rsa.ts similarity index 100% rename from src/crypto/rsa.ts rename to packages/default/src/crypto/rsa.ts diff --git a/src/did/prefix.ts b/packages/default/src/did/prefix.ts similarity index 100% rename from src/did/prefix.ts rename to packages/default/src/did/prefix.ts diff --git a/src/did/pubkey-compress.ts b/packages/default/src/did/pubkey-compress.ts similarity index 100% rename from src/did/pubkey-compress.ts rename to packages/default/src/did/pubkey-compress.ts diff --git a/src/did/transformers.ts b/packages/default/src/did/transformers.ts similarity index 100% rename from src/did/transformers.ts rename to packages/default/src/did/transformers.ts diff --git a/src/did/validation.ts b/packages/default/src/did/validation.ts similarity index 100% rename from src/did/validation.ts rename to packages/default/src/did/validation.ts diff --git a/src/keypair/base.ts b/packages/default/src/keypair/base.ts similarity index 100% rename from src/keypair/base.ts rename to packages/default/src/keypair/base.ts diff --git a/src/keypair/ecdsa.ts b/packages/default/src/keypair/ecdsa.ts similarity index 100% rename from src/keypair/ecdsa.ts rename to packages/default/src/keypair/ecdsa.ts diff --git a/src/keypair/ed25519.ts b/packages/default/src/keypair/ed25519.ts similarity index 100% rename from src/keypair/ed25519.ts rename to packages/default/src/keypair/ed25519.ts diff --git a/src/keypair/index.ts b/packages/default/src/keypair/index.ts similarity index 100% rename from src/keypair/index.ts rename to packages/default/src/keypair/index.ts diff --git a/src/keypair/rsa.ts b/packages/default/src/keypair/rsa.ts similarity index 100% rename from src/keypair/rsa.ts rename to packages/default/src/keypair/rsa.ts diff --git a/packages/ucans/jest.config.js b/packages/ucans/jest.config.js new file mode 100644 index 0000000..1a5935b --- /dev/null +++ b/packages/ucans/jest.config.js @@ -0,0 +1,21 @@ +module.exports = { // eslint-disable-line + transform: { + ".(ts|tsx)": "ts-jest" + }, + testEnvironment: "node", + testRegex: "(/__tests__/.*|\\.(test|spec))\\.(ts|tsx|js)$", + moduleFileExtensions: [ + "ts", + "tsx", + "js" + ], + preset: "ts-jest/presets/default-esm", + globals: { + "ts-jest": { + useESM: true, + }, + }, + moduleNameMapper: { + "^(\\.{1,2}/.*)\\.js$": "$1", + }, +} diff --git a/packages/ucans/package.json b/packages/ucans/package.json new file mode 100644 index 0000000..aa5c3dd --- /dev/null +++ b/packages/ucans/package.json @@ -0,0 +1,87 @@ +{ + "name": "@ucans/ucans", + "version": "0.9.1", + "description": "Typescript implementation of UCANs", + "author": "Daniel Holmgren ", + "repository": { + "type": "git", + "url": "https://github.com/fission-suite/ucan" + }, + "homepage": "https://guide.fission.codes", + "license": "Apache-2.0", + "engines": { + "node": ">=15" + }, + "scripts": { + "prebuild": "rimraf dist", + "build": "yarn run dist", + "dev": "tsc --watch --module commonjs --outDir ./dist/cjs/ --sourceMap", + "dist": "yarn run dist:prep && yarn run dist:src && yarn run dist:cjs && yarn run dist:esm && yarn run dist:types && yarn run dist:pkg", + "dist:cjs": "tsc --project ./dist/ --module commonjs --outDir ./dist/cjs/ --sourceMap", + "dist:esm": "tsc --project ./dist/ --module es2020 --outDir ./dist/esm/ --sourceMap", + "dist:pkg": "node ./scripts/package.js", + "dist:prep": "copyfiles --error tsconfig.json ./dist/", + "dist:src": "copyfiles --error --up 1 \"./src/**/*\" ./dist/src/", + "dist:types": "tsc --project ./dist/ --emitDeclarationOnly --declaration --declarationDir ./dist/types/", + "lint": "eslint src/**/*.ts src/*.ts tests/**/*.ts tests/*.ts", + "prepare": "yarn build", + "publish-alpha": "yarn publish --tag alpha", + "publish-stable": "yarn publish --tag latest", + "test": "jest", + "test:watch": "jest --watch" + }, + "exports": { + ".": { + "import": "./dist/esm/index.js", + "require": "./dist/cjs/index.js", + "types": "./dist/types/index.d.ts" + }, + "./*.js": { + "import": "./dist/esm/*.js", + "require": "./dist/cjs/*.js", + "types": "./dist/types/*.d.ts" + }, + "./*": { + "import": "./dist/esm/*.js", + "require": "./dist/cjs/*.js", + "types": "./dist/types/*.d.ts" + } + }, + "typesVersions": { + "*": { + "index.d.ts": [ + "dist/types/index.d.ts" + ], + "*": [ + "dist/types/*" + ] + } + }, + "files": [ + "dist", + "docs", + "CHANGELOG.md", + "LICENSE", + "README.md" + ], + "dependencies": { + "@stablelib/ed25519": "^1.0.2", + "big-integer": "^1.6.51", + "one-webcrypto": "^1.0.3", + "uint8arrays": "^3.0.0" + }, + "devDependencies": { + "@types/jest": "^27.4.1", + "@types/node": "^17.0.23", + "@typescript-eslint/eslint-plugin": "^5.18.0", + "@typescript-eslint/parser": "^5.18.0", + "copyfiles": "^2.4.1", + "eslint": "^8.12.0", + "fast-check": "^2.24.0", + "jest": "^27.5.1", + "rimraf": "^3.0.2", + "ts-jest": "^27.1.4", + "typescript": "^4.6.3", + "yarn": "^1.22.18" + } +} diff --git a/packages/ucans/src/attenuation.ts b/packages/ucans/src/attenuation.ts new file mode 100644 index 0000000..26ee68b --- /dev/null +++ b/packages/ucans/src/attenuation.ts @@ -0,0 +1,419 @@ +import * as token from "./token.js" +import { Capability } from "./capability/index.js" +import { Ucan } from "./types.js" +import { ResourcePointer } from "./capability/resource-pointer.js" +import { Ability } from "./capability/ability.js" +import { SUPERUSER, Superuser } from "./capability/super-user.js" + + +// TYPES + + +/** + * UCAN capabilities can have arbitrary semantics for delegation. + * These semantics can be configured via this record of functions. + * + * In most cases you may just want to use `equalCanDelegate` as your semantics, + * but sometimes you want e.g. path behavior for a file-system-like resource: + * `path:/parent/` should be able to delegate access to `path:/parent/child/`. + */ +export interface DelegationSemantics { + /** + * Whether a parent resource can delegate a child resource. + * + * An implementation may for example decide to return true for + * `canDelegateResource(resourcePointer.parse("path:/parent/"), resourcePointer.parse("path:/parent/child/"))` + */ + canDelegateResource(parentResource: ResourcePointer, childResource: ResourcePointer): boolean + /** + * Whether a parent ability can delegate a child ability. + * + * An implementation may for example decide to return true for + * `canDelegateAbility(ability.parse("crud/UPDATE"), ability.parse("crud/CREATE"))` + */ + canDelegateAbility(parentAbility: Ability, childAbility: Ability): boolean +} + + +/** + * A delegation chain for a delegated capability or delegated ownership. + * + * This type represents a valid path of delegations through a UCAN. + * + * It can be cached as a sort of "witness" that a UCAN actually delegates a particular capability. + * + * Or it can be scanned to look for UCANs that may have become invalid due to revocation. + */ +export type DelegationChain + = DelegatedCapability + | DelegatedOwnership + + +/** + * A delegation chain that ends with a concrete capability. + */ +export interface DelegatedCapability { + /** + * The capability that the end of the chain grants. + */ + capability: Capability + /** + * The specific UCAN in the chain witnessing the delegated capability. + */ + ucan: Ucan + // will probably become an array in the future due to rights amplification + /** + * The rest of the delegation chain. This may include entries + * for `DelegatedOwnership`. + */ + chainStep?: DelegationChain +} + +/** + * A delegation chain that ends with delegated ownership. + * + * This is ownership over a specific DID at a certain resource and ability scope. + */ +export interface DelegatedOwnership { + /** + * The DID that ownership is delegated for. + */ + ownershipDID: string + /** + * The kinds of capabilites that can be delegated from the ownership. + */ + scope: OwnershipScope + /** + * The specific UCAN in the chain witnessing the delegated ownership. + */ + ucan: Ucan + /** + * The rest of the ownership delegation chain. + */ + chainStep?: DelegatedOwnership +} + + +/** + * This describes the scope of capabilities that are allowed to be delegated + * from delegated ownership. + */ +export type OwnershipScope + = Superuser + | { scheme: string; ability: Ability } + + + +// FUNCTIONS + + +/** + * This computes all possible delegations from given UCAN with given + * capability delegation semantics. + * + * For each entry in the attenuations array of the UCAN there will be at least + * one delegation chain. + * + * These delegation chains are computed lazily, so that if parts of the UCAN have + * been revoked or can't be loaded, this doesn't keep this function from figuring + * out different ways of delegating a capability from the attenuations. + * It also makes it possible to return early if a valid delegation chain has been found. + */ +export async function* delegationChains( + semantics: DelegationSemantics, + ucan: Ucan, + isRevoked: (ucan: Ucan) => Promise = async () => false +): AsyncIterable { + + if (await isRevoked(ucan)) { + yield new Error(`UCAN Revoked: ${token.encode(ucan)}`) + return + } + + yield* capabilitiesFromParenthood(ucan) + yield* capabilitiesFromDelegation(semantics, ucan, isRevoked) +} + + +/** + * Figures out the implied root issuer from a delegation chain. + * + * For a given delegation chain this will give you the DID of who + * "started" the chain, so who claims to be the "owner" of said capability. + */ +export function rootIssuer(delegationChain: DelegationChain): string { + if ("capability" in delegationChain) { + return delegationChain.chainStep == null + ? delegationChain.ucan.payload.iss + : rootIssuer(delegationChain.chainStep) + } + return delegationChain.ownershipDID +} + + +/** + * The default delegation semantics. + * This will just allow equal capabilities to be delegated, + * except that it also accounts for superuser abilities. + */ +export const equalCanDelegate: DelegationSemantics = { + canDelegateResource(parentResource, childResource) { + if (parentResource.scheme !== childResource.scheme) { + return false + } + + return parentResource.hierPart === childResource.hierPart + }, + + canDelegateAbility(parentAbility, childAbility) { + if (parentAbility === SUPERUSER) { + return true + } + if (childAbility === SUPERUSER) { + return false + } + + if (parentAbility.namespace !== childAbility.namespace) { + return false + } + + // Array equality + if (parentAbility.segments.length !== childAbility.segments.length) { + return false + } + return parentAbility.segments.reduce( + (acc, v, i) => acc && childAbility.segments[ i ] === v, + true as boolean + ) + }, +} + + +export function capabilityCanBeDelegated( + semantics: DelegationSemantics, + capability: Capability, + fromDelegationChain: DelegationChain, +): boolean { + if ("capability" in fromDelegationChain) { + return canDelegate(semantics, fromDelegationChain.capability, capability) + } + const ownershipScope = fromDelegationChain.scope + if (ownershipScope === SUPERUSER) { + return true + } + return ownershipScope.scheme == capability.with.scheme + && semantics.canDelegateAbility(ownershipScope.ability, capability.can) +} + + +export function ownershipCanBeDelegated( + semantics: DelegationSemantics, + did: string, + scope: OwnershipScope, + fromDelegationChain: DelegatedOwnership +): boolean { + if (did !== fromDelegationChain.ownershipDID) { + return false + } + + const parentScope = fromDelegationChain.scope + + // parent OwnershipScope can delegate child OwnershipScope + + if (parentScope === SUPERUSER) { + return true + } + if (scope === SUPERUSER) { + return false + } + return parentScope.scheme === scope.scheme + && semantics.canDelegateAbility(parentScope.ability, scope.ability) +} + + + +// ㊙️ Internal + + +function* capabilitiesFromParenthood(ucan: Ucan): Iterable { + for (const capability of ucan.payload.att) { + switch (capability.with.scheme.toLowerCase()) { + // If it's a "my" capability, it'll indicate an ownership delegation + case "my": { + const scope = capability.with.hierPart === SUPERUSER + ? SUPERUSER + : { scheme: capability.with.hierPart, ability: capability.can } + + yield { + ownershipDID: ucan.payload.iss, + scope, + ucan, + } + break + } + // if it's another known capability, we can ignore them + // (they're not introduced by parenthood) + case "as": + case "prf": + break + // otherwise we assume it's a normal parenthood capability introduction + default: + yield { capability, ucan } + } + } +} + + +async function* capabilitiesFromDelegation( + semantics: DelegationSemantics, + ucan: Ucan, + isRevoked: (ucan: Ucan) => Promise +): AsyncIterable { + + let proofIndex = 0 + + for await (const proof of token.validateProofs(ucan)) { + if (proof instanceof Error) { + yield proof + continue + } + + for (const capability of ucan.payload.att) { + try { + switch (capability.with.scheme.toLowerCase()) { + case "my": continue // cannot be delegated, only introduced by parenthood. + case "as": { + yield* handleAsDelegation(semantics, capability, ucan, proof, isRevoked) + break + } + case "prf": { + yield* handlePrfDelegation(semantics, capability, ucan, proof, proofIndex, isRevoked) + break + } + default: { + yield* handleNormalDelegation(semantics, capability, ucan, proof, isRevoked) + } + } + } catch (e) { + yield error(e) + } + } + + proofIndex++ + } + + function error(e: unknown): Error { + if (e instanceof Error) { + return e + } else { + return new Error(`Error during capability delegation checking: ${e}`) + } + } +} + + +async function* handleAsDelegation( + semantics: DelegationSemantics, + capability: Capability, + ucan: Ucan, + proof: Ucan, + isRevoked: (ucan: Ucan) => Promise +): AsyncIterable { + const split = capability.with.hierPart.split(":") + const scheme = split[ split.length - 1 ] + const ownershipDID = split.slice(0, -1).join(":") + const scope = scheme === SUPERUSER + ? SUPERUSER + : { scheme, ability: capability.can } + + for await (const delegationChain of delegationChains(semantics, proof, isRevoked)) { + if (delegationChain instanceof Error) { + yield delegationChain + continue + } + if (!("ownershipDID" in delegationChain)) { + continue + } + if (ownershipCanBeDelegated( + semantics, + ownershipDID, + scope, + delegationChain + )) { + yield { + ownershipDID, + scope, + ucan, + chainStep: delegationChain + } + } + } +} + + +async function* handlePrfDelegation( + semantics: DelegationSemantics, + capability: Capability, + ucan: Ucan, + proof: Ucan, + proofIndex: number, + isRevoked: (ucan: Ucan) => Promise +): AsyncIterable { + if ( + capability.with.hierPart !== SUPERUSER + && parseInt(capability.with.hierPart, 10) !== proofIndex + ) { + // if it's something like prf:2, we need to make sure that + // we only process the delegation if proofIndex === 2 + return + } + for await (const delegationChain of delegationChains(semantics, proof, isRevoked)) { + if (delegationChain instanceof Error) { + yield delegationChain + continue + } + if (!("capability" in delegationChain)) { + continue + } + yield { + capability: delegationChain.capability, + ucan, + chainStep: delegationChain + } + } +} + + +async function* handleNormalDelegation( + semantics: DelegationSemantics, + capability: Capability, + ucan: Ucan, + proof: Ucan, + isRevoked: (ucan: Ucan) => Promise +): AsyncIterable { + for await (const delegationChain of delegationChains(semantics, proof, isRevoked)) { + if (delegationChain instanceof Error) { + yield delegationChain + continue + } + if (!capabilityCanBeDelegated(semantics, capability, delegationChain)) { + continue + } + yield { + capability, + ucan, + chainStep: delegationChain + } + } +} + + +function canDelegate( + semantics: DelegationSemantics, + parentCapability: Capability, + childCapability: Capability, +): boolean { + return semantics.canDelegateResource(parentCapability.with, childCapability.with) + && semantics.canDelegateAbility(parentCapability.can, childCapability.can) +} + diff --git a/src/builder.ts b/packages/ucans/src/builder.ts similarity index 100% rename from src/builder.ts rename to packages/ucans/src/builder.ts diff --git a/packages/ucans/src/capability/ability.ts b/packages/ucans/src/capability/ability.ts new file mode 100644 index 0000000..873c5b4 --- /dev/null +++ b/packages/ucans/src/capability/ability.ts @@ -0,0 +1,94 @@ +import { Superuser, SUPERUSER } from "./super-user.js" +import * as util from "../util.js" + + +// 💎 + + +export type Ability + = Superuser + | { namespace: string; segments: string[] } + + +/** + * Separator for an ability's segments. + */ +export const SEPARATOR: string = "/" + + +/** + * Ability that can be used with a `prf` resource-pointer. + * This redelegates all capabilities of the proof(s). + */ +export const REDELEGATE: Ability = { namespace: "ucan", segments: [ "DELEGATE" ] } + + + +// TYPE CHECKS + + +export function isAbility(obj: unknown): obj is Ability { + return obj === SUPERUSER + || ( + util.isRecord(obj) + && util.hasProp(obj, "namespace") && typeof obj.namespace === "string" + && util.hasProp(obj, "segments") && Array.isArray(obj.segments) && obj.segments.every(s => typeof s === "string") + ) +} + + + +// 🛠 + + +export function isEqual(a: Ability, b: Ability): boolean { + if (a === SUPERUSER && b === SUPERUSER) return true + if (a === SUPERUSER || b === SUPERUSER) return false + + return ( + a.namespace.toLowerCase() === + b.namespace.toLowerCase() + ) && + ( + joinSegments(a.segments).toLowerCase() === + joinSegments(b.segments).toLowerCase() + ) +} + + +export function joinSegments(segments: string[]): string { + return segments.join(SEPARATOR) +} + + + +// ENCODING + + +/** + * Encode an ability. + * + * @param ability The ability to encode + */ +export function encode(ability: Ability): string { + switch (ability) { + case SUPERUSER: return ability + default: return joinSegments([ ability.namespace, ...ability.segments ]) + } +} + +/** + * Parse an encoded ability. + * + * @param ability The encoded ability + */ +export function parse(ability: string): Ability { + switch (ability) { + case SUPERUSER: + return SUPERUSER + default: { + const [ namespace, ...segments ] = ability.split(SEPARATOR) + return { namespace, segments } + } + } +} \ No newline at end of file diff --git a/packages/ucans/src/capability/index.ts b/packages/ucans/src/capability/index.ts new file mode 100644 index 0000000..148306d --- /dev/null +++ b/packages/ucans/src/capability/index.ts @@ -0,0 +1,120 @@ +import * as ability from "./ability.js" +import * as resourcePointer from "./resource-pointer.js" +import * as superUser from "./super-user.js" +import * as util from "../util.js" + +import { Ability, isAbility } from "./ability.js" +import { ResourcePointer, isResourcePointer } from "./resource-pointer.js" +import { Superuser, SUPERUSER } from "./super-user.js" + + +// RE-EXPORTS + + +export { ability, resourcePointer, superUser } + + + +// 💎 + + +export type Capability = { + with: ResourcePointer + can: Ability +} + +export type EncodedCapability = { + with: string + can: string +} + + + +// TYPE CHECKS + + +export function isCapability(obj: unknown): obj is Capability { + return util.isRecord(obj) + && util.hasProp(obj, "with") && isResourcePointer(obj.with) + && util.hasProp(obj, "can") && isAbility(obj.can) +} + +export function isEncodedCapability(obj: unknown): obj is EncodedCapability { + return util.isRecord(obj) + && util.hasProp(obj, "with") && typeof obj.with === "string" + && util.hasProp(obj, "can") && typeof obj.can === "string" +} + + + +// 🌸 + + +export function as(did: string, resource: Superuser | string): Capability { + return { + with: resourcePointer.as(did, resource), + can: SUPERUSER + } +} + + +export function my(resource: Superuser | string): Capability { + return { + with: resourcePointer.my(resource), + can: SUPERUSER + } +} + + +export function prf(selector: Superuser | number, ability: Ability): Capability { + return { + with: resourcePointer.prf(selector), + can: ability + } +} + + + +// 🛠 + + +/** + * Check if two capabilities are equal. + * + * This is not the same as `JSON.stringify(capA) === JSON.stringify(capB)`. + * Specifically: + * - For resource pointers, it does case-insensitive matching of the `scheme`. + * - For abilities, it does case-insensitive matching of the namespace and segments. + */ +export function isEqual(a: Capability, b: Capability): boolean { + return resourcePointer.isEqual(a.with, b.with) && ability.isEqual(a.can, b.can) +} + + + +// ENCODING + + +/** + * Encode the individual parts of a capability. + * + * @param cap The capability to encode + */ +export function encode(cap: Capability): EncodedCapability { + return { + with: resourcePointer.encode(cap.with), + can: ability.encode(cap.can) + } +} + +/** + * Parse an encoded capability. + * + * @param cap The encoded capability + */ +export function parse(cap: EncodedCapability): Capability { + return { + with: resourcePointer.parse(cap.with), + can: ability.parse(cap.can) + } +} \ No newline at end of file diff --git a/packages/ucans/src/capability/resource-pointer.ts b/packages/ucans/src/capability/resource-pointer.ts new file mode 100644 index 0000000..60781da --- /dev/null +++ b/packages/ucans/src/capability/resource-pointer.ts @@ -0,0 +1,89 @@ +import { Superuser, SUPERUSER } from "./super-user.js" +import * as util from "../util.js" + + +// 💎 + + +export type ResourcePointer = { + scheme: string + hierPart: Superuser | string +} + + +/** + * Separator for pieces of a URI. + */ +export const SEPARATOR: string = ":" + + + +// TYPE CHECKS + + +export function isResourcePointer(obj: unknown): obj is ResourcePointer { + return util.isRecord(obj) + && util.hasProp(obj, "scheme") && typeof obj.scheme === "string" + && util.hasProp(obj, "hierPart") && (obj.hierPart === SUPERUSER || typeof obj.hierPart === "string") +} + + + +// 🌸 + + +export function as(did: string, resource: Superuser | string): ResourcePointer { + return { + scheme: "as", + hierPart: `${did}:${resource}` + } +} + + +export function my(resource: Superuser | string): ResourcePointer { + return { + scheme: "my", + hierPart: resource + } +} + + +export function prf(selector: Superuser | number): ResourcePointer { + return { + scheme: "prf", + hierPart: selector.toString() + } +} + + + +// 🛠 + + +export function isEqual(a: ResourcePointer, b: ResourcePointer): boolean { + return a.scheme.toLowerCase() === a.scheme.toLowerCase() && a.hierPart === b.hierPart +} + + + +// ENCODING + + +/** + * Encode a resource pointer. + * + * @param pointer The resource pointer to encode + */ +export function encode(pointer: ResourcePointer): string { + return `${pointer.scheme}${SEPARATOR}${pointer.hierPart}` +} + +/** + * Parse an encoded resource pointer. + * + * @param pointer The encoded resource pointer + */ +export function parse(pointer: string): ResourcePointer { + const [ scheme, ...hierPart ] = pointer.split(SEPARATOR) + return { scheme, hierPart: hierPart.join(SEPARATOR) } +} \ No newline at end of file diff --git a/packages/ucans/src/capability/super-user.ts b/packages/ucans/src/capability/super-user.ts new file mode 100644 index 0000000..5432133 --- /dev/null +++ b/packages/ucans/src/capability/super-user.ts @@ -0,0 +1,10 @@ +export const SUPERUSER: Superuser = "*" +export type Superuser = "*" // maximum ability + + +// TYPE CHECKS + + +export function isSuperuser(obj: unknown): obj is Superuser { + return obj === SUPERUSER +} \ No newline at end of file diff --git a/packages/ucans/src/compatibility.ts b/packages/ucans/src/compatibility.ts new file mode 100644 index 0000000..2f447fe --- /dev/null +++ b/packages/ucans/src/compatibility.ts @@ -0,0 +1,114 @@ +// A module to hold all the ugly compatibility logic +// for getting from old UCANs to newer version UCANs. + +import * as semver from "./semver.js" + +import * as util from "./util.js" +import { SUPERUSER } from "./capability/super-user.js" +import { UcanParts, isUcanHeader, isUcanPayload } from "./types.js" +import { my } from "./capability/index.js" + + +const VERSION_0_3 = { major: 0, minor: 3, patch: 0 } + +type UcanHeader_0_3_0 = { + alg: string + typ: string + uav: string +} + +type UcanPayload_0_3_0 = { + iss: string + aud: string + nbf?: number + exp: number + rsc: string | Record + ptc: string + prf?: string +} + +function isUcanHeader_0_3_0(obj: unknown): obj is UcanHeader_0_3_0 { + return util.isRecord(obj) + && util.hasProp(obj, "alg") && typeof obj.alg === "string" + && util.hasProp(obj, "typ") && typeof obj.typ === "string" + && util.hasProp(obj, "uav") && typeof obj.uav === "string" +} + +function isUcanPayload_0_3_0(obj: unknown): obj is UcanPayload_0_3_0 { + return util.isRecord(obj) + && util.hasProp(obj, "iss") && typeof obj.iss === "string" + && util.hasProp(obj, "aud") && typeof obj.aud === "string" + && (!util.hasProp(obj, "nbf") || typeof obj.nbf === "number") + && util.hasProp(obj, "exp") && typeof obj.exp === "number" + && util.hasProp(obj, "rsc") && (typeof obj.rsc === "string" || util.isRecord(obj)) + && util.hasProp(obj, "ptc") && typeof obj.ptc === "string" + && (!util.hasProp(obj, "prf") || typeof obj.prf === "string") +} + + +export function handleCompatibility(header: unknown, payload: unknown): UcanParts { + const fail = (place: string, reason: string) => new Error(`Can't parse UCAN ${place}: ${reason}`) + + if (!util.isRecord(header)) throw fail("header", "Invalid format: Expected a record") + + // parse either the "ucv" or "uav" as a version in the header + // we translate 'uav: 1.0.0' into 'ucv: 0.3.0' + let version: "0.8.1" | "0.3.0" = "0.8.1" + if (!util.hasProp(header, "ucv") || typeof header.ucv !== "string") { + if (!util.hasProp(header, "uav") || typeof header.uav !== "string") { + throw fail("header", "Invalid format: Missing version indicator") + } else if (header.uav !== "1.0.0") { + throw fail("header", `Unsupported version 'uav: ${header.uav}'`) + } + version = "0.3.0" + } else if (semver.lt(header.ucv, "0.8.0")) { + throw fail("header", `Unsupported version 'ucv: ${header.ucv}'`) + } + + if (semver.gte(version, "0.8.0")) { + if (typeof header.ucv !== "string") { + throw fail("header", "Invalid format: Missing 'ucv' key or 'ucv' is not a string") + } + header.ucv = semver.parse(header.ucv) + if (header.ucv == null) { + throw fail("header", "Invalid format: 'ucv' string cannot be parsed into a semantic version") + } + if (!isUcanHeader(header)) throw fail("header", "Invalid format") + if (!isUcanPayload(payload)) throw fail("payload", "Invalid format") + return { header, payload } + } + + // we know it's version 0.3.0 + if (!isUcanHeader_0_3_0(header)) throw fail("header", "Invalid version 0.3.0 format") + if (!isUcanPayload_0_3_0(payload)) throw fail("payload", "Invalid version 0.3.0 format") + + return { + header: { + alg: header.alg, + typ: header.typ, + ucv: VERSION_0_3, + }, + payload: { + iss: payload.iss, + aud: payload.aud, + nbf: payload.nbf, + exp: payload.exp, + att: (() => { + if (payload.rsc === SUPERUSER || typeof payload.rsc === "string") return [ + my(SUPERUSER) + ] + + const resources: Record = payload.rsc + return Object.keys(resources).map(rscKey => { + return { + with: { scheme: rscKey, hierPart: resources[ rscKey ] }, + can: payload.ptc === SUPERUSER + ? SUPERUSER + : { namespace: rscKey, segments: [ payload.ptc ] } + } + }) + })(), + prf: payload.prf != null ? [ payload.prf ] : [] + }, + } +} diff --git a/src/did.ts b/packages/ucans/src/did.ts similarity index 100% rename from src/did.ts rename to packages/ucans/src/did.ts diff --git a/packages/ucans/src/did/prefix.ts b/packages/ucans/src/did/prefix.ts new file mode 100644 index 0000000..c0063c5 --- /dev/null +++ b/packages/ucans/src/did/prefix.ts @@ -0,0 +1,106 @@ +import * as uint8arrays from "uint8arrays" +import * as compression from "./pubkey-compress.js" +import { KeyType } from "../types.js" + +// Each prefix is varint-encoded. So e.g. 0x1205 gets varint-encoded to 0x8524 +// The varint encoding is described here: https://github.com/multiformats/unsigned-varint +// These varints are encoded big-endian in 7-bit pieces. +// So 0x1205 is split up into 0x12 and 0x05 +// Because there's another byte to be read, the MSB of 0x05 is set: 0x85 +// The next 7 bits encode as 0x24 (instead of 0x12) => 0x8524 + +/** https://github.com/multiformats/multicodec/blob/e9ecf587558964715054a0afcc01f7ace220952c/table.csv#L94 */ +export const EDWARDS_DID_PREFIX = new Uint8Array([ 0xed, 0x01 ]) +/** https://github.com/multiformats/multicodec/blob/e9ecf587558964715054a0afcc01f7ace220952c/table.csv#L91 */ +export const BLS_DID_PREFIX = new Uint8Array([ 0xea, 0x01 ]) +/** https://github.com/multiformats/multicodec/blob/e9ecf587558964715054a0afcc01f7ace220952c/table.csv#L141 */ +export const P256_DID_PREFIX = new Uint8Array([ 0x80, 0x24 ]) +/** https://github.com/multiformats/multicodec/blob/e9ecf587558964715054a0afcc01f7ace220952c/table.csv#L142 */ +export const P384_DID_PREFIX = new Uint8Array([ 0x81, 0x24 ]) +/** https://github.com/multiformats/multicodec/blob/e9ecf587558964715054a0afcc01f7ace220952c/table.csv#L143 */ +export const P521_DID_PREFIX = new Uint8Array([ 0x82, 0x24 ]) +/** https://github.com/multiformats/multicodec/blob/e9ecf587558964715054a0afcc01f7ace220952c/table.csv#L146 */ +export const RSA_DID_PREFIX = new Uint8Array([ 0x85, 0x24 ]) +/** Old RSA DID prefix, used pre-standardisation */ +export const RSA_DID_PREFIX_OLD = new Uint8Array([ 0x00, 0xf5, 0x02 ]) + +export const BASE58_DID_PREFIX = "did:key:z" // z is the multibase prefix for base58btc byte encoding + +/** + * Magic bytes. + */ +export function magicBytes(keyType: KeyType): Uint8Array | null { + switch (keyType) { + case "ed25519": + return EDWARDS_DID_PREFIX + case "p256": + return P256_DID_PREFIX + case "rsa": + return RSA_DID_PREFIX + case "bls12-381": + return BLS_DID_PREFIX + default: + return null + } +} + +/** + * Parse magic bytes on prefixed key-bytes + * to determine cryptosystem & the unprefixed key-bytes. + */ +export const parseMagicBytes = ( + prefixedKey: Uint8Array +): { + keyBytes: Uint8Array + type: KeyType +} => { + // RSA + if (hasPrefix(prefixedKey, RSA_DID_PREFIX)) { + return { + keyBytes: prefixedKey.slice(RSA_DID_PREFIX.byteLength), + type: "rsa", + } + + // RSA OLD + } else if (hasPrefix(prefixedKey, RSA_DID_PREFIX_OLD)) { + return { + keyBytes: prefixedKey.slice(RSA_DID_PREFIX_OLD.byteLength), + type: "rsa", + } + + // EC P-256 + } else if (hasPrefix(prefixedKey, P256_DID_PREFIX)) { + const compressedKey = prefixedKey.slice(P256_DID_PREFIX.byteLength) + const keyBytes = compression.decompressNistP256Pubkey(compressedKey) + return { + keyBytes, + type: "p256", + } + + // EDWARDS + } else if (hasPrefix(prefixedKey, EDWARDS_DID_PREFIX)) { + return { + keyBytes: prefixedKey.slice(EDWARDS_DID_PREFIX.byteLength), + type: "ed25519", + } + + // BLS + } else if (hasPrefix(prefixedKey, BLS_DID_PREFIX)) { + return { + keyBytes: prefixedKey.slice(BLS_DID_PREFIX.byteLength), + type: "bls12-381", + } + } + + throw new Error("Unsupported key algorithm. Try using RSA.") +} + +/** + * Determines if a Uint8Array has a given indeterminate length-prefix. + */ +export const hasPrefix = ( + prefixedKey: Uint8Array, + prefix: Uint8Array +): boolean => { + return uint8arrays.equals(prefix, prefixedKey.subarray(0, prefix.byteLength)) +} diff --git a/packages/ucans/src/did/pubkey-compress.ts b/packages/ucans/src/did/pubkey-compress.ts new file mode 100644 index 0000000..fe9f7c2 --- /dev/null +++ b/packages/ucans/src/did/pubkey-compress.ts @@ -0,0 +1,78 @@ +import bigInt from "big-integer" +import * as uint8arrays from "uint8arrays" + +// Compression & Decompression algos from: +// https://stackoverflow.com/questions/48521840/biginteger-to-a-uint8array-of-bytes + +// Public key compression for NIST P-256 +export const compressNistP256Pubkey = (pubkeyBytes: Uint8Array): Uint8Array => { + if (pubkeyBytes.length !== 65) { + throw new Error("Expected 65 byte pubkey") + } else if (pubkeyBytes[0] !== 0x04) { + throw new Error("Expected first byte to be 0x04") + } + // first byte is a prefix + const x = pubkeyBytes.slice(1, 33) + const y = pubkeyBytes.slice(33, 65) + const out = new Uint8Array(x.length + 1) + + out[0] = 2 + (y[y.length - 1] & 1) + out.set(x, 1) + + return out +} + +// Public key decompression for NIST P-256 +export const decompressNistP256Pubkey = (compressed: Uint8Array): Uint8Array => { + if (compressed.length !== 33) { + throw new Error("Expected 33 byte compress pubkey") + } else if (compressed[0] !== 0x02 && compressed[0] !== 0x03) { + throw new Error("Expected first byte to be 0x02 or 0x03") + } + // Consts for P256 curve + const two = bigInt(2) + // 115792089210356248762697446949407573530086143415290314195533631308867097853951 + const prime = two + .pow(256) + .subtract(two.pow(224)) + .add(two.pow(192)) + .add(two.pow(96)) + .subtract(1) + const b = bigInt( + "41058363725152142129326129780047268409114441015993725554835256314039467401291", + ) + + // Pre-computed value, or literal + const pIdent = prime.add(1).divide(4) // 28948022302589062190674361737351893382521535853822578548883407827216774463488 + + // This value must be 2 or 3. 4 indicates an uncompressed key, and anything else is invalid. + const signY = bigInt(compressed[0] - 2) + const x = compressed.slice(1) + const xBig = bigInt(uint8arrays.toString(x, "base10")) + + // y^2 = x^3 - 3x + b + const maybeY = xBig + .pow(3) + .subtract(xBig.multiply(3)) + .add(b) + .modPow(pIdent, prime) + + let yBig + // If the parity matches, we found our root, otherwise it's the other root + if (maybeY.mod(2).equals(signY)) { + yBig = maybeY + } else { + // y = prime - y + yBig = prime.subtract(maybeY) + } + const y = uint8arrays.fromString(yBig.toString(10), "base10") + + // left-pad for smaller than 32 byte y + const offset = 32 - y.length + const yPadded = new Uint8Array(32) + yPadded.set(y, offset) + + // concat coords & prepend P-256 prefix + const publicKey = uint8arrays.concat([[0x04], x, yPadded]) + return publicKey +} diff --git a/packages/ucans/src/did/transformers.ts b/packages/ucans/src/did/transformers.ts new file mode 100644 index 0000000..65fb2a0 --- /dev/null +++ b/packages/ucans/src/did/transformers.ts @@ -0,0 +1,105 @@ +import * as uint8arrays from "uint8arrays" + +import * as compression from "./pubkey-compress.js" +import * as rsa from "../crypto/rsa.js" +import { BASE58_DID_PREFIX, RSA_DID_PREFIX_OLD, magicBytes, parseMagicBytes, hasPrefix } from "./prefix.js" +import { KeyType, Encodings } from "../types.js" + + +// DID → PUBLIC KEY + + +/** + * Convert a DID (did:key) to a base64 public key. + */ +export function didToPublicKey(did: string, encoding: Encodings = "base64pad"): { + publicKey: string + type: KeyType +} { + const { publicKey, type } = didToPublicKeyBytes(did) + return { + publicKey: uint8arrays.toString(publicKey, encoding), + type + } +} + +/** + * Convert a DID (did:key) to the public key into bytes in SubjectPublicKeyInfo (spki) format. + * + * For consumption e.g. in the WebCrypto API. + */ +export function didToPublicKeyBytes(did: string): { + publicKey: Uint8Array + type: KeyType +} { + if (!did.startsWith(BASE58_DID_PREFIX)) { + throw new Error("Please use a base58-encoded DID formatted `did:key:z...`") + } + + const didWithoutPrefix = did.slice(BASE58_DID_PREFIX.length) + const magicBytes = uint8arrays.fromString(didWithoutPrefix, "base58btc") + const parsed = parseMagicBytes(magicBytes) + + if (parsed.type === "rsa" && !hasPrefix(magicBytes, RSA_DID_PREFIX_OLD)) { + // DID RSA keys are ASN.1 DER encoded "RSAPublicKeys" (PKCS #1). + // But the WebCrypto API mostly works with "SubjectPublicKeyInfo" (SPKI), + // which wraps RSAPublicKey with some metadata. + // In an unofficial RSA multiformat we were using, we used SPKI, + // so we have to be careful not to transform *every* RSA DID to SPKI, but + // only newer DIDs. + parsed.keyBytes = rsa.convertRSAPublicKeyToSubjectPublicKeyInfo(parsed.keyBytes) + } + + return { + publicKey: parsed.keyBytes, + type: parsed.type, + } +} + + + +// PUBLIC KEY → DID + + +/** + * Convert a base64 public key to a DID (did:key). + */ +export function publicKeyToDid( + publicKey: string, + type: KeyType, + encoding: Encodings = "base64pad" +): string { + const pubKeyBytes = uint8arrays.fromString(publicKey, encoding) + return publicKeyBytesToDid(pubKeyBytes, type) +} + +/** + * Convert a public key in bytes to a DID (did:key). + */ +export function publicKeyBytesToDid( + publicKeyBytes: Uint8Array, + type: KeyType, +): string { + // Prefix public-write key + const prefix = magicBytes(type) + if (prefix === null) { + throw new Error(`Key type '${type}' not supported`) + } + + if (type === "rsa") { + // See also the comment in didToPublicKeyBytes + // In this library, we're assuming a single byte encoding for all types of keys. + // For RSA that is "SubjectPublicKeyInfo", because that's what the WebCrypto API understands. + // But DIDs assume that all public keys are encoded as "RSAPublicKey". + publicKeyBytes = rsa.convertSubjectPublicKeyInfoToRSAPublicKey(publicKeyBytes) + } + + if(type === "p256") { + publicKeyBytes = compression.compressNistP256Pubkey(publicKeyBytes) + } + + const prefixedBytes = uint8arrays.concat([ prefix, publicKeyBytes ]) + + // Encode prefixed + return BASE58_DID_PREFIX + uint8arrays.toString(prefixedBytes, "base58btc") +} diff --git a/packages/ucans/src/did/validation.ts b/packages/ucans/src/did/validation.ts new file mode 100644 index 0000000..f472617 --- /dev/null +++ b/packages/ucans/src/did/validation.ts @@ -0,0 +1,44 @@ +import * as ed25519 from "@stablelib/ed25519" +import * as uint8arrays from "uint8arrays" + +import * as rsa from "../crypto/rsa.js" +import * as ecdsa from "../crypto/ecdsa.js" + +import { didToPublicKeyBytes } from "./transformers.js" + + +/** + * Verify the signature of some data (Uint8Array), given a DID. + */ +export async function verifySignature(data: Uint8Array, signature: Uint8Array, did: string): Promise { + try { + const { type, publicKey } = didToPublicKeyBytes(did) + + switch (type) { + + case "ed25519": + return ed25519.verify(publicKey, data, signature) + + case "rsa": + return await rsa.verify(data, signature, publicKey) + + case "p256": + return await ecdsa.verify(data, signature, publicKey, "P-256") + + default: return false + } + + } catch (_) { + return false + + } +} + +/** + * Verify the signature of some data (string encoded as utf8), given a DID. + */ +export async function verifySignatureUtf8(data: string, signature: string, did: string): Promise { + const dataBytes = uint8arrays.fromString(data, "utf8") + const sigBytes = uint8arrays.fromString(signature, "base64url") + return await verifySignature(dataBytes, sigBytes, did) +} diff --git a/src/index.ts b/packages/ucans/src/index.ts similarity index 100% rename from src/index.ts rename to packages/ucans/src/index.ts diff --git a/packages/ucans/src/semver.ts b/packages/ucans/src/semver.ts new file mode 100644 index 0000000..ae9d7aa --- /dev/null +++ b/packages/ucans/src/semver.ts @@ -0,0 +1,117 @@ +import { hasProp, isRecord } from "./util" + + +// Types + +export interface SemVer { + major: number + minor: number + patch: number +} + +export function isSemVer(obj: unknown): obj is SemVer { + return isRecord(obj) + && hasProp(obj, "major") && typeof obj.major === "number" + && hasProp(obj, "minor") && typeof obj.minor === "number" + && hasProp(obj, "patch") && typeof obj.patch === "number" +} + + +// Parsing + +const NUM_REGEX = /^0|[1-9]\d*/ + +const matchesRegex = (regex: RegExp) => (str: string) => { + const m = str.match(regex) + if (!m) return false + return m[0].length === str.length +} + +export function parse(version: string): SemVer | null { + const parts = version.split(".") + + if (parts.length !== 3) { + return null + } + + if (!parts.every(matchesRegex(NUM_REGEX))) { + return null + } + + const [ major, minor, patch ] = parts.map(part => parseInt(part, 10)) + + if (!Number.isSafeInteger(major) || !Number.isSafeInteger(minor) || !Number.isSafeInteger(patch)) { + return null + } + if (major < 0 || minor < 0 || patch < 0) { + return null + } + + return { major, minor, patch } +} + + +// Formatting/Prettyprinting + +export function format(semver: SemVer): string { + return `${semver.major}.${semver.minor}.${semver.patch}` +} + + +// Comparison + +export const GT = 1 +export const EQ = 0 +export const LT = -1 +export type GT = typeof GT +export type EQ = typeof EQ +export type LT = typeof LT + +function comparePart(left?: number, right?: number): GT | EQ | LT { + // when at least one of them is null + if (left == null) { + if (right == null) return EQ + return LT + } else if (right == null) { + return GT + } + + // when none of them are null + if (left > right) return GT + if (left < right) return LT + return EQ +} + +export function compare(left: SemVer | string, right: SemVer | string): GT | EQ | LT { + const l = typeof left === "string" ? parse(left) : left + const r = typeof right === "string" ? parse(right) : right + return ( + comparePart(l?.major, r?.major) || + comparePart(l?.minor, r?.minor) || + comparePart(l?.patch, r?.patch) + ) +} + +export function lt(left: SemVer | string, right: SemVer | string): boolean { + return compare(left, right) === LT +} + +export function gte(left: SemVer | string, right: SemVer | string): boolean { + return !lt(left, right) +} + +export function gt(left: SemVer | string, right: SemVer | string): boolean { + return compare(left, right) === GT +} + +export function lte(left: SemVer | string, right: SemVer | string): boolean { + return !gt(left, right) +} + +export function eq(left: SemVer | string, right: SemVer | string): boolean { + return compare(left, right) === EQ +} + +export function neq(left: SemVer | string, right: SemVer | string): boolean { + return !eq(left, right) +} diff --git a/packages/ucans/src/store.ts b/packages/ucans/src/store.ts new file mode 100644 index 0000000..aa9560d --- /dev/null +++ b/packages/ucans/src/store.ts @@ -0,0 +1,92 @@ +import * as token from "./token.js" +import { capabilityCanBeDelegated, DelegationSemantics, DelegationChain, delegationChains, rootIssuer } from "./attenuation.js" +import { Ucan } from "./types.js" +import { Capability } from "./capability/index.js" + + +export interface IndexByAudience { + [ audienceDID: string ]: Array<{ + processedUcan: Ucan + capabilities: DelegationChain[] + }> +} + +export class Store { + + private index: IndexByAudience + private knownSemantics: DelegationSemantics + + constructor(knownSemantics: DelegationSemantics, index: IndexByAudience) { + this.index = index + this.knownSemantics = knownSemantics + } + + static async fromTokens(knownSemantics: DelegationSemantics, tokens: Iterable | AsyncIterable): Promise { + const store = new Store(knownSemantics, {}) + for await (const encodedUcan of tokens) { + const ucan = await token.validate(encodedUcan) + await store.add(ucan) + } + return store + } + + async add(ucan: Ucan): Promise { + const audience = ucan.payload.aud + const byAudience = this.index[ audience ] ?? [] + const encoded = token.encode(ucan) + + if (byAudience.find(stored => token.encode(stored.processedUcan) === encoded) != null) { + return + } + + const chains = [] + for await (const delegationChain of delegationChains(this.knownSemantics, ucan)) { + if (delegationChain instanceof Error) { + console.warn(`Delegation chain error while storing UCAN:`, delegationChain) + continue + } + chains.push(delegationChain) + } + + // Also do this *after* the all awaits to prevent races. + if (byAudience.find(stored => token.encode(stored.processedUcan) === encoded) != null) { + return + } + + byAudience.push({ + processedUcan: ucan, + capabilities: chains + }) + this.index[ audience ] = byAudience + } + + getByAudience(audience: string): Ucan[] { + return (this.index[ audience ] ?? []).map(elem => elem.processedUcan) + } + + findByAudience(audience: string, predicate: (ucan: Ucan) => boolean): Ucan | null { + return this.index[ audience ]?.find(elem => predicate(elem.processedUcan))?.processedUcan ?? null + } + + *findWithCapability( + audience: string, + requiredCapability: Capability, + requiredIssuer: string, + ): Iterable { + const cache = this.index[ audience ] + + if (cache == null) { + return + } + + for (const cacheElement of cache) { + for (const delegationChain of cacheElement.capabilities) { + if (capabilityCanBeDelegated(this.knownSemantics, requiredCapability, delegationChain) + && rootIssuer(delegationChain) === requiredIssuer) { + yield delegationChain + } + } + } + } + +} diff --git a/src/token.ts b/packages/ucans/src/token.ts similarity index 97% rename from src/token.ts rename to packages/ucans/src/token.ts index 787c8f1..638f9f3 100644 --- a/src/token.ts +++ b/packages/ucans/src/token.ts @@ -1,12 +1,11 @@ -import * as uint8arrays from "uint8arrays" +import * as uint8arrays from "uint8arrays" // @IMPORT import * as semver from "./semver.js" import * as capability from "./capability/index.js" -import * as did from "./did.js" import * as util from "./util.js" import { Capability, isCapability, isEncodedCapability } from "./capability/index.js" -import { Fact, KeyType, Keypair } from "./types.js" +import { Fact, KeyType, Keypair, Didable } from "./types.js" import { Ucan, UcanHeader, UcanParts, UcanPayload } from "./types.js" import { handleCompatibility } from "./compatibility.js" import { verifySignatureUtf8 } from "./did/validation.js" @@ -46,7 +45,7 @@ const VERSION = { major: 0, minor: 8, patch: 1 } */ export async function build(params: { // from/to - issuer: Keypair + issuer: Keypair & Didable audience: string // capabilities @@ -63,8 +62,7 @@ export async function build(params: { addNonce?: boolean }): Promise { const keypair = params.issuer - const didStr = did.publicKeyBytesToDid(keypair.publicKey, keypair.keyType) - const payload = buildPayload({ ...params, issuer: didStr }) + const payload = buildPayload({ ...params, issuer: keypair.did() }) return signWithKeypair(payload, keypair) } @@ -136,6 +134,7 @@ export async function sign( ucv: VERSION, } + // @TODO what about this?? // Issuer key type must match UCAN algorithm if (did.didToPublicKey(payload.iss).type !== keyType) { throw new Error("The issuer's key type must match the given key type.") diff --git a/packages/ucans/src/types.ts b/packages/ucans/src/types.ts new file mode 100644 index 0000000..7ad62e9 --- /dev/null +++ b/packages/ucans/src/types.ts @@ -0,0 +1,143 @@ +import * as semver from "./semver.js" +import { SemVer } from "./semver.js" +import { SupportedEncodings } from "uint8arrays/util/bases.js" // @IMPORT +import { Capability, isCapability, isEncodedCapability } from "./capability/index.js" +import * as util from "./util.js" + + +// 💎 + + +export type Ucan = { + header: UcanHeader + payload: UcanPayload + // We need to keep the encoded version around to preserve the signature + signedData: string + signature: string +} + + + +// CHUNKS + + +export interface UcanParts { + header: UcanHeader + payload: UcanPayload +} + +export type UcanHeader = { + alg: string + typ: string + ucv: SemVer +} + +export type UcanPayload = { + iss: string + aud: string + exp: number + nbf?: number + nnc?: string + att: Array + fct?: Array + prf: Array +} + + + +// FRAGMENTS + + +export type Fact = Record + + + +// CRYPTOGRAPHY + + +/** Unlike tslib's CryptoKeyPair, this requires the `privateKey` and `publicKey` fields */ +export interface AvailableCryptoKeyPair { + privateKey: CryptoKey + publicKey: CryptoKey +} + +export interface Didable { + publicKeyStr: (format?: Encodings) => string + did: () => string +} + +export interface ExportableKey { + export: (format?: Encodings) => Promise +} + +export interface Keypair { + publicKey: Uint8Array + keyType: KeyType + sign: (msg: Uint8Array) => Promise +} + +export type KeyType = + | "rsa" + | "p256" + | "ed25519" + | "bls12-381" + +// https://developer.mozilla.org/en-US/docs/Web/API/EcKeyGenParams +export type NamedCurve = "P-256" + +export type PublicKeyJwk = { + kty: string + crv: string + x: string + y: string +} + +export type PrivateKeyJwk = PublicKeyJwk & { d: string } + + +// MISC + + +export type Encodings = SupportedEncodings + + + +// TYPE CHECKS + + +export function isAvailableCryptoKeyPair(keypair: CryptoKeyPair): keypair is AvailableCryptoKeyPair { + return keypair.publicKey != null && keypair.privateKey != null +} + +export function isKeypair(obj: unknown): obj is Keypair { + return util.isRecord(obj) + && util.hasProp(obj, "publicKey") && obj.publicKey instanceof Uint8Array + && util.hasProp(obj, "keyType") && typeof obj.keyType === "string" + && util.hasProp(obj, "sign") && typeof obj.sign === "function" +} + +export function isUcanHeader(obj: unknown): obj is UcanHeader { + return util.isRecord(obj) + && util.hasProp(obj, "alg") && typeof obj.alg === "string" + && util.hasProp(obj, "typ") && typeof obj.typ === "string" + && util.hasProp(obj, "ucv") && semver.isSemVer(obj.ucv) +} + +export function isUcanPayload(obj: unknown): obj is UcanPayload { + return util.isRecord(obj) + && util.hasProp(obj, "iss") && typeof obj.iss === "string" + && util.hasProp(obj, "aud") && typeof obj.aud === "string" + && util.hasProp(obj, "exp") && typeof obj.exp === "number" + && (!util.hasProp(obj, "nbf") || typeof obj.nbf === "number") + && (!util.hasProp(obj, "nnc") || typeof obj.nnc === "string") + && util.hasProp(obj, "att") && Array.isArray(obj.att) && obj.att.every(a => isCapability(a) || isEncodedCapability(a)) + && (!util.hasProp(obj, "fct") || Array.isArray(obj.fct) && obj.fct.every(util.isRecord)) + && util.hasProp(obj, "prf") && Array.isArray(obj.prf) && obj.prf.every(str => typeof str === "string") +} + + + +// @TODO Sort these + +export type DidSignatureCheck = (did: string, signature: Uint8Array) => Promise +export type DidToJwtAlg = (did: string) => string \ No newline at end of file diff --git a/packages/ucans/src/util.ts b/packages/ucans/src/util.ts new file mode 100644 index 0000000..ef2297f --- /dev/null +++ b/packages/ucans/src/util.ts @@ -0,0 +1,70 @@ +const CHARS = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" + + +export const generateNonce = (len = 6): string => { + let nonce = "" + for (let i = 0; i < len; i++) { + nonce += CHARS[ Math.floor(Math.random() * CHARS.length) ] + } + return nonce +} + +export function hasProp(data: unknown, prop: K): data is Record { + return typeof data === "object" && data != null && prop in data +} + +export function isRecord(data: unknown): data is Record { + return typeof data === "object" && data != null +} + +export function isIterable(obj: unknown): obj is Iterable { + return typeof obj === "object" && obj != null && Symbol.iterator in obj +} + +export function isAsyncIterable(obj: unknown): obj is AsyncIterable { + return typeof obj === "object" && obj != null && Symbol.asyncIterator in obj +} + + +export function all(it: Iterable): T[] +export function all(it: AsyncIterable): Promise +export function all(it: Iterable | AsyncIterable): T[] | Promise { + if (isIterable(it)) { + const arr = [] + for (const elem of it) { + arr.push(elem) + } + return arr + } else if (isAsyncIterable(it)) { + return (async () => { + const arr = [] + for await (const elem of it) { + arr.push(elem) + } + return arr + })() + } else { + throw new TypeError(`Expected either Iterable or AsyncIterable, but got ${it}`) + } +} + +export function first(it: Iterable): T | undefined +export function first(it: AsyncIterable): Promise +export function first(it: Iterable | AsyncIterable): T | undefined | Promise { + if (isIterable(it)) { + for (const elem of it) { + return elem + } + return undefined + } else if (isAsyncIterable(it)) { + return (async () => { + for await (const elem of it) { + return elem + } + return undefined + })() + } else { + throw new TypeError(`Expected either Iterable or AsyncIterable, but got ${it}`) + } +} + diff --git a/src/verify.ts b/packages/ucans/src/verify.ts similarity index 100% rename from src/verify.ts rename to packages/ucans/src/verify.ts diff --git a/packages/ucans/tsconfig.eslint.json b/packages/ucans/tsconfig.eslint.json new file mode 100644 index 0000000..e662081 --- /dev/null +++ b/packages/ucans/tsconfig.eslint.json @@ -0,0 +1,5 @@ +{ + "extends": "./tsconfig.json", + "include": ["**/*.ts"], + "exclude": [] +} diff --git a/packages/ucans/tsconfig.json b/packages/ucans/tsconfig.json new file mode 100644 index 0000000..b59f12b --- /dev/null +++ b/packages/ucans/tsconfig.json @@ -0,0 +1,12 @@ +{ + "compilerOptions": { + "target": "ES2015", + "strict": true, + "moduleResolution": "node", + "esModuleInterop": true, + "noFallthroughCasesInSwitch": true + }, + "include": [ + "src/**/*" + ] +} \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index 87c89f9..a78c185 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3,131 +3,69 @@ "@ampproject/remapping@^2.1.0": - version "2.1.2" - resolved "https://registry.yarnpkg.com/@ampproject/remapping/-/remapping-2.1.2.tgz#4edca94973ded9630d20101cd8559cedb8d8bd34" - integrity sha512-hoyByceqwKirw7w3Z7gnIIZC3Wx3J484Y3L/cMpXFbr7d9ZQj2mODrirNzcJa+SM3UlpWXYvKV4RlRpFXlWgXg== + version "2.2.0" + resolved "https://registry.yarnpkg.com/@ampproject/remapping/-/remapping-2.2.0.tgz#56c133824780de3174aed5ab6834f3026790154d" + integrity sha512-qRmjj8nj9qmLTQXXmaR1cck3UXSRMPrbsLJAasZpF+t3riI71BXed5ebIOYwQntykeZuhjsdweEc9BxH5Jc26w== dependencies: - "@jridgewell/trace-mapping" "^0.3.0" + "@jridgewell/gen-mapping" "^0.1.0" + "@jridgewell/trace-mapping" "^0.3.9" -"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.16.7": +"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.12.13", "@babel/code-frame@^7.16.7": version "7.16.7" resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.16.7.tgz#44416b6bd7624b998f5b1af5d470856c40138789" integrity sha512-iAXqUn8IIeBTNd72xsFlgaXHkMBMt6y4HJp1tIaK465CWLT/fG1aqB7ykr95gHHmlBdGbFeWWfyB4NJJ0nmeIg== dependencies: "@babel/highlight" "^7.16.7" -"@babel/code-frame@^7.12.13", "@babel/code-frame@^7.14.5": - version "7.14.5" - resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.14.5.tgz#23b08d740e83f49c5e59945fbf1b43e80bbf4edb" - integrity sha512-9pzDqyc6OLDaqe+zbACgFkb6fKMNG6CObKpnYXChRsvYGyEdc7CA2BaqeOM+vOtCS5ndmJicPJhKAwYRI6UfFw== - dependencies: - "@babel/highlight" "^7.14.5" - -"@babel/compat-data@^7.15.0": - version "7.15.0" - resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.15.0.tgz#2dbaf8b85334796cafbb0f5793a90a2fc010b176" - integrity sha512-0NqAC1IJE0S0+lL1SWFMxMkz1pKCNCjI4tr2Zx4LJSXxCLAdr6KyArnY+sno5m3yH9g737ygOyPABDsnXkpxiA== - -"@babel/compat-data@^7.17.7": - version "7.17.7" - resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.17.7.tgz#078d8b833fbbcc95286613be8c716cef2b519fa2" - integrity sha512-p8pdE6j0a29TNGebNm7NzYZWB3xVZJBZ7XGs42uAKzQo8VQ3F0By/cQCtUEABwIqw5zo6WA4NbmxsfzADzMKnQ== - -"@babel/core@^7.1.0", "@babel/core@^7.7.2": - version "7.15.5" - resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.15.5.tgz#f8ed9ace730722544609f90c9bb49162dc3bf5b9" - integrity sha512-pYgXxiwAgQpgM1bNkZsDEq85f0ggXMA5L7c+o3tskGMh2BunCI9QUwB9Z4jpvXUOuMdyGKiGKQiRe11VS6Jzvg== - dependencies: - "@babel/code-frame" "^7.14.5" - "@babel/generator" "^7.15.4" - "@babel/helper-compilation-targets" "^7.15.4" - "@babel/helper-module-transforms" "^7.15.4" - "@babel/helpers" "^7.15.4" - "@babel/parser" "^7.15.5" - "@babel/template" "^7.15.4" - "@babel/traverse" "^7.15.4" - "@babel/types" "^7.15.4" - convert-source-map "^1.7.0" - debug "^4.1.0" - gensync "^1.0.0-beta.2" - json5 "^2.1.2" - semver "^6.3.0" - source-map "^0.5.0" +"@babel/compat-data@^7.17.10": + version "7.18.5" + resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.18.5.tgz#acac0c839e317038c73137fbb6ef71a1d6238471" + integrity sha512-BxhE40PVCBxVEJsSBhB6UWyAuqJRxGsAw8BdHMJ3AKGydcwuWW4kOO3HmqBQAdcq/OP+/DlTVxLvsCzRTnZuGg== -"@babel/core@^7.12.3", "@babel/core@^7.8.0": - version "7.17.9" - resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.17.9.tgz#6bae81a06d95f4d0dec5bb9d74bbc1f58babdcfe" - integrity sha512-5ug+SfZCpDAkVp9SFIZAzlW18rlzsOcJGaetCjkySnrXXDUw9AR8cDUm1iByTmdWM6yxX6/zycaV76w3YTF2gw== +"@babel/core@^7.1.0", "@babel/core@^7.12.3", "@babel/core@^7.7.2", "@babel/core@^7.8.0": + version "7.18.5" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.18.5.tgz#c597fa680e58d571c28dda9827669c78cdd7f000" + integrity sha512-MGY8vg3DxMnctw0LdvSEojOsumc70g0t18gNyUdAZqB1Rpd1Bqo/svHGvt+UJ6JcGX+DIekGFDxxIWofBxLCnQ== dependencies: "@ampproject/remapping" "^2.1.0" "@babel/code-frame" "^7.16.7" - "@babel/generator" "^7.17.9" - "@babel/helper-compilation-targets" "^7.17.7" - "@babel/helper-module-transforms" "^7.17.7" - "@babel/helpers" "^7.17.9" - "@babel/parser" "^7.17.9" + "@babel/generator" "^7.18.2" + "@babel/helper-compilation-targets" "^7.18.2" + "@babel/helper-module-transforms" "^7.18.0" + "@babel/helpers" "^7.18.2" + "@babel/parser" "^7.18.5" "@babel/template" "^7.16.7" - "@babel/traverse" "^7.17.9" - "@babel/types" "^7.17.0" + "@babel/traverse" "^7.18.5" + "@babel/types" "^7.18.4" convert-source-map "^1.7.0" debug "^4.1.0" gensync "^1.0.0-beta.2" json5 "^2.2.1" semver "^6.3.0" -"@babel/generator@^7.15.4", "@babel/generator@^7.7.2": - version "7.15.4" - resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.15.4.tgz#85acb159a267ca6324f9793986991ee2022a05b0" - integrity sha512-d3itta0tu+UayjEORPNz6e1T3FtvWlP5N4V5M+lhp/CxT4oAA7/NcScnpRyspUMLK6tu9MNHmQHxRykuN2R7hw== - dependencies: - "@babel/types" "^7.15.4" - jsesc "^2.5.1" - source-map "^0.5.0" - -"@babel/generator@^7.17.9": - version "7.17.9" - resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.17.9.tgz#f4af9fd38fa8de143c29fce3f71852406fc1e2fc" - integrity sha512-rAdDousTwxbIxbz5I7GEQ3lUip+xVCXooZNbsydCWs3xA7ZsYOv+CFRdzGxRX78BmQHu9B1Eso59AOZQOJDEdQ== +"@babel/generator@^7.18.2", "@babel/generator@^7.7.2": + version "7.18.2" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.18.2.tgz#33873d6f89b21efe2da63fe554460f3df1c5880d" + integrity sha512-W1lG5vUwFvfMd8HVXqdfbuG7RuaSrTCCD8cl8fP8wOivdbtbIg2Db3IWUcgvfxKbbn6ZBGYRW/Zk1MIwK49mgw== dependencies: - "@babel/types" "^7.17.0" + "@babel/types" "^7.18.2" + "@jridgewell/gen-mapping" "^0.3.0" jsesc "^2.5.1" - source-map "^0.5.0" - -"@babel/helper-compilation-targets@^7.15.4": - version "7.15.4" - resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.15.4.tgz#cf6d94f30fbefc139123e27dd6b02f65aeedb7b9" - integrity sha512-rMWPCirulnPSe4d+gwdWXLfAXTTBj8M3guAf5xFQJ0nvFY7tfNAFnWdqaHegHlgDZOCT4qvhF3BYlSJag8yhqQ== - dependencies: - "@babel/compat-data" "^7.15.0" - "@babel/helper-validator-option" "^7.14.5" - browserslist "^4.16.6" - semver "^6.3.0" -"@babel/helper-compilation-targets@^7.17.7": - version "7.17.7" - resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.17.7.tgz#a3c2924f5e5f0379b356d4cfb313d1414dc30e46" - integrity sha512-UFzlz2jjd8kroj0hmCFV5zr+tQPi1dpC2cRsDV/3IEW8bJfCPrPpmcSN6ZS8RqIq4LXcmpipCQFPddyFA5Yc7w== +"@babel/helper-compilation-targets@^7.18.2": + version "7.18.2" + resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.18.2.tgz#67a85a10cbd5fc7f1457fec2e7f45441dc6c754b" + integrity sha512-s1jnPotJS9uQnzFtiZVBUxe67CuBa679oWFHpxYYnTpRL/1ffhyX44R9uYiXoa/pLXcY9H2moJta0iaanlk/rQ== dependencies: - "@babel/compat-data" "^7.17.7" + "@babel/compat-data" "^7.17.10" "@babel/helper-validator-option" "^7.16.7" - browserslist "^4.17.5" + browserslist "^4.20.2" semver "^6.3.0" -"@babel/helper-environment-visitor@^7.16.7": - version "7.16.7" - resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.16.7.tgz#ff484094a839bde9d89cd63cba017d7aae80ecd7" - integrity sha512-SLLb0AAn6PkUeAfKJCCOl9e1R53pQlGAfc4y4XuMRZfqeMYLE0dM1LMhqbGAlGQY0lfw5/ohoYWAe9V1yibRag== - dependencies: - "@babel/types" "^7.16.7" - -"@babel/helper-function-name@^7.15.4": - version "7.15.4" - resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.15.4.tgz#845744dafc4381a4a5fb6afa6c3d36f98a787ebc" - integrity sha512-Z91cOMM4DseLIGOnog+Z8OI6YseR9bua+HpvLAQ2XayUGU+neTtX+97caALaLdyu53I/fjhbeCnWnRH1O3jFOw== - dependencies: - "@babel/helper-get-function-arity" "^7.15.4" - "@babel/template" "^7.15.4" - "@babel/types" "^7.15.4" +"@babel/helper-environment-visitor@^7.16.7", "@babel/helper-environment-visitor@^7.18.2": + version "7.18.2" + resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.2.tgz#8a6d2dedb53f6bf248e31b4baf38739ee4a637bd" + integrity sha512-14GQKWkX9oJzPiQQ7/J36FTXcD4kSp8egKjO9nINlSKiHITRA9q/R74qu8S9xlc/b/yjsJItQUeeh3xnGN0voQ== "@babel/helper-function-name@^7.17.9": version "7.17.9" @@ -137,20 +75,6 @@ "@babel/template" "^7.16.7" "@babel/types" "^7.17.0" -"@babel/helper-get-function-arity@^7.15.4": - version "7.15.4" - resolved "https://registry.yarnpkg.com/@babel/helper-get-function-arity/-/helper-get-function-arity-7.15.4.tgz#098818934a137fce78b536a3e015864be1e2879b" - integrity sha512-1/AlxSF92CmGZzHnC515hm4SirTxtpDnLEJ0UyEMgTMZN+6bxXKg04dKhiRx5Enel+SUA1G1t5Ed/yQia0efrA== - dependencies: - "@babel/types" "^7.15.4" - -"@babel/helper-hoist-variables@^7.15.4": - version "7.15.4" - resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.15.4.tgz#09993a3259c0e918f99d104261dfdfc033f178df" - integrity sha512-VTy085egb3jUGVK9ycIxQiPbquesq0HUQ+tPO0uv5mPEBZipk+5FkRKiWq5apuyTE9FUrjENB0rCf8y+n+UuhA== - dependencies: - "@babel/types" "^7.15.4" - "@babel/helper-hoist-variables@^7.16.7": version "7.16.7" resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.16.7.tgz#86bcb19a77a509c7b77d0e22323ef588fa58c246" @@ -158,20 +82,6 @@ dependencies: "@babel/types" "^7.16.7" -"@babel/helper-member-expression-to-functions@^7.15.4": - version "7.15.4" - resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.15.4.tgz#bfd34dc9bba9824a4658b0317ec2fd571a51e6ef" - integrity sha512-cokOMkxC/BTyNP1AlY25HuBWM32iCEsLPI4BHDpJCHHm1FU2E7dKWWIXJgQgSFiu4lp8q3bL1BIKwqkSUviqtA== - dependencies: - "@babel/types" "^7.15.4" - -"@babel/helper-module-imports@^7.15.4": - version "7.15.4" - resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.15.4.tgz#e18007d230632dea19b47853b984476e7b4e103f" - integrity sha512-jeAHZbzUwdW/xHgHQ3QmWR4Jg6j15q4w/gCfwZvtqOxoo5DKtLHk8Bsf4c5RZRC7NmLEs+ohkdq8jFefuvIxAA== - dependencies: - "@babel/types" "^7.15.4" - "@babel/helper-module-imports@^7.16.7": version "7.16.7" resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.16.7.tgz#25612a8091a999704461c8a222d0efec5d091437" @@ -179,24 +89,10 @@ dependencies: "@babel/types" "^7.16.7" -"@babel/helper-module-transforms@^7.15.4": - version "7.15.4" - resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.15.4.tgz#962cc629a7f7f9a082dd62d0307fa75fe8788d7c" - integrity sha512-9fHHSGE9zTC++KuXLZcB5FKgvlV83Ox+NLUmQTawovwlJ85+QMhk1CnVk406CQVj97LaWod6KVjl2Sfgw9Aktw== - dependencies: - "@babel/helper-module-imports" "^7.15.4" - "@babel/helper-replace-supers" "^7.15.4" - "@babel/helper-simple-access" "^7.15.4" - "@babel/helper-split-export-declaration" "^7.15.4" - "@babel/helper-validator-identifier" "^7.14.9" - "@babel/template" "^7.15.4" - "@babel/traverse" "^7.15.4" - "@babel/types" "^7.15.4" - -"@babel/helper-module-transforms@^7.17.7": - version "7.17.7" - resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.17.7.tgz#3943c7f777139e7954a5355c815263741a9c1cbd" - integrity sha512-VmZD99F3gNTYB7fJRDTi+u6l/zxY0BE6OIxPSU7a50s6ZUQkHwSDmV92FfM+oCG0pZRVojGYhkR8I0OGeCVREw== +"@babel/helper-module-transforms@^7.18.0": + version "7.18.0" + resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.18.0.tgz#baf05dec7a5875fb9235bd34ca18bad4e21221cd" + integrity sha512-kclUYSUBIjlvnzN2++K9f2qzYKFgjmnmjwL4zlmU5f8ZtzgWe8s0rUPSTGy2HmK4P8T52MQsS+HTQAgZd3dMEA== dependencies: "@babel/helper-environment-visitor" "^7.16.7" "@babel/helper-module-imports" "^7.16.7" @@ -204,51 +100,20 @@ "@babel/helper-split-export-declaration" "^7.16.7" "@babel/helper-validator-identifier" "^7.16.7" "@babel/template" "^7.16.7" - "@babel/traverse" "^7.17.3" - "@babel/types" "^7.17.0" + "@babel/traverse" "^7.18.0" + "@babel/types" "^7.18.0" -"@babel/helper-optimise-call-expression@^7.15.4": - version "7.15.4" - resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.15.4.tgz#f310a5121a3b9cc52d9ab19122bd729822dee171" - integrity sha512-E/z9rfbAOt1vDW1DR7k4SzhzotVV5+qMciWV6LaG1g4jeFrkDlJedjtV4h0i4Q/ITnUu+Pk08M7fczsB9GXBDw== - dependencies: - "@babel/types" "^7.15.4" - -"@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.10.4", "@babel/helper-plugin-utils@^7.12.13", "@babel/helper-plugin-utils@^7.14.5", "@babel/helper-plugin-utils@^7.8.0": - version "7.14.5" - resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.14.5.tgz#5ac822ce97eec46741ab70a517971e443a70c5a9" - integrity sha512-/37qQCE3K0vvZKwoK4XU/irIJQdIfCJuhU5eKnNxpFDsOkgFaUAwbv+RYw6eYgsC0E4hS7r5KqGULUogqui0fQ== - -"@babel/helper-replace-supers@^7.15.4": - version "7.15.4" - resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.15.4.tgz#52a8ab26ba918c7f6dee28628b07071ac7b7347a" - integrity sha512-/ztT6khaXF37MS47fufrKvIsiQkx1LBRvSJNzRqmbyeZnTwU9qBxXYLaaT/6KaxfKhjs2Wy8kG8ZdsFUuWBjzw== - dependencies: - "@babel/helper-member-expression-to-functions" "^7.15.4" - "@babel/helper-optimise-call-expression" "^7.15.4" - "@babel/traverse" "^7.15.4" - "@babel/types" "^7.15.4" - -"@babel/helper-simple-access@^7.15.4": - version "7.15.4" - resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.15.4.tgz#ac368905abf1de8e9781434b635d8f8674bcc13b" - integrity sha512-UzazrDoIVOZZcTeHHEPYrr1MvTR/K+wgLg6MY6e1CJyaRhbibftF6fR2KU2sFRtI/nERUZR9fBd6aKgBlIBaPg== - dependencies: - "@babel/types" "^7.15.4" +"@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.10.4", "@babel/helper-plugin-utils@^7.12.13", "@babel/helper-plugin-utils@^7.14.5", "@babel/helper-plugin-utils@^7.17.12", "@babel/helper-plugin-utils@^7.8.0": + version "7.17.12" + resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.17.12.tgz#86c2347da5acbf5583ba0a10aed4c9bf9da9cf96" + integrity sha512-JDkf04mqtN3y4iAbO1hv9U2ARpPyPL1zqyWs/2WG1pgSq9llHFjStX5jdxb84himgJm+8Ng+x0oiWF/nw/XQKA== "@babel/helper-simple-access@^7.17.7": - version "7.17.7" - resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.17.7.tgz#aaa473de92b7987c6dfa7ce9a7d9674724823367" - integrity sha512-txyMCGroZ96i+Pxr3Je3lzEJjqwaRC9buMUgtomcrLe5Nd0+fk1h0LLA+ixUF5OW7AhHuQ7Es1WcQJZmZsz2XA== + version "7.18.2" + resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.18.2.tgz#4dc473c2169ac3a1c9f4a51cfcd091d1c36fcff9" + integrity sha512-7LIrjYzndorDY88MycupkpQLKS1AFfsVRm2k/9PtKScSy5tZq0McZTj+DiMRynboZfIqOKvo03pmhTaUgiD6fQ== dependencies: - "@babel/types" "^7.17.0" - -"@babel/helper-split-export-declaration@^7.15.4": - version "7.15.4" - resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.15.4.tgz#aecab92dcdbef6a10aa3b62ab204b085f776e257" - integrity sha512-HsFqhLDZ08DxCpBdEVtKmywj6PQbwnF6HHybur0MAnkAKnlS6uHkwnmRIkElB2Owpfb4xL4NwDmDLFubueDXsw== - dependencies: - "@babel/types" "^7.15.4" + "@babel/types" "^7.18.2" "@babel/helper-split-export-declaration@^7.16.7": version "7.16.7" @@ -257,71 +122,38 @@ dependencies: "@babel/types" "^7.16.7" -"@babel/helper-validator-identifier@^7.14.5", "@babel/helper-validator-identifier@^7.14.9": - version "7.14.9" - resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.9.tgz#6654d171b2024f6d8ee151bf2509699919131d48" - integrity sha512-pQYxPY0UP6IHISRitNe8bsijHex4TWZXi2HwKVsjPiltzlhse2znVcm9Ace510VT1kxIHjGJCZZQBX2gJDbo0g== - "@babel/helper-validator-identifier@^7.16.7": version "7.16.7" resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.16.7.tgz#e8c602438c4a8195751243da9031d1607d247cad" integrity sha512-hsEnFemeiW4D08A5gUAZxLBTXpZ39P+a+DGDsHw1yxqyQ/jzFEnxf5uTEGp+3bzAbNOxU1paTgYS4ECU/IgfDw== -"@babel/helper-validator-option@^7.14.5": - version "7.14.5" - resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.14.5.tgz#6e72a1fff18d5dfcb878e1e62f1a021c4b72d5a3" - integrity sha512-OX8D5eeX4XwcroVW45NMvoYaIuFI+GQpA2a8Gi+X/U/cDUIRsV37qQfF905F0htTRCREQIB4KqPeaveRJUl3Ow== - "@babel/helper-validator-option@^7.16.7": version "7.16.7" resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.16.7.tgz#b203ce62ce5fe153899b617c08957de860de4d23" integrity sha512-TRtenOuRUVo9oIQGPC5G9DgK4743cdxvtOw0weQNpZXaS16SCBi5MNjZF8vba3ETURjZpTbVn7Vvcf2eAwFozQ== -"@babel/helpers@^7.15.4": - version "7.15.4" - resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.15.4.tgz#5f40f02050a3027121a3cf48d497c05c555eaf43" - integrity sha512-V45u6dqEJ3w2rlryYYXf6i9rQ5YMNu4FLS6ngs8ikblhu2VdR1AqAd6aJjBzmf2Qzh6KOLqKHxEN9+TFbAkAVQ== - dependencies: - "@babel/template" "^7.15.4" - "@babel/traverse" "^7.15.4" - "@babel/types" "^7.15.4" - -"@babel/helpers@^7.17.9": - version "7.17.9" - resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.17.9.tgz#b2af120821bfbe44f9907b1826e168e819375a1a" - integrity sha512-cPCt915ShDWUEzEp3+UNRktO2n6v49l5RSnG9M5pS24hA+2FAc5si+Pn1i4VVbQQ+jh+bIZhPFQOJOzbrOYY1Q== +"@babel/helpers@^7.18.2": + version "7.18.2" + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.18.2.tgz#970d74f0deadc3f5a938bfa250738eb4ac889384" + integrity sha512-j+d+u5xT5utcQSzrh9p+PaJX94h++KN+ng9b9WEJq7pkUPAd61FGqhjuUEdfknb3E/uDBb7ruwEeKkIxNJPIrg== dependencies: "@babel/template" "^7.16.7" - "@babel/traverse" "^7.17.9" - "@babel/types" "^7.17.0" - -"@babel/highlight@^7.14.5": - version "7.14.5" - resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.14.5.tgz#6861a52f03966405001f6aa534a01a24d99e8cd9" - integrity sha512-qf9u2WFWVV0MppaL877j2dBtQIDgmidgjGk5VIMw3OadXvYaXn66U1BFlH2t4+t3i+8PhedppRv+i40ABzd+gg== - dependencies: - "@babel/helper-validator-identifier" "^7.14.5" - chalk "^2.0.0" - js-tokens "^4.0.0" + "@babel/traverse" "^7.18.2" + "@babel/types" "^7.18.2" "@babel/highlight@^7.16.7": - version "7.17.9" - resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.17.9.tgz#61b2ee7f32ea0454612def4fccdae0de232b73e3" - integrity sha512-J9PfEKCbFIv2X5bjTMiZu6Vf341N05QIY+d6FvVKynkG1S7G0j3I0QoRtWIrXhZ+/Nlb5Q0MzqL7TokEJ5BNHg== + version "7.17.12" + resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.17.12.tgz#257de56ee5afbd20451ac0a75686b6b404257351" + integrity sha512-7yykMVF3hfZY2jsHZEEgLc+3x4o1O+fYyULu11GynEUQNwB6lua+IIQn1FiJxNucd5UlyJryrwsOh8PL9Sn8Qg== dependencies: "@babel/helper-validator-identifier" "^7.16.7" chalk "^2.0.0" js-tokens "^4.0.0" -"@babel/parser@^7.1.0", "@babel/parser@^7.15.4", "@babel/parser@^7.15.5": - version "7.15.6" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.15.6.tgz#043b9aa3c303c0722e5377fef9197f4cf1796549" - integrity sha512-S/TSCcsRuCkmpUuoWijua0Snt+f3ewU/8spLo+4AXJCZfT0bVCzLD5MuOKdrx0mlAptbKzn5AdgEIIKXxXkz9Q== - -"@babel/parser@^7.14.7", "@babel/parser@^7.16.7", "@babel/parser@^7.17.9": - version "7.17.9" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.17.9.tgz#9c94189a6062f0291418ca021077983058e171ef" - integrity sha512-vqUSBLP8dQHFPdPi9bc5GK9vRkYHJ49fsZdtoJ8EQ8ibpwk5rPKfvNIwChB0KVXcIjcepEBBd2VHC5r9Gy8ueg== +"@babel/parser@^7.1.0", "@babel/parser@^7.14.7", "@babel/parser@^7.16.7", "@babel/parser@^7.18.5": + version "7.18.5" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.18.5.tgz#337062363436a893a2d22faa60be5bb37091c83c" + integrity sha512-YZWVaglMiplo7v8f1oMQ5ZPQr0vn7HPeZXxXWsxXJRjGVrzUFn9OxFQl1sb5wzfootjA/yChhW84BV+383FSOw== "@babel/plugin-syntax-async-generators@^7.8.4": version "7.8.4" @@ -408,22 +240,13 @@ "@babel/helper-plugin-utils" "^7.14.5" "@babel/plugin-syntax-typescript@^7.7.2": - version "7.14.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.14.5.tgz#b82c6ce471b165b5ce420cf92914d6fb46225716" - integrity sha512-u6OXzDaIXjEstBRRoBCQ/uKQKlbuaeE5in0RvWdA4pN6AhqxTIwUsnHPU1CFZA/amYObMsuWhYfRl3Ch90HD0Q== + version "7.17.12" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.17.12.tgz#b54fc3be6de734a56b87508f99d6428b5b605a7b" + integrity sha512-TYY0SXFiO31YXtNg3HtFwNJHjLsAyIIhAhNWkQ5whPPS7HWUFlg9z0Ta4qAQNjQbP1wsSt/oKkmZ/4/WWdMUpw== dependencies: - "@babel/helper-plugin-utils" "^7.14.5" + "@babel/helper-plugin-utils" "^7.17.12" -"@babel/template@^7.15.4", "@babel/template@^7.3.3": - version "7.15.4" - resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.15.4.tgz#51898d35dcf3faa670c4ee6afcfd517ee139f194" - integrity sha512-UgBAfEa1oGuYgDIPM2G+aHa4Nlo9Lh6mGD2bDBGMTbYnc38vulXPuC1MGjYILIEmlwl6Rd+BPR9ee3gm20CBtg== - dependencies: - "@babel/code-frame" "^7.14.5" - "@babel/parser" "^7.15.4" - "@babel/types" "^7.15.4" - -"@babel/template@^7.16.7": +"@babel/template@^7.16.7", "@babel/template@^7.3.3": version "7.16.7" resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.16.7.tgz#8d126c8701fde4d66b264b3eba3d96f07666d155" integrity sha512-I8j/x8kHUrbYRTUxXrrMbfCa7jxkE7tZre39x3kjr9hvI82cK1FfqLygotcWN5kdPGWcLdWMHpSBavse5tWw3w== @@ -432,49 +255,26 @@ "@babel/parser" "^7.16.7" "@babel/types" "^7.16.7" -"@babel/traverse@^7.15.4", "@babel/traverse@^7.7.2": - version "7.15.4" - resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.15.4.tgz#ff8510367a144bfbff552d9e18e28f3e2889c22d" - integrity sha512-W6lQD8l4rUbQR/vYgSuCAE75ADyyQvOpFVsvPPdkhf6lATXAsQIG9YdtOcu8BB1dZ0LKu+Zo3c1wEcbKeuhdlA== - dependencies: - "@babel/code-frame" "^7.14.5" - "@babel/generator" "^7.15.4" - "@babel/helper-function-name" "^7.15.4" - "@babel/helper-hoist-variables" "^7.15.4" - "@babel/helper-split-export-declaration" "^7.15.4" - "@babel/parser" "^7.15.4" - "@babel/types" "^7.15.4" - debug "^4.1.0" - globals "^11.1.0" - -"@babel/traverse@^7.17.3", "@babel/traverse@^7.17.9": - version "7.17.9" - resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.17.9.tgz#1f9b207435d9ae4a8ed6998b2b82300d83c37a0d" - integrity sha512-PQO8sDIJ8SIwipTPiR71kJQCKQYB5NGImbOviK8K+kg5xkNSYXLBupuX9QhatFowrsvo9Hj8WgArg3W7ijNAQw== +"@babel/traverse@^7.18.0", "@babel/traverse@^7.18.2", "@babel/traverse@^7.18.5", "@babel/traverse@^7.7.2": + version "7.18.5" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.18.5.tgz#94a8195ad9642801837988ab77f36e992d9a20cd" + integrity sha512-aKXj1KT66sBj0vVzk6rEeAO6Z9aiiQ68wfDgge3nHhA/my6xMM/7HGQUNumKZaoa2qUPQ5whJG9aAifsxUKfLA== dependencies: "@babel/code-frame" "^7.16.7" - "@babel/generator" "^7.17.9" - "@babel/helper-environment-visitor" "^7.16.7" + "@babel/generator" "^7.18.2" + "@babel/helper-environment-visitor" "^7.18.2" "@babel/helper-function-name" "^7.17.9" "@babel/helper-hoist-variables" "^7.16.7" "@babel/helper-split-export-declaration" "^7.16.7" - "@babel/parser" "^7.17.9" - "@babel/types" "^7.17.0" + "@babel/parser" "^7.18.5" + "@babel/types" "^7.18.4" debug "^4.1.0" globals "^11.1.0" -"@babel/types@^7.0.0", "@babel/types@^7.15.4", "@babel/types@^7.3.0", "@babel/types@^7.3.3": - version "7.15.6" - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.15.6.tgz#99abdc48218b2881c058dd0a7ab05b99c9be758f" - integrity sha512-BPU+7QhqNjmWyDO0/vitH/CuhpV8ZmK1wpKva8nuyNF5MJfuRNWMc+hc14+u9xT93kvykMdncrJT19h74uB1Ig== - dependencies: - "@babel/helper-validator-identifier" "^7.14.9" - to-fast-properties "^2.0.0" - -"@babel/types@^7.16.7", "@babel/types@^7.17.0": - version "7.17.0" - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.17.0.tgz#a826e368bccb6b3d84acd76acad5c0d87342390b" - integrity sha512-TmKSNO4D5rzhL5bjWFcVHHLETzfQ/AmbKpKPOSjlP0WoHZ6L911fgoOKY4Alp/emzG4cHJdyN49zpgkbXFEHHw== +"@babel/types@^7.0.0", "@babel/types@^7.16.7", "@babel/types@^7.17.0", "@babel/types@^7.18.0", "@babel/types@^7.18.2", "@babel/types@^7.18.4", "@babel/types@^7.3.0", "@babel/types@^7.3.3": + version "7.18.4" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.18.4.tgz#27eae9b9fd18e9dccc3f9d6ad051336f307be354" + integrity sha512-ThN1mBcMq5pG/Vm2IcBmPPfyPXbd8S02rS+OBIDENdufvqC7Z/jHPCv9IcP01277aKtDI8g/2XysBN4hA8niiw== dependencies: "@babel/helper-validator-identifier" "^7.16.7" to-fast-properties "^2.0.0" @@ -484,19 +284,19 @@ resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39" integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw== -"@eslint/eslintrc@^1.2.1": - version "1.2.1" - resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-1.2.1.tgz#8b5e1c49f4077235516bc9ec7d41378c0f69b8c6" - integrity sha512-bxvbYnBPN1Gibwyp6NrpnFzA3YtRL3BBAyEAFVIpNTm2Rn4Vy87GA5M4aSn3InRrlsbX5N0GW7XIx+U4SAEKdQ== +"@eslint/eslintrc@^1.3.0": + version "1.3.0" + resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-1.3.0.tgz#29f92c30bb3e771e4a2048c95fa6855392dfac4f" + integrity sha512-UWW0TMTmk2d7hLcWD1/e2g5HDM/HQ3csaLSqXCfqwh4uNDuNqlaKWXmEsL4Cs41Z0KnILNvwbHAah3C2yt06kw== dependencies: ajv "^6.12.4" debug "^4.3.2" - espree "^9.3.1" - globals "^13.9.0" + espree "^9.3.2" + globals "^13.15.0" ignore "^5.2.0" import-fresh "^3.2.1" js-yaml "^4.1.0" - minimatch "^3.0.4" + minimatch "^3.1.2" strip-json-comments "^3.1.1" "@humanwhocodes/config-array@^0.9.2": @@ -687,17 +487,6 @@ source-map "^0.6.1" write-file-atomic "^3.0.0" -"@jest/types@^27.1.1": - version "27.1.1" - resolved "https://registry.yarnpkg.com/@jest/types/-/types-27.1.1.tgz#77a3fc014f906c65752d12123a0134359707c0ad" - integrity sha512-yqJPDDseb0mXgKqmNqypCsb85C22K1aY5+LUxh7syIM9n/b0AsaltxNy+o6tt29VcfGDpYEve175bm3uOhcehA== - dependencies: - "@types/istanbul-lib-coverage" "^2.0.0" - "@types/istanbul-reports" "^3.0.0" - "@types/node" "*" - "@types/yargs" "^16.0.0" - chalk "^4.0.0" - "@jest/types@^27.5.1": version "27.5.1" resolved "https://registry.yarnpkg.com/@jest/types/-/types-27.5.1.tgz#3c79ec4a8ba61c170bf937bcf9e98a9df175ec80" @@ -709,20 +498,42 @@ "@types/yargs" "^16.0.0" chalk "^4.0.0" +"@jridgewell/gen-mapping@^0.1.0": + version "0.1.1" + resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.1.1.tgz#e5d2e450306a9491e3bd77e323e38d7aff315996" + integrity sha512-sQXCasFk+U8lWYEe66WxRDOE9PjVz4vSM51fTu3Hw+ClTpUSQb718772vH3pyS5pShp6lvQM7SxgIDXXXmOX7w== + dependencies: + "@jridgewell/set-array" "^1.0.0" + "@jridgewell/sourcemap-codec" "^1.4.10" + +"@jridgewell/gen-mapping@^0.3.0": + version "0.3.2" + resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz#c1aedc61e853f2bb9f5dfe6d4442d3b565b253b9" + integrity sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A== + dependencies: + "@jridgewell/set-array" "^1.0.1" + "@jridgewell/sourcemap-codec" "^1.4.10" + "@jridgewell/trace-mapping" "^0.3.9" + "@jridgewell/resolve-uri@^3.0.3": - version "3.0.5" - resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.0.5.tgz#68eb521368db76d040a6315cdb24bf2483037b9c" - integrity sha512-VPeQ7+wH0itvQxnG+lIzWgkysKIr3L9sslimFW55rHMdGu/qCQ5z5h9zq4gI8uBtqkpHhsF4Z/OwExufUCThew== + version "3.0.8" + resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.0.8.tgz#687cc2bbf243f4e9a868ecf2262318e2658873a1" + integrity sha512-YK5G9LaddzGbcucK4c8h5tWFmMPBvRZ/uyWmN1/SbBdIvqGUdWGkJ5BAaccgs6XbzVLsqbPJrBSFwKv3kT9i7w== + +"@jridgewell/set-array@^1.0.0", "@jridgewell/set-array@^1.0.1": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@jridgewell/set-array/-/set-array-1.1.2.tgz#7c6cf998d6d20b914c0a55a91ae928ff25965e72" + integrity sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw== "@jridgewell/sourcemap-codec@^1.4.10": - version "1.4.11" - resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.11.tgz#771a1d8d744eeb71b6adb35808e1a6c7b9b8c8ec" - integrity sha512-Fg32GrJo61m+VqYSdRSjRXMjQ06j8YIYfcTqndLYVAaHmroZHLJZCydsWBOTDqXS2v+mjxohBWEMfg97GXmYQg== + version "1.4.14" + resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz#add4c98d341472a289190b424efbdb096991bb24" + integrity sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw== -"@jridgewell/trace-mapping@^0.3.0": - version "0.3.4" - resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.4.tgz#f6a0832dffd5b8a6aaa633b7d9f8e8e94c83a0c3" - integrity sha512-vFv9ttIedivx0ux3QSjhgtCVjPZd5l46ZOMDSCwnH1yUO2e964gO8LZGyv2QkqcgR6TnBU1v+1IFqmeoG+0UJQ== +"@jridgewell/trace-mapping@^0.3.9": + version "0.3.14" + resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.14.tgz#b231a081d8f66796e475ad588a1ef473112701ed" + integrity sha512-bJWEfQ9lPTvm3SneWwRFVLzrh6nhjwqw7TUFFBEMzwvg7t7PCDenf2lDwqo4NQXzdpgBXyFgDWnQA+2vkruksQ== dependencies: "@jridgewell/resolve-uri" "^3.0.3" "@jridgewell/sourcemap-codec" "^1.4.10" @@ -816,9 +627,9 @@ integrity sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw== "@types/babel__core@^7.0.0", "@types/babel__core@^7.1.14": - version "7.1.16" - resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.1.16.tgz#bc12c74b7d65e82d29876b5d0baf5c625ac58702" - integrity sha512-EAEHtisTMM+KaKwfWdC3oyllIqswlznXCIVCt7/oRNrh+DhgT4UEBNC/jlADNjvw7UnfbcdkGQcPVZ1xYiLcrQ== + version "7.1.19" + resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.1.19.tgz#7b497495b7d1b4812bdb9d02804d0576f43ee460" + integrity sha512-WEOTgRsbYkvA/KCsDwVEGkd7WAr1e3g31VHQ8zy5gul/V1qKullU/BU5I68X5v7V3GnB9eotmom4v5a5gjxorw== dependencies: "@babel/parser" "^7.1.0" "@babel/types" "^7.0.0" @@ -827,9 +638,9 @@ "@types/babel__traverse" "*" "@types/babel__generator@*": - version "7.6.3" - resolved "https://registry.yarnpkg.com/@types/babel__generator/-/babel__generator-7.6.3.tgz#f456b4b2ce79137f768aa130d2423d2f0ccfaba5" - integrity sha512-/GWCmzJWqV7diQW54smJZzWbSFf4QYtF71WCKhcx6Ru/tFyQIY2eiiITcCAeuPbNSvT9YCGkVMqqvSk2Z0mXiA== + version "7.6.4" + resolved "https://registry.yarnpkg.com/@types/babel__generator/-/babel__generator-7.6.4.tgz#1f20ce4c5b1990b37900b63f050182d28c2439b7" + integrity sha512-tFkciB9j2K755yrTALxD44McOrk+gfpIpvC3sxHjRawj6PfnQxrse4Clq5y/Rq+G3mrBurMax/lG8Qn2t9mSsg== dependencies: "@babel/types" "^7.0.0" @@ -842,9 +653,9 @@ "@babel/types" "^7.0.0" "@types/babel__traverse@*", "@types/babel__traverse@^7.0.4", "@types/babel__traverse@^7.0.6": - version "7.14.2" - resolved "https://registry.yarnpkg.com/@types/babel__traverse/-/babel__traverse-7.14.2.tgz#ffcd470bbb3f8bf30481678fb5502278ca833a43" - integrity sha512-K2waXdXBi2302XUdcHcR1jCeU0LL4TD9HRs/gk0N2Xvrht+G/BfJa4QObBQZfhMdxiCpV3COl5Nfq4uKTeTnJA== + version "7.17.1" + resolved "https://registry.yarnpkg.com/@types/babel__traverse/-/babel__traverse-7.17.1.tgz#1a0e73e8c28c7e832656db372b779bfd2ef37314" + integrity sha512-kVzjari1s2YVi77D3w1yuvohV2idweYXMCDzqBiVNN63TcDWrIlTVOYpqVrvbbyOE/IyzBoTKF0fdnLPEORFxA== dependencies: "@babel/types" "^7.3.0" @@ -856,9 +667,9 @@ "@types/node" "*" "@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.0", "@types/istanbul-lib-coverage@^2.0.1": - version "2.0.3" - resolved "https://registry.yarnpkg.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.3.tgz#4ba8ddb720221f432e443bd5f9117fd22cfd4762" - integrity sha512-sz7iLqvVUg1gIedBOvlkxPlc8/uVzyS5OwGz1cKjXzkl3FpL3al0crU8YGU1WoHkxn0Wxbw5tyi6hvzJKNzFsw== + version "2.0.4" + resolved "https://registry.yarnpkg.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz#8467d4b3c087805d63580480890791277ce35c44" + integrity sha512-z/QT1XN4K4KYuslS23k62yDIDLwLFkzxOuMplDtObz0+y7VqJCaO2o+SPwHCvLFZh7xazvvoor2tA/hPz9ee7g== "@types/istanbul-lib-report@*": version "3.0.0" @@ -875,32 +686,32 @@ "@types/istanbul-lib-report" "*" "@types/jest@^27.4.1": - version "27.4.1" - resolved "https://registry.yarnpkg.com/@types/jest/-/jest-27.4.1.tgz#185cbe2926eaaf9662d340cc02e548ce9e11ab6d" - integrity sha512-23iPJADSmicDVrWk+HT58LMJtzLAnB2AgIzplQuq/bSrGaxCrlvRFjGbXmamnnk/mAmCdLStiGqggu28ocUyiw== + version "27.5.2" + resolved "https://registry.yarnpkg.com/@types/jest/-/jest-27.5.2.tgz#ec49d29d926500ffb9fd22b84262e862049c026c" + integrity sha512-mpT8LJJ4CMeeahobofYWIjFo0xonRS/HfxnVEPMPFSQdGUt1uHCnoPT7Zhb+sjDU2wz0oKV0OLUR0WzrHNgfeA== dependencies: jest-matcher-utils "^27.0.0" pretty-format "^27.0.0" "@types/json-schema@^7.0.9": - version "7.0.9" - resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.9.tgz#97edc9037ea0c38585320b28964dde3b39e4660d" - integrity sha512-qcUXuemtEu+E5wZSJHNxUXeCZhAfXKQ41D+duX+VYPde7xyEVZci+/oXKJL13tnRs9lR2pr4fod59GT6/X1/yQ== + version "7.0.11" + resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.11.tgz#d421b6c527a3037f7c84433fd2c4229e016863d3" + integrity sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ== "@types/node@*": - version "16.9.2" - resolved "https://registry.yarnpkg.com/@types/node/-/node-16.9.2.tgz#81f5a039d6ed1941f8cc57506c74e7c2b8fc64b9" - integrity sha512-ZHty/hKoOLZvSz6BtP1g7tc7nUeJhoCf3flLjh8ZEv1vFKBWHXcnMbJMyN/pftSljNyy0kNW/UqI3DccnBnZ8w== + version "18.0.0" + resolved "https://registry.yarnpkg.com/@types/node/-/node-18.0.0.tgz#67c7b724e1bcdd7a8821ce0d5ee184d3b4dd525a" + integrity sha512-cHlGmko4gWLVI27cGJntjs/Sj8th9aYwplmZFwmmgYQQvL5NUsgVJG7OddLvNfLqYS31KFN0s3qlaD9qCaxACA== "@types/node@^17.0.23": - version "17.0.23" - resolved "https://registry.yarnpkg.com/@types/node/-/node-17.0.23.tgz#3b41a6e643589ac6442bdbd7a4a3ded62f33f7da" - integrity sha512-UxDxWn7dl97rKVeVS61vErvw086aCYhDLyvRQZ5Rk65rZKepaFdm53GeqXaKBuOhED4e9uWq34IC3TdSdJJ2Gw== + version "17.0.45" + resolved "https://registry.yarnpkg.com/@types/node/-/node-17.0.45.tgz#2c0fafd78705e7a18b7906b5201a522719dc5190" + integrity sha512-w+tIMs3rq2afQdsPJlODhoUEKzFP1ayaoyl1CcnwtIlsVe7K7bA1NGm4s3PraqTLlXnbIN84zuBlxBWo1u9BLw== "@types/prettier@^2.1.5": - version "2.3.2" - resolved "https://registry.yarnpkg.com/@types/prettier/-/prettier-2.3.2.tgz#fc8c2825e4ed2142473b4a81064e6e081463d1b3" - integrity sha512-eI5Yrz3Qv4KPUa/nSIAi0h+qX0XyewOliug5F2QAtuRg6Kjg6jfmxe1GIwoIRhZspD1A0RP8ANrPwvEXXtRFog== + version "2.6.3" + resolved "https://registry.yarnpkg.com/@types/prettier/-/prettier-2.6.3.tgz#68ada76827b0010d0db071f739314fa429943d0a" + integrity sha512-ymZk3LEC/fsut+/Q5qejp6R9O1rMxz3XaRHDV6kX8MrGAhOSPqVARbDi+EZvInBpw+BnCX3TD240byVkOfQsHg== "@types/stack-utils@^2.0.0": version "2.0.1" @@ -908,9 +719,9 @@ integrity sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw== "@types/yargs-parser@*": - version "20.2.1" - resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-20.2.1.tgz#3b9ce2489919d9e4fea439b76916abc34b2df129" - integrity sha512-7tFImggNeNBVMsn0vLrpn1H1uPrUBdnARPTpZoitY37ZrdJREzf7I16tMrlK3hen349gr1NYh8CmZQa7CTG6Aw== + version "21.0.0" + resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-21.0.0.tgz#0c60e537fa790f5f9472ed2776c2b71ec117351b" + integrity sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA== "@types/yargs@^16.0.0": version "16.0.4" @@ -920,89 +731,89 @@ "@types/yargs-parser" "*" "@typescript-eslint/eslint-plugin@^5.18.0": - version "5.18.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.18.0.tgz#950df411cec65f90d75d6320a03b2c98f6c3af7d" - integrity sha512-tzrmdGMJI/uii9/V6lurMo4/o+dMTKDH82LkNjhJ3adCW22YQydoRs5MwTiqxGF9CSYxPxQ7EYb4jLNlIs+E+A== - dependencies: - "@typescript-eslint/scope-manager" "5.18.0" - "@typescript-eslint/type-utils" "5.18.0" - "@typescript-eslint/utils" "5.18.0" - debug "^4.3.2" + version "5.30.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.30.0.tgz#524a11e15c09701733033c96943ecf33f55d9ca1" + integrity sha512-lvhRJ2pGe2V9MEU46ELTdiHgiAFZPKtLhiU5wlnaYpMc2+c1R8fh8i80ZAa665drvjHKUJyRRGg3gEm1If54ow== + dependencies: + "@typescript-eslint/scope-manager" "5.30.0" + "@typescript-eslint/type-utils" "5.30.0" + "@typescript-eslint/utils" "5.30.0" + debug "^4.3.4" functional-red-black-tree "^1.0.1" - ignore "^5.1.8" + ignore "^5.2.0" regexpp "^3.2.0" - semver "^7.3.5" + semver "^7.3.7" tsutils "^3.21.0" "@typescript-eslint/parser@^5.18.0": - version "5.18.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.18.0.tgz#2bcd4ff21df33621df33e942ccb21cb897f004c6" - integrity sha512-+08nYfurBzSSPndngnHvFw/fniWYJ5ymOrn/63oMIbgomVQOvIDhBoJmYZ9lwQOCnQV9xHGvf88ze3jFGUYooQ== - dependencies: - "@typescript-eslint/scope-manager" "5.18.0" - "@typescript-eslint/types" "5.18.0" - "@typescript-eslint/typescript-estree" "5.18.0" - debug "^4.3.2" - -"@typescript-eslint/scope-manager@5.18.0": - version "5.18.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.18.0.tgz#a7d7b49b973ba8cebf2a3710eefd457ef2fb5505" - integrity sha512-C0CZML6NyRDj+ZbMqh9FnPscg2PrzSaVQg3IpTmpe0NURMVBXlghGZgMYqBw07YW73i0MCqSDqv2SbywnCS8jQ== - dependencies: - "@typescript-eslint/types" "5.18.0" - "@typescript-eslint/visitor-keys" "5.18.0" - -"@typescript-eslint/type-utils@5.18.0": - version "5.18.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-5.18.0.tgz#62dbfc8478abf36ba94a90ddf10be3cc8e471c74" - integrity sha512-vcn9/6J5D6jtHxpEJrgK8FhaM8r6J1/ZiNu70ZUJN554Y3D9t3iovi6u7JF8l/e7FcBIxeuTEidZDR70UuCIfA== - dependencies: - "@typescript-eslint/utils" "5.18.0" - debug "^4.3.2" + version "5.30.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.30.0.tgz#a2184fb5f8ef2bf1db0ae61a43907e2e32aa1b8f" + integrity sha512-2oYYUws5o2liX6SrFQ5RB88+PuRymaM2EU02/9Ppoyu70vllPnHVO7ioxDdq/ypXHA277R04SVjxvwI8HmZpzA== + dependencies: + "@typescript-eslint/scope-manager" "5.30.0" + "@typescript-eslint/types" "5.30.0" + "@typescript-eslint/typescript-estree" "5.30.0" + debug "^4.3.4" + +"@typescript-eslint/scope-manager@5.30.0": + version "5.30.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.30.0.tgz#bf585ee801ab4ad84db2f840174e171a6bb002c7" + integrity sha512-3TZxvlQcK5fhTBw5solQucWSJvonXf5yua5nx8OqK94hxdrT7/6W3/CS42MLd/f1BmlmmbGEgQcTHHCktUX5bQ== + dependencies: + "@typescript-eslint/types" "5.30.0" + "@typescript-eslint/visitor-keys" "5.30.0" + +"@typescript-eslint/type-utils@5.30.0": + version "5.30.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-5.30.0.tgz#98f3af926a5099153f092d4dad87148df21fbaae" + integrity sha512-GF8JZbZqSS+azehzlv/lmQQ3EU3VfWYzCczdZjJRxSEeXDQkqFhCBgFhallLDbPwQOEQ4MHpiPfkjKk7zlmeNg== + dependencies: + "@typescript-eslint/utils" "5.30.0" + debug "^4.3.4" tsutils "^3.21.0" -"@typescript-eslint/types@5.18.0": - version "5.18.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.18.0.tgz#4f0425d85fdb863071680983853c59a62ce9566e" - integrity sha512-bhV1+XjM+9bHMTmXi46p1Led5NP6iqQcsOxgx7fvk6gGiV48c6IynY0apQb7693twJDsXiVzNXTflhplmaiJaw== +"@typescript-eslint/types@5.30.0": + version "5.30.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.30.0.tgz#db7d81d585a3da3801432a9c1d2fafbff125e110" + integrity sha512-vfqcBrsRNWw/LBXyncMF/KrUTYYzzygCSsVqlZ1qGu1QtGs6vMkt3US0VNSQ05grXi5Yadp3qv5XZdYLjpp8ag== -"@typescript-eslint/typescript-estree@5.18.0": - version "5.18.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.18.0.tgz#6498e5ee69a32e82b6e18689e2f72e4060986474" - integrity sha512-wa+2VAhOPpZs1bVij9e5gyVu60ReMi/KuOx4LKjGx2Y3XTNUDJgQ+5f77D49pHtqef/klglf+mibuHs9TrPxdQ== +"@typescript-eslint/typescript-estree@5.30.0": + version "5.30.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.30.0.tgz#4565ee8a6d2ac368996e20b2344ea0eab1a8f0bb" + integrity sha512-hDEawogreZB4n1zoqcrrtg/wPyyiCxmhPLpZ6kmWfKF5M5G0clRLaEexpuWr31fZ42F96SlD/5xCt1bT5Qm4Nw== dependencies: - "@typescript-eslint/types" "5.18.0" - "@typescript-eslint/visitor-keys" "5.18.0" - debug "^4.3.2" - globby "^11.0.4" + "@typescript-eslint/types" "5.30.0" + "@typescript-eslint/visitor-keys" "5.30.0" + debug "^4.3.4" + globby "^11.1.0" is-glob "^4.0.3" - semver "^7.3.5" + semver "^7.3.7" tsutils "^3.21.0" -"@typescript-eslint/utils@5.18.0": - version "5.18.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.18.0.tgz#27fc84cf95c1a96def0aae31684cb43a37e76855" - integrity sha512-+hFGWUMMri7OFY26TsOlGa+zgjEy1ssEipxpLjtl4wSll8zy85x0GrUSju/FHdKfVorZPYJLkF3I4XPtnCTewA== +"@typescript-eslint/utils@5.30.0": + version "5.30.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.30.0.tgz#1dac771fead5eab40d31860716de219356f5f754" + integrity sha512-0bIgOgZflLKIcZsWvfklsaQTM3ZUbmtH0rJ1hKyV3raoUYyeZwcjQ8ZUJTzS7KnhNcsVT1Rxs7zeeMHEhGlltw== dependencies: "@types/json-schema" "^7.0.9" - "@typescript-eslint/scope-manager" "5.18.0" - "@typescript-eslint/types" "5.18.0" - "@typescript-eslint/typescript-estree" "5.18.0" + "@typescript-eslint/scope-manager" "5.30.0" + "@typescript-eslint/types" "5.30.0" + "@typescript-eslint/typescript-estree" "5.30.0" eslint-scope "^5.1.1" eslint-utils "^3.0.0" -"@typescript-eslint/visitor-keys@5.18.0": - version "5.18.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.18.0.tgz#c7c07709823804171d569017f3b031ced7253e60" - integrity sha512-Hf+t+dJsjAKpKSkg3EHvbtEpFFb/1CiOHnvI8bjHgOD4/wAw3gKrA0i94LrbekypiZVanJu3McWJg7rWDMzRTg== +"@typescript-eslint/visitor-keys@5.30.0": + version "5.30.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.30.0.tgz#07721d23daca2ec4c2da7f1e660d41cd78bacac3" + integrity sha512-6WcIeRk2DQ3pHKxU1Ni0qMXJkjO/zLjBymlYBy/53qxe7yjEFSvzKLDToJjURUhSl2Fzhkl4SMXQoETauF74cw== dependencies: - "@typescript-eslint/types" "5.18.0" - eslint-visitor-keys "^3.0.0" + "@typescript-eslint/types" "5.30.0" + eslint-visitor-keys "^3.3.0" abab@^2.0.3, abab@^2.0.5: - version "2.0.5" - resolved "https://registry.yarnpkg.com/abab/-/abab-2.0.5.tgz#c0b678fb32d60fc1219c784d6a826fe385aeb79a" - integrity sha512-9IK9EadsbHo6jLWIpxpR6pL0sazTXV6+SQv25ZB+F7Bj9mJNaOc4nCRabwd5M/JwmUa8idz6Eci6eKfJryPs6Q== + version "2.0.6" + resolved "https://registry.yarnpkg.com/abab/-/abab-2.0.6.tgz#41b80f2c871d19686216b82309231cfd3cb3d291" + integrity sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA== acorn-globals@^6.0.0: version "6.0.0" @@ -1012,7 +823,7 @@ acorn-globals@^6.0.0: acorn "^7.1.1" acorn-walk "^7.1.1" -acorn-jsx@^5.3.1: +acorn-jsx@^5.3.2: version "5.3.2" resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937" integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== @@ -1027,15 +838,10 @@ acorn@^7.1.1: resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.4.1.tgz#feaed255973d2e77555b83dbc08851a6c63520fa" integrity sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A== -acorn@^8.2.4: - version "8.5.0" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.5.0.tgz#4512ccb99b3698c752591e9bb4472e38ad43cee2" - integrity sha512-yXbYeFy+jUuYd3/CDcg2NkIYE991XYX/bje7LmjJigUciaeO1JR4XxXgCIV1/Zc/dRuFEyw1L0pbA+qynJkW5Q== - -acorn@^8.7.0: - version "8.7.0" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.7.0.tgz#90951fde0f8f09df93549481e5fc141445b791cf" - integrity sha512-V/LGr1APy+PXIwKebEWrkZPwoeoF+w1jiOBUmuxuiUIaOHtob8Qc9BTrYo7VuI5fR8tqsy+buA2WFooR5olqvQ== +acorn@^8.2.4, acorn@^8.7.1: + version "8.7.1" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.7.1.tgz#0197122c843d1bf6d0a5e83220a788f278f63c30" + integrity sha512-Xx54uLJQZ19lKygFXOWsscKUbsBZW0CPykPhVQdhIeIwrbPmJzqeASDInc8nKBnp/JT6igTs82qPXz069H8I/A== agent-base@6: version "6.0.2" @@ -1061,7 +867,7 @@ ansi-escapes@^4.2.1: dependencies: type-fest "^0.21.3" -ansi-regex@^5.0.0, ansi-regex@^5.0.1: +ansi-regex@^5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== @@ -1113,7 +919,7 @@ array-union@^2.1.0: asynckit@^0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" - integrity sha1-x57Zf380y48robyXkLzDZkdLS3k= + integrity sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q== babel-jest@^27.5.1: version "27.5.1" @@ -1194,7 +1000,7 @@ brace-expansion@^1.1.7: balanced-match "^1.0.0" concat-map "0.0.1" -braces@^3.0.1: +braces@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== @@ -1206,27 +1012,15 @@ browser-process-hrtime@^1.0.0: resolved "https://registry.yarnpkg.com/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz#3c9b4b7d782c8121e56f10106d84c0d0ffc94626" integrity sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow== -browserslist@^4.16.6: - version "4.17.0" - resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.17.0.tgz#1fcd81ec75b41d6d4994fb0831b92ac18c01649c" - integrity sha512-g2BJ2a0nEYvEFQC208q8mVAhfNwpZ5Mu8BwgtCdZKO3qx98HChmeg448fPdUzld8aFmfLgVh7yymqV+q1lJZ5g== +browserslist@^4.20.2: + version "4.21.0" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.21.0.tgz#7ab19572361a140ecd1e023e2c1ed95edda0cefe" + integrity sha512-UQxE0DIhRB5z/zDz9iA03BOfxaN2+GQdBYH/2WrSIWEUrnpzTPJbhqt+umq6r3acaPRTW1FNTkrcp0PXgtFkvA== dependencies: - caniuse-lite "^1.0.30001254" - colorette "^1.3.0" - electron-to-chromium "^1.3.830" - escalade "^3.1.1" - node-releases "^1.1.75" - -browserslist@^4.17.5: - version "4.20.2" - resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.20.2.tgz#567b41508757ecd904dab4d1c646c612cd3d4f88" - integrity sha512-CQOBCqp/9pDvDbx3xfMi+86pr4KXIf2FDkTTdeuYw8OxS9t898LA1Khq57gtufFILXpfgsSx5woNgsBgvGjpsA== - dependencies: - caniuse-lite "^1.0.30001317" - electron-to-chromium "^1.4.84" - escalade "^3.1.1" - node-releases "^2.0.2" - picocolors "^1.0.0" + caniuse-lite "^1.0.30001358" + electron-to-chromium "^1.4.164" + node-releases "^2.0.5" + update-browserslist-db "^1.0.0" bs-logger@0.x: version "0.2.6" @@ -1258,19 +1052,14 @@ camelcase@^5.3.1: integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg== camelcase@^6.2.0: - version "6.2.0" - resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.2.0.tgz#924af881c9d525ac9d87f40d964e5cea982a1809" - integrity sha512-c7wVvbw3f37nuobQNtgsgG9POC9qMbNuMQmTCqZv23b6MIz0fcYpBiOlv9gEN/hdLdnZTDQhg6e9Dq5M1vKvfg== - -caniuse-lite@^1.0.30001254: - version "1.0.30001257" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001257.tgz#150aaf649a48bee531104cfeda57f92ce587f6e5" - integrity sha512-JN49KplOgHSXpIsVSF+LUyhD8PUp6xPpAXeRrrcBh4KBeP7W864jHn6RvzJgDlrReyeVjMFJL3PLpPvKIxlIHA== + version "6.3.0" + resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.3.0.tgz#5685b95eb209ac9c0c177467778c9c84df58ba9a" + integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA== -caniuse-lite@^1.0.30001317: - version "1.0.30001327" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001327.tgz#c1546d7d7bb66506f0ccdad6a7d07fc6d668c858" - integrity sha512-1/Cg4jlD9qjZzhbzkzEaAC2JHsP0WrOc8Rd/3a3LuajGzGWR/hD7TVyvq99VqmTy99eVh8Zkmdq213OgvgXx7w== +caniuse-lite@^1.0.30001358: + version "1.0.30001359" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001359.tgz#a1c1cbe1c2da9e689638813618b4219acbd4925e" + integrity sha512-Xln/BAsPzEuiVLgJ2/45IaqD9jShtk3Y33anKb4+yLwQzws3+v6odKfpgES/cDEaZMLzSChpIGdbOYtH9MyuHw== chalk@^2.0.0: version "2.4.2" @@ -1294,15 +1083,10 @@ char-regex@^1.0.2: resolved "https://registry.yarnpkg.com/char-regex/-/char-regex-1.0.2.tgz#d744358226217f981ed58f479b1d6bcc29545dcf" integrity sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw== -ci-info@^3.1.1: - version "3.2.0" - resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-3.2.0.tgz#2876cb948a498797b5236f0095bc057d0dca38b6" - integrity sha512-dVqRX7fLUm8J6FgHJ418XuIgDLZDkYcDFTeL6TA2gt5WlIZUQrrH6EZrNClwT/H0FateUsZkGIOPRrLbP+PR9A== - ci-info@^3.2.0: - version "3.3.0" - resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-3.3.0.tgz#b4ed1fb6818dea4803a55c623041f9165d2066b2" - integrity sha512-riT/3vI5YpVH6/qomlDnJow6TBee2PBKSEpx3O32EGPYbWGIRsIlGRms3Sm74wYE1JMo8RnO04Hb12+v1J5ICw== + version "3.3.2" + resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-3.3.2.tgz#6d2967ffa407466481c6c90b6e16b3098f080128" + integrity sha512-xmDt/QIAdeZ9+nfdPsaBCpMvHNLFiLdjj59qjqn+6iPe6YmHGQ35sBnQ8uslRBXFmXkiZQOJRjvQeoGppoTjjg== cjs-module-lexer@^1.0.0: version "1.2.2" @@ -1321,7 +1105,7 @@ cliui@^7.0.2: co@^4.6.0: version "4.6.0" resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184" - integrity sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ= + integrity sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ== collect-v8-coverage@^1.0.0: version "1.0.1" @@ -1345,18 +1129,13 @@ color-convert@^2.0.1: color-name@1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" - integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU= + integrity sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw== color-name@~1.1.4: version "1.1.4" resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== -colorette@^1.3.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/colorette/-/colorette-1.4.0.tgz#5190fbb87276259a86ad700bff2c6d6faa3fca40" - integrity sha512-Y2oEozpomLn7Q3HFP7dpww7AtMJplbM9lGZP6RDfHqmbeRjiwRg4n6VM6j4KLmRke85uWEI7JqF17f3pqdRA0g== - combined-stream@^1.0.8: version "1.0.8" resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" @@ -1367,7 +1146,7 @@ combined-stream@^1.0.8: concat-map@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" - integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= + integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== convert-source-map@^1.4.0, convert-source-map@^1.6.0, convert-source-map@^1.7.0: version "1.8.0" @@ -1429,17 +1208,10 @@ data-urls@^2.0.0: whatwg-mimetype "^2.3.0" whatwg-url "^8.0.0" -debug@4, debug@^4.1.0, debug@^4.1.1: - version "4.3.2" - resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.2.tgz#f0a49c18ac8779e31d4a0c6029dfb76873c7428b" - integrity sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw== - dependencies: - ms "2.1.2" - -debug@^4.3.2: - version "4.3.3" - resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.3.tgz#04266e0b70a98d4462e6e288e38259213332b664" - integrity sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q== +debug@4, debug@^4.1.0, debug@^4.1.1, debug@^4.3.2, debug@^4.3.4: + version "4.3.4" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" + integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== dependencies: ms "2.1.2" @@ -1451,7 +1223,7 @@ decimal.js@^10.2.1: dedent@^0.7.0: version "0.7.0" resolved "https://registry.yarnpkg.com/dedent/-/dedent-0.7.0.tgz#2495ddbaf6eb874abb0e1be9df22d2e5a544326c" - integrity sha1-JJXduvbrh0q7Dhvp3yLS5aVEMmw= + integrity sha512-Q6fKUPqnAHAyhiUgFU7BUzLiv0kd8saH9al7tnu5Q/okj6dnupxyTgFIBjVzJATdfIAm9NAsvXNzjaKa+bxVyA== deep-is@^0.1.3, deep-is@~0.1.3: version "0.1.4" @@ -1466,7 +1238,7 @@ deepmerge@^4.2.2: delayed-stream@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" - integrity sha1-3zrhmayt+31ECqrgsp4icrJOxhk= + integrity sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ== detect-newline@^3.0.0: version "3.1.0" @@ -1499,15 +1271,10 @@ domexception@^2.0.1: dependencies: webidl-conversions "^5.0.0" -electron-to-chromium@^1.3.830: - version "1.3.841" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.841.tgz#c3088948462df0dea552877ad0902df6d0d55de0" - integrity sha512-0w3dJaRfl4A+LfbdvZaGT3JPm6TbTYKeJj8y5YWaEI9Z4WBNIshlzxL3S1msMGpuhiMZQE9cfgAV8oMib+Z4Tg== - -electron-to-chromium@^1.4.84: - version "1.4.106" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.106.tgz#e7a3bfa9d745dd9b9e597616cb17283cc349781a" - integrity sha512-ZYfpVLULm67K7CaaGP7DmjyeMY4naxsbTy+syVVxT6QHI1Ww8XbJjmr9fDckrhq44WzCrcC5kH3zGpdusxwwqg== +electron-to-chromium@^1.4.164: + version "1.4.170" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.170.tgz#0415fc489402e09bfbe1f0c99bbf4d73f31d48d4" + integrity sha512-rZ8PZLhK4ORPjFqLp9aqC4/S1j4qWFsPPz13xmWdrbBkU/LlxMcok+f+6f8YnQ57MiZwKtOaW15biZZsY5Igvw== emittery@^0.8.1: version "0.8.1" @@ -1534,7 +1301,7 @@ escalade@^3.1.1: escape-string-regexp@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" - integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ= + integrity sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg== escape-string-regexp@^2.0.0: version "2.0.0" @@ -1586,22 +1353,17 @@ eslint-visitor-keys@^2.0.0: resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz#f65328259305927392c938ed44eb0a5c9b2bd303" integrity sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw== -eslint-visitor-keys@^3.0.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.1.0.tgz#eee4acea891814cda67a7d8812d9647dd0179af2" - integrity sha512-yWJFpu4DtjsWKkt5GeNBBuZMlNcYVs6vRCLoCVEJrTjaSB6LC98gFipNK/erM2Heg/E8mIK+hXG/pJMLK+eRZA== - eslint-visitor-keys@^3.3.0: version "3.3.0" resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz#f6480fa6b1f30efe2d1968aa8ac745b862469826" integrity sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA== eslint@^8.12.0: - version "8.12.0" - resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.12.0.tgz#c7a5bd1cfa09079aae64c9076c07eada66a46e8e" - integrity sha512-it1oBL9alZg1S8UycLm5YDMAkIhtH6FtAzuZs6YvoGVldWjbS08BkAdb/ymP9LlAyq8koANu32U7Ib/w+UNh8Q== + version "8.18.0" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.18.0.tgz#78d565d16c993d0b73968c523c0446b13da784fd" + integrity sha512-As1EfFMVk7Xc6/CvhssHUjsAQSkpfXvUGMFC3ce8JDe6WvqCgRrLOBQbVpsBFr1X1V+RACOadnzVvcUS5ni2bA== dependencies: - "@eslint/eslintrc" "^1.2.1" + "@eslint/eslintrc" "^1.3.0" "@humanwhocodes/config-array" "^0.9.2" ajv "^6.10.0" chalk "^4.0.0" @@ -1612,14 +1374,14 @@ eslint@^8.12.0: eslint-scope "^7.1.1" eslint-utils "^3.0.0" eslint-visitor-keys "^3.3.0" - espree "^9.3.1" + espree "^9.3.2" esquery "^1.4.0" esutils "^2.0.2" fast-deep-equal "^3.1.3" file-entry-cache "^6.0.1" functional-red-black-tree "^1.0.1" glob-parent "^6.0.1" - globals "^13.6.0" + globals "^13.15.0" ignore "^5.2.0" import-fresh "^3.0.0" imurmurhash "^0.1.4" @@ -1628,7 +1390,7 @@ eslint@^8.12.0: json-stable-stringify-without-jsonify "^1.0.1" levn "^0.4.1" lodash.merge "^4.6.2" - minimatch "^3.0.4" + minimatch "^3.1.2" natural-compare "^1.4.0" optionator "^0.9.1" regexpp "^3.2.0" @@ -1637,13 +1399,13 @@ eslint@^8.12.0: text-table "^0.2.0" v8-compile-cache "^2.0.3" -espree@^9.3.1: - version "9.3.1" - resolved "https://registry.yarnpkg.com/espree/-/espree-9.3.1.tgz#8793b4bc27ea4c778c19908e0719e7b8f4115bcd" - integrity sha512-bvdyLmJMfwkV3NCRl5ZhJf22zBFo1y8bYh3VYb+bfzqNB4Je68P2sSuXyuFquzWLebHpNd2/d5uv7yoP9ISnGQ== +espree@^9.3.2: + version "9.3.2" + resolved "https://registry.yarnpkg.com/espree/-/espree-9.3.2.tgz#f58f77bd334731182801ced3380a8cc859091596" + integrity sha512-D211tC7ZwouTIuY5x9XnS0E9sWNChB7IYKX/Xp5eQj3nFXhqmiUDB9q27y76oFl8jTg3pXcQx/bpxMfs3CIZbA== dependencies: - acorn "^8.7.0" - acorn-jsx "^5.3.1" + acorn "^8.7.1" + acorn-jsx "^5.3.2" eslint-visitor-keys "^3.3.0" esprima@^4.0.0, esprima@^4.0.1: @@ -1670,16 +1432,11 @@ estraverse@^4.1.1: resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.3.0.tgz#398ad3f3c5a24948be7725e83d11a7de28cdbd1d" integrity sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw== -estraverse@^5.1.0: +estraverse@^5.1.0, estraverse@^5.2.0: version "5.3.0" resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.3.0.tgz#2eea5290702f26ab8fe5370370ff86c965d21123" integrity sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA== -estraverse@^5.2.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.2.0.tgz#307df42547e6cc7324d3cf03c155d5cdb8c53880" - integrity sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ== - esutils@^2.0.2: version "2.0.3" resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" @@ -1703,7 +1460,7 @@ execa@^5.0.0: exit@^0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/exit/-/exit-0.1.2.tgz#0632638f8d877cc82107d30a0fff1a17cba1cd0c" - integrity sha1-BjJjj42HfMghB9MKD/8aF8uhzQw= + integrity sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ== expect@^27.5.1: version "27.5.1" @@ -1716,9 +1473,9 @@ expect@^27.5.1: jest-message-util "^27.5.1" fast-check@^2.24.0: - version "2.24.0" - resolved "https://registry.yarnpkg.com/fast-check/-/fast-check-2.24.0.tgz#39f85586862108a4de6394c5196ebcf8b76b6c8b" - integrity sha512-iNXbN90lbabaCUfnW5jyXYPwMJLFYl09eJDkXA9ZoidFlBK63gNRvcKxv+8D1OJ1kIYjwBef4bO/K3qesUeWLQ== + version "2.25.0" + resolved "https://registry.yarnpkg.com/fast-check/-/fast-check-2.25.0.tgz#5146601851bf3be0953bd17eb2b7d547936c6561" + integrity sha512-wRUT2KD2lAmT75WNIJIHECawoUUMHM0I5jrlLXGtGeqmPL8jl/EldUDjY1VCp6fDY8yflyfUeIOsOBrIbIiArg== dependencies: pure-rand "^5.0.1" @@ -1727,10 +1484,10 @@ fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== -fast-glob@^3.1.1: - version "3.2.7" - resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.7.tgz#fd6cb7a2d7e9aa7a7846111e85a196d6b2f766a1" - integrity sha512-rYGMRwip6lUMvYD3BTScMwT1HtAs2d71SMv66Vrxs0IekGZEjhM0pcMfjQPnknBt2zeCwQMEupiN02ZP4DiT1Q== +fast-glob@^3.2.9: + version "3.2.11" + resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.11.tgz#a1172ad95ceb8a16e20caa5c5e56480e5129c1d9" + integrity sha512-xrO3+1bxSo3ZVHAnqzyuewYT6aMFHRAd4Kcs92MAonjwQZLsK9d0SF1IyQ3k5PoirxTW0Oe/RqFgMQ6TcNE5Ew== dependencies: "@nodelib/fs.stat" "^2.0.2" "@nodelib/fs.walk" "^1.2.3" @@ -1746,7 +1503,7 @@ fast-json-stable-stringify@2.x, fast-json-stable-stringify@^2.0.0: fast-levenshtein@^2.0.6, fast-levenshtein@~2.0.6: version "2.0.6" resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" - integrity sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc= + integrity sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw== fastq@^1.6.0: version "1.13.0" @@ -1793,9 +1550,9 @@ flat-cache@^3.0.4: rimraf "^3.0.2" flatted@^3.1.0: - version "3.2.4" - resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.2.4.tgz#28d9969ea90661b5134259f312ab6aa7929ac5e2" - integrity sha512-8/sOawo8tJ4QOBX8YlQBMxL8+RLZfxMQOif9o0KUKTNTjMYElWPE0r/m5VNFxTRd0NSw8qSy8dajrwX4RYI1Hw== + version "3.2.6" + resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.2.6.tgz#022e9218c637f9f3fc9c35ab9c9193f05add60b2" + integrity sha512-0sQoMh9s0BYsm+12Huy/rkKxVu4R1+r96YX5cG44rHV0pQ6iC3Q+mkoMFaGWObMFYQxCVT+ssG1ksneA2MI9KQ== form-data@^3.0.0: version "3.0.1" @@ -1809,7 +1566,7 @@ form-data@^3.0.0: fs.realpath@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" - integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8= + integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== fsevents@^2.3.2: version "2.3.2" @@ -1824,7 +1581,7 @@ function-bind@^1.1.1: functional-red-black-tree@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz#1b0ab3bd553b2a0d6399d29c0e3ea0b252078327" - integrity sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc= + integrity sha512-dsKNQNdj6xA3T+QlADDA7mOSlX0qiMINjn0cgr+eGHGsbSHzTabcIogz2+p/iqP1Xs6EP/sS2SbqH+brGTbq0g== gensync@^1.0.0-beta.2: version "1.0.0-beta.2" @@ -1860,27 +1617,15 @@ glob-parent@^6.0.1: dependencies: is-glob "^4.0.3" -glob@^7.0.5: - version "7.2.0" - resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.0.tgz#d15535af7732e02e948f4c41628bd910293f6023" - integrity sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q== +glob@^7.0.5, glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4: + version "7.2.3" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b" + integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q== dependencies: fs.realpath "^1.0.0" inflight "^1.0.4" inherits "2" - minimatch "^3.0.4" - once "^1.3.0" - path-is-absolute "^1.0.0" - -glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4: - version "7.1.7" - resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.7.tgz#3b193e9233f01d42d0b3f78294bbeeb418f94a90" - integrity sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ== - dependencies: - fs.realpath "^1.0.0" - inflight "^1.0.4" - inherits "2" - minimatch "^3.0.4" + minimatch "^3.1.1" once "^1.3.0" path-is-absolute "^1.0.0" @@ -1889,30 +1634,25 @@ globals@^11.1.0: resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e" integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA== -globals@^13.6.0, globals@^13.9.0: - version "13.12.0" - resolved "https://registry.yarnpkg.com/globals/-/globals-13.12.0.tgz#4d733760304230a0082ed96e21e5c565f898089e" - integrity sha512-uS8X6lSKN2JumVoXrbUz+uG4BYG+eiawqm3qFcT7ammfbUHeCBoJMlHcec/S3krSk73/AE/f0szYFmgAA3kYZg== +globals@^13.15.0: + version "13.15.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-13.15.0.tgz#38113218c907d2f7e98658af246cef8b77e90bac" + integrity sha512-bpzcOlgDhMG070Av0Vy5Owklpv1I6+j96GhUI7Rh7IzDCKLzboflLrrfqMu8NquDbiR4EOQk7XzJwqVJxicxog== dependencies: type-fest "^0.20.2" -globby@^11.0.4: - version "11.0.4" - resolved "https://registry.yarnpkg.com/globby/-/globby-11.0.4.tgz#2cbaff77c2f2a62e71e9b2813a67b97a3a3001a5" - integrity sha512-9O4MVG9ioZJ08ffbcyVYyLOJLk5JQ688pJ4eMGLpdWLHq/Wr1D9BlriLQyL0E+jbkuePVZXYFj47QM/v093wHg== +globby@^11.1.0: + version "11.1.0" + resolved "https://registry.yarnpkg.com/globby/-/globby-11.1.0.tgz#bd4be98bb042f83d796f7e3811991fbe82a0d34b" + integrity sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g== dependencies: array-union "^2.1.0" dir-glob "^3.0.1" - fast-glob "^3.1.1" - ignore "^5.1.4" - merge2 "^1.3.0" + fast-glob "^3.2.9" + ignore "^5.2.0" + merge2 "^1.4.1" slash "^3.0.0" -graceful-fs@^4.2.4: - version "4.2.8" - resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.8.tgz#e412b8d33f5e006593cbd3cee6df9f2cebbe802a" - integrity sha512-qkIilPUYcNhJpd33n0GBXTB1MMPp14TxEsEs0pTrsSVucApsYzW5V+Q8Qxhik6KU3evy+qkAAowTByymK0avdg== - graceful-fs@^4.2.9: version "4.2.10" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.10.tgz#147d3a006da4ca3ce14728c7aefc287c367d7a6c" @@ -1921,7 +1661,7 @@ graceful-fs@^4.2.9: has-flag@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" - integrity sha1-tdRU3CGZriJWmfNGfloH87lVuv0= + integrity sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw== has-flag@^4.0.0: version "4.0.0" @@ -1957,9 +1697,9 @@ http-proxy-agent@^4.0.1: debug "4" https-proxy-agent@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz#e2a90542abb68a762e0a0850f6c9edadfd8506b2" - integrity sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA== + version "5.0.1" + resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz#c59ef224a04fe8b754f3db0063a25ea30d0005d6" + integrity sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA== dependencies: agent-base "6" debug "4" @@ -1976,11 +1716,6 @@ iconv-lite@0.4.24: dependencies: safer-buffer ">= 2.1.2 < 3" -ignore@^5.1.4, ignore@^5.1.8: - version "5.1.9" - resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.1.9.tgz#9ec1a5cbe8e1446ec60d4420060d43aa6e7382fb" - integrity sha512-2zeMQpbKz5dhZ9IwL0gbxSW5w0NK/MSAMtNuhgIHEPmaU3vPdKPL0UdvUCXs5SS4JAwsBxysK5sFMW8ocFiVjQ== - ignore@^5.2.0: version "5.2.0" resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.2.0.tgz#6d3bac8fa7fe0d45d9f9be7bac2fc279577e345a" @@ -1995,9 +1730,9 @@ import-fresh@^3.0.0, import-fresh@^3.2.1: resolve-from "^4.0.0" import-local@^3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/import-local/-/import-local-3.0.2.tgz#a8cfd0431d1de4a2199703d003e3e62364fa6db6" - integrity sha512-vjL3+w0oulAVZ0hBHnxa/Nm5TAurf9YLQJDhqRZyqb+VKGOB6LU8t9H1Nr5CIo16vh9XfJTOoHwU0B71S557gA== + version "3.1.0" + resolved "https://registry.yarnpkg.com/import-local/-/import-local-3.1.0.tgz#b4479df8a5fd44f6cdce24070675676063c95cb4" + integrity sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg== dependencies: pkg-dir "^4.2.0" resolve-cwd "^3.0.0" @@ -2005,12 +1740,12 @@ import-local@^3.0.2: imurmurhash@^0.1.4: version "0.1.4" resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" - integrity sha1-khi5srkoojixPcT7a21XbyMUU+o= + integrity sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA== inflight@^1.0.4: version "1.0.6" resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" - integrity sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk= + integrity sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA== dependencies: once "^1.3.0" wrappy "1" @@ -2023,26 +1758,19 @@ inherits@2, inherits@^2.0.1, inherits@~2.0.1, inherits@~2.0.3: is-arrayish@^0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" - integrity sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0= - -is-ci@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/is-ci/-/is-ci-3.0.0.tgz#c7e7be3c9d8eef7d0fa144390bd1e4b88dc4c994" - integrity sha512-kDXyttuLeslKAHYL/K28F2YkM3x5jvFPEw3yXbRptXydjD9rpLEz+C5K5iutY9ZiUu6AP41JdvRQwF4Iqs4ZCQ== - dependencies: - ci-info "^3.1.1" + integrity sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg== -is-core-module@^2.2.0: - version "2.6.0" - resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.6.0.tgz#d7553b2526fe59b92ba3e40c8df757ec8a709e19" - integrity sha512-wShG8vs60jKfPWpF2KZRaAtvt3a20OAn7+IJ6hLPECpSABLcKtFKTTI4ZtH5QcBruBHlq+WsdHWyz0BCZW7svQ== +is-core-module@^2.9.0: + version "2.9.0" + resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.9.0.tgz#e1c34429cd51c6dd9e09e0799e396e27b19a9c69" + integrity sha512-+5FPy5PnwmO3lvfMb0AsoPaBG+5KHUI0wYFXOtYPnVVVspTFUuMZNfNaNVRt3FZadstu2c8x23vykRW/NBoU6A== dependencies: has "^1.0.3" is-extglob@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" - integrity sha1-qIwCU1eR8C7TfHahueqXc8gz+MI= + integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ== is-fullwidth-code-point@^3.0.0: version "3.0.0" @@ -2079,37 +1807,32 @@ is-stream@^2.0.0: is-typedarray@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" - integrity sha1-5HnICFjfDBsR3dppQPlgEfzaSpo= + integrity sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA== isarray@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/isarray/-/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf" - integrity sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8= + integrity sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ== isarray@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" - integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE= + integrity sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ== isexe@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" - integrity sha1-6PvzdNxVb/iUehDcsFctYz8s+hA= - -istanbul-lib-coverage@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-3.0.0.tgz#f5944a37c70b550b02a78a5c3b2055b280cec8ec" - integrity sha512-UiUIqxMgRDET6eR+o5HbfRYP1l0hqkWOs7vNxC/mggutCMUIhWMm8gAHb8tHlyfD3/l6rlgNA5cKdDzEAf6hEg== + integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== -istanbul-lib-coverage@^3.2.0: +istanbul-lib-coverage@^3.0.0, istanbul-lib-coverage@^3.2.0: version "3.2.0" resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz#189e7909d0a39fa5a3dfad5b03f71947770191d3" integrity sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw== istanbul-lib-instrument@^5.0.4, istanbul-lib-instrument@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-5.1.0.tgz#7b49198b657b27a730b8e9cb601f1e1bff24c59a" - integrity sha512-czwUz525rkOFDJxfKK6mYfIs9zBKILyrZQxjz3ABhjQXhbhFsSbo1HW/BFcsDnfJYJWA6thRR5/TUY2qs5W99Q== + version "5.2.0" + resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.0.tgz#31d18bdd127f825dd02ea7bfdfd906f8ab840e9f" + integrity sha512-6Lthe1hqXHBNsqvgDzGO6l03XNeu3CrG4RqQ1KM9+l5+jNGpEJfIELx1NS3SEHmJQA8np/u+E4EPRKRiu6m19A== dependencies: "@babel/core" "^7.12.3" "@babel/parser" "^7.14.7" @@ -2127,9 +1850,9 @@ istanbul-lib-report@^3.0.0: supports-color "^7.1.0" istanbul-lib-source-maps@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.0.tgz#75743ce6d96bb86dc7ee4352cf6366a23f0b1ad9" - integrity sha512-c16LpFRkR8vQXyHZ5nLpY35JZtzj1PQY1iZmesUbf1FZHbIupcWfjgOXBY9YHkLEQ6puz1u4Dgj6qmU/DisrZg== + version "4.0.1" + resolved "https://registry.yarnpkg.com/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz#895f3a709fcfba34c6de5a42939022f3e4358551" + integrity sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw== dependencies: debug "^4.1.1" istanbul-lib-coverage "^3.0.0" @@ -2493,19 +2216,7 @@ jest-snapshot@^27.5.1: pretty-format "^27.5.1" semver "^7.3.2" -jest-util@^27.0.0: - version "27.2.0" - resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-27.2.0.tgz#bfccb85cfafae752257319e825a5b8d4ada470dc" - integrity sha512-T5ZJCNeFpqcLBpx+Hl9r9KoxBCUqeWlJ1Htli+vryigZVJ1vuLB9j35grEBASp4R13KFkV7jM52bBGnArpJN6A== - dependencies: - "@jest/types" "^27.1.1" - "@types/node" "*" - chalk "^4.0.0" - graceful-fs "^4.2.4" - is-ci "^3.0.0" - picomatch "^2.2.3" - -jest-util@^27.5.1: +jest-util@^27.0.0, jest-util@^27.5.1: version "27.5.1" resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-27.5.1.tgz#3ba9771e8e31a0b85da48fe0b0891fb86c01c2f9" integrity sha512-Kv2o/8jNvX1MQ0KGtw480E/w4fBCDOnH6+6DmeKi6LZUIlKA5kwY0YNdlzaWTiVgxqAqik11QyxDOKk543aKXw== @@ -2631,16 +2342,9 @@ json-schema-traverse@^0.4.1: json-stable-stringify-without-jsonify@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651" - integrity sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE= - -json5@2.x, json5@^2.1.2: - version "2.2.0" - resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.0.tgz#2dfefe720c6ba525d9ebd909950f0515316c89a3" - integrity sha512-f+8cldu7X/y7RAJurMEJmdoKXGB/X550w2Nr3tTbezL6RwEE/iMcm+tZnXeoZtKuOq6ft8+CqzEkrIgx1fPoQA== - dependencies: - minimist "^1.2.5" + integrity sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw== -json5@^2.2.1: +json5@2.x, json5@^2.2.1: version "2.2.1" resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.1.tgz#655d50ed1e6f95ad1a3caababd2b0efda10b395c" integrity sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA== @@ -2666,7 +2370,7 @@ levn@^0.4.1: levn@~0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/levn/-/levn-0.3.0.tgz#3b09924edf9f083c0490fdd4c0bc4421e04764ee" - integrity sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4= + integrity sha512-0OO4y2iOHix2W6ujICbKIaEQXvFQHue65vUG3pb5EUomzPI90z9hsA1VsO/dbIIpC53J8gxM9Q4Oho0jrCM/yA== dependencies: prelude-ls "~1.1.2" type-check "~0.3.2" @@ -2686,7 +2390,7 @@ locate-path@^5.0.0: lodash.memoize@4.x: version "4.1.2" resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe" - integrity sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4= + integrity sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag== lodash.merge@^4.6.2: version "4.6.2" @@ -2717,67 +2421,55 @@ make-error@1.x: resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.6.tgz#2eb2e37ea9b67c4891f684a1394799af484cf7a2" integrity sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw== -makeerror@1.0.x: - version "1.0.11" - resolved "https://registry.yarnpkg.com/makeerror/-/makeerror-1.0.11.tgz#e01a5c9109f2af79660e4e8b9587790184f5a96c" - integrity sha1-4BpckQnyr3lmDk6LlYd5AYT1qWw= +makeerror@1.0.12: + version "1.0.12" + resolved "https://registry.yarnpkg.com/makeerror/-/makeerror-1.0.12.tgz#3e5dd2079a82e812e983cc6610c4a2cb0eaa801a" + integrity sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg== dependencies: - tmpl "1.0.x" + tmpl "1.0.5" merge-stream@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60" integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w== -merge2@^1.3.0: +merge2@^1.3.0, merge2@^1.4.1: version "1.4.1" resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae" integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== micromatch@^4.0.4: - version "4.0.4" - resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.4.tgz#896d519dfe9db25fce94ceb7a500919bf881ebf9" - integrity sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg== + version "4.0.5" + resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.5.tgz#bc8999a7cbbf77cdc89f132f6e467051b49090c6" + integrity sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA== dependencies: - braces "^3.0.1" - picomatch "^2.2.3" + braces "^3.0.2" + picomatch "^2.3.1" -mime-db@1.49.0: - version "1.49.0" - resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.49.0.tgz#f3dfde60c99e9cf3bc9701d687778f537001cbed" - integrity sha512-CIc8j9URtOVApSFCQIF+VBkX1RwXp/oMMOrqdyXSBXq5RWNEsRfyj1kiRnQgmNXmHxPoFIxOroKA3zcU9P+nAA== +mime-db@1.52.0: + version "1.52.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70" + integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== mime-types@^2.1.12: - version "2.1.32" - resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.32.tgz#1d00e89e7de7fe02008db61001d9e02852670fd5" - integrity sha512-hJGaVS4G4c9TSMYh2n6SQAGrC4RnfU+daP8G7cSCmaqNjiOoUY0VHCMS42pxnQmVF1GWwFhbHWn3RIxCqTmZ9A== + version "2.1.35" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a" + integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== dependencies: - mime-db "1.49.0" + mime-db "1.52.0" mimic-fn@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== -minimatch@^3.0.3: +minimatch@^3.0.3, minimatch@^3.0.4, minimatch@^3.1.1, minimatch@^3.1.2: version "3.1.2" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== dependencies: brace-expansion "^1.1.7" -minimatch@^3.0.4: - version "3.0.4" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" - integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA== - dependencies: - brace-expansion "^1.1.7" - -minimist@^1.2.5: - version "1.2.5" - resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602" - integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw== - mkdirp@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e" @@ -2789,34 +2481,29 @@ ms@2.1.2: integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== multiformats@^9.4.2: - version "9.4.7" - resolved "https://registry.yarnpkg.com/multiformats/-/multiformats-9.4.7.tgz#df2caa6ccd975218bbbacd69e45bb76cc2a53654" - integrity sha512-fZbcdf7LnvokPAZYkv4TLXe7PAg9sQ5qLXcwrAmZOloEb2+5FtFiAY+l3/9wsu4oTJXTV3JSggFQQ2dJLS01vA== + version "9.7.0" + resolved "https://registry.yarnpkg.com/multiformats/-/multiformats-9.7.0.tgz#845799e8df70fbb6b15922500e45cb87cf12f7e5" + integrity sha512-uv/tcgwk0yN4DStopnBN4GTgvaAlYdy6KnZpuzEPFOYQd71DYFJjs0MN1ERElAflrZaYyGBWXyGxL5GgrxIx0Q== natural-compare@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" - integrity sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc= + integrity sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw== node-int64@^0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/node-int64/-/node-int64-0.4.0.tgz#87a9065cdb355d3182d8f94ce11188b825c68a3b" - integrity sha1-h6kGXNs1XTGC2PlM4RGIuCXGijs= - -node-releases@^1.1.75: - version "1.1.75" - resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.75.tgz#6dd8c876b9897a1b8e5a02de26afa79bb54ebbfe" - integrity sha512-Qe5OUajvqrqDSy6wrWFmMwfJ0jVgwiw4T3KqmbTcZ62qW0gQkheXYhcFM1+lOVcGUoRxcEcfyvFMAnDgaF1VWw== + integrity sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw== -node-releases@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.2.tgz#7139fe71e2f4f11b47d4d2986aaf8c48699e0c01" - integrity sha512-XxYDdcQ6eKqp/YjI+tb2C5WM2LgjnZrfYg4vgQt49EK268b6gYCHsBLrK2qvJo4FmCtqmKezb0WZFK4fkrZNsg== +node-releases@^2.0.5: + version "2.0.5" + resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.5.tgz#280ed5bc3eba0d96ce44897d8aee478bfb3d9666" + integrity sha512-U9h1NLROZTq9uE1SNffn6WuPDg8icmi3ns4rEl/oTfIle4iLjTliCzgTsbaIFMq/Xn078/lfY/BL0GWZ+psK4Q== noms@0.0.0: version "0.0.0" resolved "https://registry.yarnpkg.com/noms/-/noms-0.0.0.tgz#da8ebd9f3af9d6760919b27d9cdc8092a7332859" - integrity sha1-2o69nzr51nYJGbJ9nNyAkqczKFk= + integrity sha512-lNDU9VJaOPxUmXcLb+HQFeUgQQPtMI24Gt6hgfuMHRJgMRHMF/qZ4HJD3GDru4sSw9IQl2jPjAYnQrdIeLbwow== dependencies: inherits "^2.0.1" readable-stream "~1.0.31" @@ -2834,14 +2521,14 @@ npm-run-path@^4.0.1: path-key "^3.0.0" nwsapi@^2.2.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/nwsapi/-/nwsapi-2.2.0.tgz#204879a9e3d068ff2a55139c2c772780681a38b7" - integrity sha512-h2AatdwYH+JHiZpv7pt/gSX1XoRGb7L/qSIeuqA6GwYoF9w1vP1cw42TO0aI2pNyshRK5893hNSl+1//vHK7hQ== + version "2.2.1" + resolved "https://registry.yarnpkg.com/nwsapi/-/nwsapi-2.2.1.tgz#10a9f268fbf4c461249ebcfe38e359aa36e2577c" + integrity sha512-JYOWTeFoS0Z93587vRJgASD5Ut11fYl5NyihP3KrYBvMe1FRRs6RN7m20SA/16GM4P6hTnZjT+UmDOt38UeXNg== once@^1.3.0: version "1.4.0" resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" - integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E= + integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w== dependencies: wrappy "1" @@ -2930,14 +2617,14 @@ path-exists@^4.0.0: path-is-absolute@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" - integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18= + integrity sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg== path-key@^3.0.0, path-key@^3.1.0: version "3.1.1" resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== -path-parse@^1.0.6: +path-parse@^1.0.7: version "1.0.7" resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== @@ -2952,10 +2639,10 @@ picocolors@^1.0.0: resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c" integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ== -picomatch@^2.0.4, picomatch@^2.2.3: - version "2.3.0" - resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.0.tgz#f1f061de8f6a4bf022892e2d128234fb98302972" - integrity sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw== +picomatch@^2.0.4, picomatch@^2.2.3, picomatch@^2.3.1: + version "2.3.1" + resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" + integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== pirates@^4.0.4: version "4.0.5" @@ -2977,19 +2664,9 @@ prelude-ls@^1.2.1: prelude-ls@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54" - integrity sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ= + integrity sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w== -pretty-format@^27.0.0: - version "27.2.0" - resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-27.2.0.tgz#ee37a94ce2a79765791a8649ae374d468c18ef19" - integrity sha512-KyJdmgBkMscLqo8A7K77omgLx5PWPiXJswtTtFV7XgVZv2+qPk6UivpXXO+5k6ZEbWIbLoKdx1pZ6ldINzbwTA== - dependencies: - "@jest/types" "^27.1.1" - ansi-regex "^5.0.0" - ansi-styles "^5.0.0" - react-is "^17.0.1" - -pretty-format@^27.5.1: +pretty-format@^27.0.0, pretty-format@^27.5.1: version "27.5.1" resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-27.5.1.tgz#2181879fdea51a7a5851fb39d920faa63f01d88e" integrity sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ== @@ -3004,9 +2681,9 @@ process-nextick-args@~2.0.0: integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag== prompts@^2.0.1: - version "2.4.1" - resolved "https://registry.yarnpkg.com/prompts/-/prompts-2.4.1.tgz#befd3b1195ba052f9fd2fde8a486c4e82ee77f61" - integrity sha512-EQyfIuO2hPDsX1L/blblV+H7I0knhgAd82cVneCwcdND9B8AuCDuRcBH6yIcG4dFzlOUqbazQqwGjx5xmsNLuQ== + version "2.4.2" + resolved "https://registry.yarnpkg.com/prompts/-/prompts-2.4.2.tgz#7b57e73b3a48029ad10ebd44f74b01722a4cb069" + integrity sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q== dependencies: kleur "^3.0.3" sisteransi "^1.0.5" @@ -3039,7 +2716,7 @@ react-is@^17.0.1: readable-stream@~1.0.31: version "1.0.34" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-1.0.34.tgz#125820e34bc842d2f2aaafafe4c2916ee32c157c" - integrity sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw= + integrity sha512-ok1qVCJuRkNmvebYikljxJA/UEsKwLl2nI1OmaqAu4/UE+h0wKCHok4XkL/gvi39OacXvw59RJUOFUkDib2rHg== dependencies: core-util-is "~1.0.0" inherits "~2.0.1" @@ -3067,7 +2744,7 @@ regexpp@^3.2.0: require-directory@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" - integrity sha1-jGStX9MNqxyXbiNE/+f3kqam30I= + integrity sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q== resolve-cwd@^3.0.0: version "3.0.0" @@ -3092,12 +2769,13 @@ resolve.exports@^1.1.0: integrity sha512-J1l+Zxxp4XK3LUDZ9m60LRJF/mAe4z6a4xyabPHk7pvK5t35dACV32iIjJDFeWZFfZlO29w6SZ67knR0tHzJtQ== resolve@^1.20.0: - version "1.20.0" - resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.20.0.tgz#629a013fb3f70755d6f0b7935cc1c2c5378b1975" - integrity sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A== + version "1.22.1" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.1.tgz#27cb2ebb53f91abb49470a928bba7558066ac177" + integrity sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw== dependencies: - is-core-module "^2.2.0" - path-parse "^1.0.6" + is-core-module "^2.9.0" + path-parse "^1.0.7" + supports-preserve-symlinks-flag "^1.0.0" reusify@^1.0.4: version "1.0.4" @@ -3135,10 +2813,10 @@ saxes@^5.0.1: dependencies: xmlchars "^2.2.0" -semver@7.x, semver@^7.3.2, semver@^7.3.5: - version "7.3.5" - resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.5.tgz#0b621c879348d8998e4b0e4be94b3f12e6018ef7" - integrity sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ== +semver@7.x, semver@^7.3.2, semver@^7.3.7: + version "7.3.7" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.7.tgz#12c5b649afdbf9049707796e22a4028814ce523f" + integrity sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g== dependencies: lru-cache "^6.0.0" @@ -3160,9 +2838,9 @@ shebang-regex@^3.0.0: integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== signal-exit@^3.0.2, signal-exit@^3.0.3: - version "3.0.4" - resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.4.tgz#366a4684d175b9cab2081e3681fda3747b6c51d7" - integrity sha512-rqYhcAnZ6d/vTPGghdrw7iumdcbXpsk1b8IG/rz+VWV51DM0p7XCtMoJ3qhPLIbp3tvyt3pKRbaaEMZYpHto8Q== + version "3.0.7" + resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9" + integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== sisteransi@^1.0.5: version "1.0.5" @@ -3174,41 +2852,35 @@ slash@^3.0.0: resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q== -source-map-support@^0.5.20, source-map-support@^0.5.6: - version "0.5.20" - resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.20.tgz#12166089f8f5e5e8c56926b377633392dd2cb6c9" - integrity sha512-n1lZZ8Ve4ksRqizaBQgxXDgKwttHDhyfQjA6YZZn8+AroHbsIz+JjwxQDxbp+7y5OYCI8t1Yk7etjD9CRd2hIw== +source-map-support@^0.5.6: + version "0.5.21" + resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.21.tgz#04fe7c7f9e1ed2d662233c28cb2b35b9f63f6e4f" + integrity sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w== dependencies: buffer-from "^1.0.0" source-map "^0.6.0" -source-map@^0.5.0: - version "0.5.7" - resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" - integrity sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w= - source-map@^0.6.0, source-map@^0.6.1, source-map@~0.6.1: version "0.6.1" resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== source-map@^0.7.3: - version "0.7.3" - resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.7.3.tgz#5302f8169031735226544092e64981f751750383" - integrity sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ== + version "0.7.4" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.7.4.tgz#a9bbe705c9d8846f4e08ff6765acf0f1b0898656" + integrity sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA== sprintf-js@~1.0.2: version "1.0.3" resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" - integrity sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw= + integrity sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g== stack-utils@^2.0.3: - version "2.0.4" - resolved "https://registry.yarnpkg.com/stack-utils/-/stack-utils-2.0.4.tgz#bf967ae2813d3d2d1e1f59a4408676495c8112ab" - integrity sha512-ERg+H//lSSYlZhBIUu+wJnqg30AbyBbpZlIhcshpn7BNzpoRODZgfyr9J+8ERf3ooC6af3u7Lcl01nleau7MrA== + version "2.0.5" + resolved "https://registry.yarnpkg.com/stack-utils/-/stack-utils-2.0.5.tgz#d25265fca995154659dbbfba3b49254778d2fdd5" + integrity sha512-xrQcmYhOsn/1kX+Vraq+7j4oE2j/6BFscZ0etmYg81xuM8Gq0022Pxb8+IqgOFUIaxHs0KaSb7T1+OegiNrNFA== dependencies: escape-string-regexp "^2.0.0" - source-map-support "^0.5.20" string-length@^4.0.1: version "4.0.2" @@ -3219,18 +2891,18 @@ string-length@^4.0.1: strip-ansi "^6.0.0" string-width@^4.1.0, string-width@^4.2.0: - version "4.2.2" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.2.tgz#dafd4f9559a7585cfba529c6a0a4f73488ebd4c5" - integrity sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA== + version "4.2.3" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" + integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== dependencies: emoji-regex "^8.0.0" is-fullwidth-code-point "^3.0.0" - strip-ansi "^6.0.0" + strip-ansi "^6.0.1" string_decoder@~0.10.x: version "0.10.31" resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-0.10.31.tgz#62e203bc41766c6c28c9fc84301dab1c5310fa94" - integrity sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ= + integrity sha512-ev2QzSzWPYmy9GuqfIVildA4OdcGLeFZQrq5ys6RtiuF+RQQiZWr8TZNyAcuVXyQRYfEO+MsoB/1BuQVhOJuoQ== string_decoder@~1.1.1: version "1.1.1" @@ -3239,14 +2911,7 @@ string_decoder@~1.1.1: dependencies: safe-buffer "~5.1.0" -strip-ansi@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.0.tgz#0b1571dd7669ccd4f3e06e14ef1eed26225ae532" - integrity sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w== - dependencies: - ansi-regex "^5.0.0" - -strip-ansi@^6.0.1: +strip-ansi@^6.0.0, strip-ansi@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== @@ -3297,6 +2962,11 @@ supports-hyperlinks@^2.0.0: has-flag "^4.0.0" supports-color "^7.0.0" +supports-preserve-symlinks-flag@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" + integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== + symbol-tree@^3.2.4: version "3.2.4" resolved "https://registry.yarnpkg.com/symbol-tree/-/symbol-tree-3.2.4.tgz#430637d248ba77e078883951fb9aa0eed7c63fa2" @@ -3322,7 +2992,7 @@ test-exclude@^6.0.0: text-table@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" - integrity sha1-f17oI66AUgfACvLfSoTsP8+lcLQ= + integrity sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw== throat@^6.0.1: version "6.0.1" @@ -3337,7 +3007,7 @@ through2@^2.0.1: readable-stream "~2.3.6" xtend "~4.0.1" -tmpl@1.0.x: +tmpl@1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/tmpl/-/tmpl-1.0.5.tgz#8683e0b902bb9c20c4f726e3c0b69f36518c07cc" integrity sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw== @@ -3345,7 +3015,7 @@ tmpl@1.0.x: to-fast-properties@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e" - integrity sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4= + integrity sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog== to-regex-range@^5.0.1: version "5.0.1" @@ -3371,9 +3041,9 @@ tr46@^2.1.0: punycode "^2.1.1" ts-jest@^27.1.4: - version "27.1.4" - resolved "https://registry.yarnpkg.com/ts-jest/-/ts-jest-27.1.4.tgz#84d42cf0f4e7157a52e7c64b1492c46330943e00" - integrity sha512-qjkZlVPWVctAezwsOD1OPzbZ+k7zA5z3oxII4dGdZo5ggX/PL7kvwTM0pXTr10fAtbiVpJaL3bWd502zAhpgSQ== + version "27.1.5" + resolved "https://registry.yarnpkg.com/ts-jest/-/ts-jest-27.1.5.tgz#0ddf1b163fbaae3d5b7504a1e65c914a95cff297" + integrity sha512-Xv6jBQPoBEvBq/5i2TeSG9tt/nqkbpcurrEG1b+2yfBrcJelOZF9Ml6dmyMh7bcW9JyFbRYpR5rxROSlBLTZHA== dependencies: bs-logger "0.x" fast-json-stable-stringify "2.x" @@ -3406,7 +3076,7 @@ type-check@^0.4.0, type-check@~0.4.0: type-check@~0.3.2: version "0.3.2" resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.3.2.tgz#5884cab512cf1d355e3fb784f30804b2b520db72" - integrity sha1-WITKtRLPHTVeP7eE8wgEsrUg23I= + integrity sha512-ZCmOJdvOWDBYJlzAoFkC+Q0+bUyEOS1ltgp1MGU03fqHG+dbi9tBFU2Rd9QKiDZFAYrhPh2JUf7rZRIuHRKtOg== dependencies: prelude-ls "~1.1.2" @@ -3433,9 +3103,9 @@ typedarray-to-buffer@^3.1.5: is-typedarray "^1.0.0" typescript@^4.6.3: - version "4.6.3" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.6.3.tgz#eefeafa6afdd31d725584c67a0eaba80f6fc6c6c" - integrity sha512-yNIatDa5iaofVozS/uQJEl3JRWLKKGJKh6Yaiv0GLGSuhpFJe7P3SbHZ8/yjAHRQwKRoA6YZqlfjXWmVzoVSMw== + version "4.7.4" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.7.4.tgz#1a88596d1cf47d59507a1bcdfb5b9dfe4d488235" + integrity sha512-C0WQT0gezHuw6AdY1M2jxUO83Rjf0HP7Sk1DtXj6j1EwkQNZrHAg2XPWlq62oqEhYvONq5pkC2Y9oPljWToLmQ== uint8arrays@^3.0.0: version "3.0.0" @@ -3454,6 +3124,14 @@ untildify@^4.0.0: resolved "https://registry.yarnpkg.com/untildify/-/untildify-4.0.0.tgz#2bc947b953652487e4600949fb091e3ae8cd919b" integrity sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw== +update-browserslist-db@^1.0.0: + version "1.0.4" + resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.0.4.tgz#dbfc5a789caa26b1db8990796c2c8ebbce304824" + integrity sha512-jnmO2BEGUjsMOe/Fg9u0oczOe/ppIDZPebzccl1yDWGLFP16Pa1/RM5wEoKYPG2zstNcDuAStejyxsOuKINdGA== + dependencies: + escalade "^3.1.1" + picocolors "^1.0.0" + uri-js@^4.2.2: version "4.4.1" resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e" @@ -3464,7 +3142,7 @@ uri-js@^4.2.2: util-deprecate@~1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" - integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8= + integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw== v8-compile-cache@^2.0.3: version "2.3.0" @@ -3495,11 +3173,11 @@ w3c-xmlserializer@^2.0.0: xml-name-validator "^3.0.0" walker@^1.0.7: - version "1.0.7" - resolved "https://registry.yarnpkg.com/walker/-/walker-1.0.7.tgz#2f7f9b8fd10d677262b18a884e28d19618e028fb" - integrity sha1-L3+bj9ENZ3JisYqITijRlhjgKPs= + version "1.0.8" + resolved "https://registry.yarnpkg.com/walker/-/walker-1.0.8.tgz#bd498db477afe573dc04185f011d3ab8a8d7653f" + integrity sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ== dependencies: - makeerror "1.0.x" + makeerror "1.0.12" webidl-conversions@^5.0.0: version "5.0.0" @@ -3556,7 +3234,7 @@ wrap-ansi@^7.0.0: wrappy@1: version "1.0.2" resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" - integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= + integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== write-file-atomic@^3.0.0: version "3.0.3" @@ -3569,9 +3247,9 @@ write-file-atomic@^3.0.0: typedarray-to-buffer "^3.1.5" ws@^7.4.6: - version "7.5.5" - resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.5.tgz#8b4bc4af518cfabd0473ae4f99144287b33eb881" - integrity sha512-BAkMFcAzl8as1G/hArkxOxq3G7pjUqQ3gzYbLL0/5zNkph70e+lCoxBGnm6AW1+/aiNeV4fnKqZ8m4GZewmH2w== + version "7.5.8" + resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.8.tgz#ac2729881ab9e7cbaf8787fe3469a48c5c7f636a" + integrity sha512-ri1Id1WinAX5Jqn9HejiGb8crfRio0Qgu8+MtL36rlTA6RLsMdWt1Az/19A2Qij6uSHUMphEFaTKa4WG+UNHNw== xml-name-validator@^3.0.0: version "3.0.0" @@ -3617,6 +3295,6 @@ yargs@^16.1.0, yargs@^16.2.0: yargs-parser "^20.2.2" yarn@^1.22.18: - version "1.22.18" - resolved "https://registry.yarnpkg.com/yarn/-/yarn-1.22.18.tgz#05b822ade8c672987bab8858635145da0850f78a" - integrity sha512-oFffv6Jp2+BTUBItzx1Z0dpikTX+raRdqupfqzeMKnoh7WD6RuPAxcqDkMUy9vafJkrB0YaV708znpuMhEBKGQ== + version "1.22.19" + resolved "https://registry.yarnpkg.com/yarn/-/yarn-1.22.19.tgz#4ba7fc5c6e704fce2066ecbfb0b0d8976fe62447" + integrity sha512-/0V5q0WbslqnwP91tirOvldvYISzaqhClxzyUKXYxs07yUILIs5jx/k6CFe8bvKSkds5w+eiOqta39Wk3WxdcQ== From 93b529555953db3158976cde31bbb902430b6270 Mon Sep 17 00:00:00 2001 From: dholms Date: Tue, 28 Jun 2022 13:51:50 -0500 Subject: [PATCH 02/25] fleshing out plugins --- .eslintrc.js | 2 +- packages/core/package.json | 3 +- packages/core/src/builder.ts | 2 +- packages/core/src/plugins.ts | 98 +++ packages/core/src/token.ts | 15 +- packages/core/src/types.ts | 22 +- packages/core/src/verify.ts | 5 +- packages/core/tests/capability/wnfs.test.ts | 5 + packages/core/tests/fixtures.ts | 3 +- packages/core/tests/token.test.ts | 21 +- packages/core/tests/verify.test.ts | 675 +++++++++--------- packages/defaults/.eslintrc.js | 32 + packages/defaults/jest.config.js | 21 + packages/defaults/package.json | 88 +++ .../{default => defaults}/src/crypto/ecdsa.ts | 26 +- .../{default => defaults}/src/crypto/index.ts | 0 .../{default => defaults}/src/crypto/rsa.ts | 0 packages/defaults/src/did/index.ts | 3 + .../{default => defaults}/src/did/prefix.ts | 2 - .../src/did/pubkey-compress.ts | 0 .../src/did/transformers.ts | 3 +- .../src/did/validation.ts | 0 packages/defaults/src/index.ts | 4 + .../{default => defaults}/src/keypair/base.ts | 5 +- .../src/keypair/ecdsa.ts | 20 +- .../src/keypair/ed25519.ts | 2 +- .../src/keypair/index.ts | 0 .../{default => defaults}/src/keypair/rsa.ts | 3 +- packages/defaults/src/types.ts | 24 + .../tests/did-key.test.ts} | 0 packages/{core => defaults}/tests/did.test.ts | 0 .../{core => defaults}/tests/ecdsa.test.ts | 0 .../{core => defaults}/tests/ed25519.test.ts | 0 packages/{core => defaults}/tests/rsa.test.ts | 0 packages/defaults/tsconfig.eslint.json | 5 + packages/defaults/tsconfig.json | 12 + packages/ucans/package.json | 8 +- packages/ucans/src/attenuation.ts | 419 ----------- packages/ucans/src/builder.ts | 289 -------- packages/ucans/src/capability/ability.ts | 94 --- packages/ucans/src/capability/index.ts | 120 ---- .../ucans/src/capability/resource-pointer.ts | 89 --- packages/ucans/src/capability/super-user.ts | 10 - packages/ucans/src/compatibility.ts | 114 --- packages/ucans/src/did.ts | 3 - packages/ucans/src/did/prefix.ts | 106 --- packages/ucans/src/did/pubkey-compress.ts | 78 -- packages/ucans/src/did/transformers.ts | 105 --- packages/ucans/src/did/validation.ts | 44 -- packages/ucans/src/index.ts | 33 +- packages/ucans/src/semver.ts | 117 --- packages/ucans/src/store.ts | 92 --- packages/ucans/src/token.ts | 460 ------------ packages/ucans/src/types.ts | 143 ---- packages/ucans/src/util.ts | 70 -- packages/ucans/src/verify.ts | 138 ---- packages/ucans/tests/attenuation.test.ts | 155 ++++ packages/ucans/tests/builder.test.ts | 135 ++++ packages/ucans/tests/capability.test.ts | 22 + packages/ucans/tests/capability/email.ts | 82 +++ packages/ucans/tests/capability/wnfs.test.ts | 238 ++++++ packages/ucans/tests/capability/wnfs.ts | 213 ++++++ packages/ucans/tests/compatibility.test.ts | 39 + packages/ucans/tests/did-key.ts | 36 + packages/ucans/tests/did.test.ts | 78 ++ packages/ucans/tests/ecdsa.test.ts | 34 + packages/ucans/tests/ed25519.test.ts | 31 + packages/ucans/tests/fixtures.ts | 19 + packages/ucans/tests/rsa.test.ts | 96 +++ packages/ucans/tests/semver.test.ts | 89 +++ packages/ucans/tests/store.test.ts | 142 ++++ packages/ucans/tests/token.test.ts | 175 +++++ packages/ucans/tests/verify.test.ts | 350 +++++++++ tests-copy/attenuation.test.ts | 155 ++++ tests-copy/builder.test.ts | 135 ++++ tests-copy/capability.test.ts | 22 + tests-copy/capability/email.ts | 82 +++ tests-copy/capability/wnfs.test.ts | 238 ++++++ tests-copy/capability/wnfs.ts | 213 ++++++ tests-copy/compatibility.test.ts | 39 + tests-copy/did-key.ts | 36 + tests-copy/did.test.ts | 78 ++ tests-copy/ecdsa.test.ts | 34 + tests-copy/ed25519.test.ts | 31 + tests-copy/fixtures.ts | 19 + tests-copy/rsa.test.ts | 96 +++ tests-copy/semver.test.ts | 89 +++ tests-copy/store.test.ts | 142 ++++ tests-copy/token.test.ts | 175 +++++ tests-copy/verify.test.ts | 350 +++++++++ tsconfig.eslint.json | 5 + 91 files changed, 4578 insertions(+), 2928 deletions(-) create mode 100644 packages/core/src/plugins.ts create mode 100644 packages/defaults/.eslintrc.js create mode 100644 packages/defaults/jest.config.js create mode 100644 packages/defaults/package.json rename packages/{default => defaults}/src/crypto/ecdsa.ts (77%) rename packages/{default => defaults}/src/crypto/index.ts (100%) rename packages/{default => defaults}/src/crypto/rsa.ts (100%) create mode 100644 packages/defaults/src/did/index.ts rename packages/{default => defaults}/src/did/prefix.ts (98%) rename packages/{default => defaults}/src/did/pubkey-compress.ts (100%) rename packages/{default => defaults}/src/did/transformers.ts (97%) rename packages/{default => defaults}/src/did/validation.ts (100%) create mode 100644 packages/defaults/src/index.ts rename packages/{default => defaults}/src/keypair/base.ts (86%) rename packages/{default => defaults}/src/keypair/ecdsa.ts (72%) rename packages/{default => defaults}/src/keypair/ed25519.ts (97%) rename packages/{default => defaults}/src/keypair/index.ts (100%) rename packages/{default => defaults}/src/keypair/rsa.ts (91%) create mode 100644 packages/defaults/src/types.ts rename packages/{core/tests/did-key.ts => defaults/tests/did-key.test.ts} (100%) rename packages/{core => defaults}/tests/did.test.ts (100%) rename packages/{core => defaults}/tests/ecdsa.test.ts (100%) rename packages/{core => defaults}/tests/ed25519.test.ts (100%) rename packages/{core => defaults}/tests/rsa.test.ts (100%) create mode 100644 packages/defaults/tsconfig.eslint.json create mode 100644 packages/defaults/tsconfig.json delete mode 100644 packages/ucans/src/attenuation.ts delete mode 100644 packages/ucans/src/builder.ts delete mode 100644 packages/ucans/src/capability/ability.ts delete mode 100644 packages/ucans/src/capability/index.ts delete mode 100644 packages/ucans/src/capability/resource-pointer.ts delete mode 100644 packages/ucans/src/capability/super-user.ts delete mode 100644 packages/ucans/src/compatibility.ts delete mode 100644 packages/ucans/src/did.ts delete mode 100644 packages/ucans/src/did/prefix.ts delete mode 100644 packages/ucans/src/did/pubkey-compress.ts delete mode 100644 packages/ucans/src/did/transformers.ts delete mode 100644 packages/ucans/src/did/validation.ts delete mode 100644 packages/ucans/src/semver.ts delete mode 100644 packages/ucans/src/store.ts delete mode 100644 packages/ucans/src/token.ts delete mode 100644 packages/ucans/src/types.ts delete mode 100644 packages/ucans/src/util.ts delete mode 100644 packages/ucans/src/verify.ts create mode 100644 packages/ucans/tests/attenuation.test.ts create mode 100644 packages/ucans/tests/builder.test.ts create mode 100644 packages/ucans/tests/capability.test.ts create mode 100644 packages/ucans/tests/capability/email.ts create mode 100644 packages/ucans/tests/capability/wnfs.test.ts create mode 100644 packages/ucans/tests/capability/wnfs.ts create mode 100644 packages/ucans/tests/compatibility.test.ts create mode 100644 packages/ucans/tests/did-key.ts create mode 100644 packages/ucans/tests/did.test.ts create mode 100644 packages/ucans/tests/ecdsa.test.ts create mode 100644 packages/ucans/tests/ed25519.test.ts create mode 100644 packages/ucans/tests/fixtures.ts create mode 100644 packages/ucans/tests/rsa.test.ts create mode 100644 packages/ucans/tests/semver.test.ts create mode 100644 packages/ucans/tests/store.test.ts create mode 100644 packages/ucans/tests/token.test.ts create mode 100644 packages/ucans/tests/verify.test.ts create mode 100644 tests-copy/attenuation.test.ts create mode 100644 tests-copy/builder.test.ts create mode 100644 tests-copy/capability.test.ts create mode 100644 tests-copy/capability/email.ts create mode 100644 tests-copy/capability/wnfs.test.ts create mode 100644 tests-copy/capability/wnfs.ts create mode 100644 tests-copy/compatibility.test.ts create mode 100644 tests-copy/did-key.ts create mode 100644 tests-copy/did.test.ts create mode 100644 tests-copy/ecdsa.test.ts create mode 100644 tests-copy/ed25519.test.ts create mode 100644 tests-copy/fixtures.ts create mode 100644 tests-copy/rsa.test.ts create mode 100644 tests-copy/semver.test.ts create mode 100644 tests-copy/store.test.ts create mode 100644 tests-copy/token.test.ts create mode 100644 tests-copy/verify.test.ts create mode 100644 tsconfig.eslint.json diff --git a/.eslintrc.js b/.eslintrc.js index 4bde45a..227ccc1 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -1,4 +1,4 @@ -module.exports = { +export default { root: true, parser: "@typescript-eslint/parser", parserOptions: { diff --git a/packages/core/package.json b/packages/core/package.json index 68a5b60..47a6b3f 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -19,7 +19,7 @@ "dist": "yarn run dist:prep && yarn run dist:src && yarn run dist:cjs && yarn run dist:esm && yarn run dist:types && yarn run dist:pkg", "dist:cjs": "tsc --project ./dist/ --module commonjs --outDir ./dist/cjs/ --sourceMap", "dist:esm": "tsc --project ./dist/ --module es2020 --outDir ./dist/esm/ --sourceMap", - "dist:pkg": "node ./scripts/package.js", + "dist:pkg": "node ../../scripts/package.js", "dist:prep": "copyfiles --error tsconfig.json ./dist/", "dist:src": "copyfiles --error --up 1 \"./src/**/*\" ./dist/src/", "dist:types": "tsc --project ./dist/ --emitDeclarationOnly --declaration --declarationDir ./dist/types/", @@ -69,6 +69,7 @@ "@types/node": "^17.0.23", "@typescript-eslint/eslint-plugin": "^5.18.0", "@typescript-eslint/parser": "^5.18.0", + "@ucans/defaults": "*", "copyfiles": "^2.4.1", "eslint": "^8.12.0", "fast-check": "^2.24.0", diff --git a/packages/core/src/builder.ts b/packages/core/src/builder.ts index 99dcc02..f3cfeb5 100644 --- a/packages/core/src/builder.ts +++ b/packages/core/src/builder.ts @@ -84,7 +84,7 @@ export class Builder> { * * The UCAN must be signed with the private key of the issuer to be valid. */ - issuedBy(issuer: Keypair): Builder { + issuedBy(issuer: Keypair & Didable): Builder { if (!isKeypair(issuer)) { throw new TypeError(`Expected a Keypair, but got ${issuer}`) } diff --git a/packages/core/src/plugins.ts b/packages/core/src/plugins.ts new file mode 100644 index 0000000..e87931d --- /dev/null +++ b/packages/core/src/plugins.ts @@ -0,0 +1,98 @@ +import * as uint8arrays from 'uint8arrays' + +export type DidKeyPlugin = { + prefix: Uint8Array + jwtAlg: string + checkSignature: (did: string, data: Uint8Array, sig: Uint8Array) => Promise +} + +export type DidMethodPlugin = { + method: string + checkJwtAlg: (did: string, jwtAlg: string) => boolean + checkSignature: (did: string, data: Uint8Array, sig: Uint8Array) => Promise +} + +export type Plugins = { + keys: DidKeyPlugin[] + methods: DidMethodPlugin[] +} + +let plugins: Plugins | null = null + +export const checkIssuer = (did: string, jwtAlg: string): boolean => { + if(plugins === null) { + throw new Error("No plugins loaded") + } + const didMethod = parseDidMethod(did) + if(didMethod === 'key') { + const bytes = parsePrefixedBytes(did) + for (const keyPlugin of plugins.keys) { + if(hasPrefix(bytes, keyPlugin.prefix)) { + return jwtAlg === keyPlugin.jwtAlg + } + } + } else { + for (const didPlugin of plugins.methods) { + if(didMethod === didPlugin.method) { + return didPlugin.checkJwtAlg(did, jwtAlg) + } + } + } + throw new Error(`DID method not supported by plugins: ${did}`) +} + +export const checkSignature = async (did: string, data: Uint8Array, sig: Uint8Array): Promise => { + if(plugins === null) { + throw new Error("No plugins loaded") + } + const didMethod = parseDidMethod(did) + if(didMethod === 'key') { + const bytes = parsePrefixedBytes(did) + for (const keyPlugin of plugins.keys) { + if(hasPrefix(bytes, keyPlugin.prefix)) { + return keyPlugin.checkSignature(did, data, sig) + } + } + } else { + for (const didPlugin of plugins.methods) { + if(didMethod === didPlugin.method) { + return didPlugin.checkSignature(did, data, sig) + } + } + } + throw new Error(`DID method not supported by plugins: ${did}`) +} + +export const loadPlugins = (plugins: Plugins): void => { + plugins = plugins +} + +export const hasPrefix = ( + prefixedKey: Uint8Array, + prefix: Uint8Array +): boolean => { + return uint8arrays.equals(prefix, prefixedKey.subarray(0, prefix.byteLength)) +} + +// @TODO would be better to follow the actual varint spec here: +// https://github.com/multiformats/unsigned-varint +const parsePrefixedBytes = (did: string): Uint8Array => { + if(!did.startsWith("did:key:z")) { + throw new Error(`Not a valid base58 formatted did:key: ${did}`) + } + return uint8arrays.fromString( + did.replace("did:key:z", ""), + "base58btc" + ) +} + +const parseDidMethod = (did: string) => { + const parts = did.split(':') + if(parts[0] !== 'did') { + throw new Error(`Not a DID: ${did}`) + } + if(parts[1].length < 1) { + throw new Error(`No DID method included: ${did}`) + } + return parts[1] +} \ No newline at end of file diff --git a/packages/core/src/token.ts b/packages/core/src/token.ts index ef818d1..baf6d68 100644 --- a/packages/core/src/token.ts +++ b/packages/core/src/token.ts @@ -3,6 +3,7 @@ import * as uint8arrays from "uint8arrays" // @IMPORT import * as semver from "./semver.js" import * as capability from "./capability/index.js" import * as util from "./util.js" +import * as plugins from './plugins.js' import { Capability, isCapability, isEncodedCapability } from "./capability/index.js" import { Fact, KeyType, Keypair, Didable } from "./types.js" @@ -297,13 +298,12 @@ export function parse(encodedUcan: string): UcanParts { // VALIDATION - /** * Validation options */ export interface ValidateOptions { - checkIssuer: (did: string, jwtAlg: string) => boolean - checkSignature: (did: string, data: Uint8Array, sig: Uint8Array) => Promise + checkIssuer?: boolean + checkSignature?: boolean checkIsExpired?: boolean checkIsTooEarly?: boolean } @@ -320,13 +320,14 @@ export interface ValidateOptions { * @throws Error if the UCAN is invalid */ export async function validate(encodedUcan: string, opts?: Partial): Promise { - const { checkIssuer, checkSignature, checkIsExpired = true, checkIsTooEarly = true } = opts ?? {} + const { checkIssuer = true, checkSignature = true, checkIsExpired = true, checkIsTooEarly = true } = opts ?? {} const { header, payload } = parse(encodedUcan) const [ encodedHeader, encodedPayload, signature ] = encodedUcan.split(".") if (checkIssuer) { - if (checkIssuer(payload.iss, header.alg)) { + const validIssuer = plugins.checkIssuer(payload.iss, header.alg) + if (!validIssuer) { throw new Error(`Invalid UCAN: ${encodedUcan}: Issuer key type does not match UCAN's \`alg\` property.`) } } @@ -334,8 +335,8 @@ export async function validate(encodedUcan: string, opts?: Partial /** Unlike tslib's CryptoKeyPair, this requires the `privateKey` and `publicKey` fields */ -export interface AvailableCryptoKeyPair { - privateKey: CryptoKey - publicKey: CryptoKey -} export interface Didable { publicKeyStr: (format?: Encodings) => string @@ -76,25 +72,13 @@ export interface Keypair { sign: (msg: Uint8Array) => Promise } +// @TODO get rid of this export type KeyType = | "rsa" | "p256" | "ed25519" | "bls12-381" -// https://developer.mozilla.org/en-US/docs/Web/API/EcKeyGenParams -export type NamedCurve = "P-256" - -export type PublicKeyJwk = { - kty: string - crv: string - x: string - y: string -} - -export type PrivateKeyJwk = PublicKeyJwk & { d: string } - - // MISC @@ -105,10 +89,6 @@ export type Encodings = SupportedEncodings // TYPE CHECKS -export function isAvailableCryptoKeyPair(keypair: CryptoKeyPair): keypair is AvailableCryptoKeyPair { - return keypair.publicKey != null && keypair.privateKey != null -} - export function isKeypair(obj: unknown): obj is Keypair { return util.isRecord(obj) && util.hasProp(obj, "publicKey") && obj.publicKey instanceof Uint8Array diff --git a/packages/core/src/verify.ts b/packages/core/src/verify.ts index a8b5c0e..e6a5c1e 100644 --- a/packages/core/src/verify.ts +++ b/packages/core/src/verify.ts @@ -24,9 +24,6 @@ export interface VerifyOptions { * that's access-controlled. */ requiredCapabilities: { capability: Capability; rootIssuer: string }[] - // @TODO add comment & argument type checks (in code) - checkIssuer: (did: string, jwtAlg: string) => boolean - checkSignature: (did: string, data: Uint8Array, sig: Uint8Array) => Promise /** * an optional record of functions that specify what the rules for delegating capabilities are. * If not provided, the default semantics will be `equalCanDelegate`. @@ -89,7 +86,7 @@ export async function verify(ucan: string, options: VerifyOptions): Promise { + // @TODO undo + it('passes', () => { + expect(true) + }) + it("works with a simple example", async () => { const { ucan } = await makeSimpleDelegation( [ wnfsCapability("//boris.fission.name/public/Apps/", "OVERWRITE") ], diff --git a/packages/core/tests/fixtures.ts b/packages/core/tests/fixtures.ts index fbdeeb1..0402546 100644 --- a/packages/core/tests/fixtures.ts +++ b/packages/core/tests/fixtures.ts @@ -1,4 +1,5 @@ -import { EdKeypair } from "../src/keypair/ed25519" +import { EdKeypair } from '@ucans/defaults' +// import { EdKeypair } from "../src/keypair/ed25519" /** did:key:z6Mkk89bC3JrVqKie71YEcc5M1SMVxuCgNx6zLZ8SYJsxALi */ diff --git a/packages/core/tests/token.test.ts b/packages/core/tests/token.test.ts index b625a78..fa6840d 100644 --- a/packages/core/tests/token.test.ts +++ b/packages/core/tests/token.test.ts @@ -2,7 +2,7 @@ import * as uint8arrays from "uint8arrays" import * as capability from "../src/capability" import * as token from "../src/token" -import { verifySignatureUtf8 } from "../src/did" +// import { verifySignatureUtf8 } from "../src/did" import { alice, bob } from "./fixtures" @@ -162,14 +162,15 @@ describe("token.validate", () => { }) }) -describe("verifySignatureUtf8", () => { +// @TODO Move this?? +// describe("verifySignatureUtf8", () => { - it("works with an example", async () => { - const [ header, payload, signature ] = token.encode(await token.build({ - issuer: alice, - audience: bob.did(), - })).split(".") - expect(await verifySignatureUtf8(`${header}.${payload}`, signature, alice.did())).toEqual(true) - }) +// it("works with an example", async () => { +// const [ header, payload, signature ] = token.encode(await token.build({ +// issuer: alice, +// audience: bob.did(), +// })).split(".") +// expect(await verifySignatureUtf8(`${header}.${payload}`, signature, alice.did())).toEqual(true) +// }) -}) +// }) diff --git a/packages/core/tests/verify.test.ts b/packages/core/tests/verify.test.ts index ba91658..f9d3f80 100644 --- a/packages/core/tests/verify.test.ts +++ b/packages/core/tests/verify.test.ts @@ -9,342 +9,347 @@ import { REDELEGATE } from "../src/capability/ability" describe("verify", () => { - async function aliceEmailDelegationExample(expiration?: number) { - // alice -> bob, bob -> mallory - // alice delegates access to sending email as her to bob - // and bob delegates it further to mallory - const leafUcan = await token.build({ - issuer: alice, - audience: bob.did(), - expiration, - capabilities: [ emailCapability("alice@email.com") ] - }) - - const ucan = await token.build({ - issuer: bob, - audience: mallory.did(), - expiration, - capabilities: [ emailCapability("alice@email.com") ], - proofs: [ token.encode(leafUcan) ] - }) - - return token.encode(ucan) - } - - const alicesEmail = { - capability: emailCapability("alice@email.com"), - rootIssuer: alice.did(), - } - - it("verifies a delegation chain", async () => { - const ucan = await aliceEmailDelegationExample() - - const result = await verify(ucan, { - audience: mallory.did(), - requiredCapabilities: [ alicesEmail ] - }) - - if (result.ok === false) { - console.log(result.error) - } - - expect(result.ok).toEqual(true) - - if (!result.ok) return - - expect(result.value[ 0 ]?.rootIssuer).toEqual(alice.did()) - expect(result.value[ 0 ]?.capability).toEqual(emailCapability("alice@email.com")) + // @TODO undo + it('passes', () => { + expect(true) }) - it("rejects an invalid escalation", async () => { - const ucan = await aliceEmailDelegationExample() - - const result = await verify(ucan, { - audience: mallory.did(), - requiredCapabilities: [ { - capability: { - ...emailCapability("alice@email.com"), - can: SUPERUSER, - }, - rootIssuer: alice.did() - } ] - }) - - expect(result.ok).toEqual(false) - }) - - it("rejects for an invalid audience", async () => { - const ucan = await aliceEmailDelegationExample() - - const result = await verify(ucan, { - audience: bob.did(), - requiredCapabilities: [ alicesEmail ] - }) - - expect(result.ok).toEqual(false) - }) - - it("rejects for an invalid rootIssuer", async () => { - const ucan = await aliceEmailDelegationExample() - - const result = await verify(ucan, { - audience: mallory.did(), - requiredCapabilities: [ { - capability: emailCapability("alice@email.com"), - // an invalid rootIssuer - rootIssuer: "did:someone-else", - } ] - }) - - expect(result.ok).toEqual(false) - }) - - it("rejects for an expired capability", async () => { - // unix timestamp in seconds. Will be after - const nowInSeconds = Math.floor(Date.now() / 1000) - // expiry is in the past - const ucan = await aliceEmailDelegationExample(nowInSeconds - 60 * 60 * 24) - - const result = await verify(ucan, { - audience: mallory.did(), - requiredCapabilities: [ alicesEmail ] - }) - - expect(result.ok).toEqual(false) - }) - - it("supports redelegation with a `prf:*` capability", async () => { - // alice -> bob - // alice delegates access to sending email as her to bob - // and bob delegates it further to mallory - const leafUcan = await token.build({ - issuer: alice, - audience: bob.did(), - capabilities: [ emailCapability("alice@email.com") ] - }) - - const ucan = await token.build({ - issuer: bob, - audience: mallory.did(), - capabilities: [ capability.prf(capability.superUser.SUPERUSER, REDELEGATE) ], - proofs: [ token.encode(leafUcan) ] - }) - - const result = await verify(token.encode(ucan), { - audience: mallory.did(), - requiredCapabilities: [ alicesEmail ] - }) - - expect(result.ok).toEqual(true) - - if (!result.ok) return - - expect(result.value[ 0 ]?.rootIssuer).toEqual(alice.did()) - expect(result.value[ 0 ]?.capability).toEqual(emailCapability("alice@email.com")) - }) - - it("supports redelegation with a `prf:1` capability", async () => { - // alice -> bob - // alice delegates access to sending email as her to bob - // and bob delegates it further to mallory - const leafUcanA = await token.build({ - issuer: alice, - audience: bob.did(), - capabilities: [ emailCapability("ignore-me@email.com") ] - }) - - const leafUcanB = await token.build({ - issuer: alice, - audience: bob.did(), - capabilities: [ emailCapability("alice@email.com") ] - }) - - const ucan = await token.build({ - issuer: bob, - audience: mallory.did(), - capabilities: [ capability.prf(1, REDELEGATE) ], - proofs: [ token.encode(leafUcanA), token.encode(leafUcanB) ] - }) - - const result = await verify(token.encode(ucan), { - audience: mallory.did(), - requiredCapabilities: [ alicesEmail ] - }) - - expect(result.ok).toEqual(true) - - if (!result.ok) return - - expect(result.value[ 0 ]?.rootIssuer).toEqual(alice.did()) - expect(result.value[ 0 ]?.capability).toEqual(emailCapability("alice@email.com")) - }) - - it("ignores other proofs not referred to by `prf:0`", async () => { - const leafUcanA = await token.build({ - issuer: alice, - audience: bob.did(), - capabilities: [ emailCapability("ignore-me@email.com") ] - }) - - const leafUcanB = await token.build({ - issuer: alice, - audience: bob.did(), - capabilities: [ emailCapability("alice@email.com") ] - }) - - const faultyUcan = await token.build({ - issuer: bob, - audience: mallory.did(), - capabilities: [ capability.prf(0, REDELEGATE) ], - proofs: [ token.encode(leafUcanA), token.encode(leafUcanB) ] - }) - - const result = await verify(token.encode(faultyUcan), { - audience: mallory.did(), - requiredCapabilities: [ alicesEmail ] - }) - - expect(result.ok).toEqual(false) - }) - - it("rejects an improper `prf` redelegation", async () => { - // alice -> bob - // alice delegates access to sending email as her to bob - // and bob delegates it further to mallory - const leafUcan = await token.build({ - issuer: alice, - audience: bob.did(), - capabilities: [ emailCapability("invalid@email.com") ] - }) - - const ucan = await token.build({ - issuer: bob, - audience: mallory.did(), - capabilities: [ capability.prf(capability.superUser.SUPERUSER, REDELEGATE) ], - proofs: [ token.encode(leafUcan) ] - }) - - const result = await verify(token.encode(ucan), { - audience: mallory.did(), - requiredCapabilities: [ alicesEmail ] - }) - - expect(result.ok).toEqual(false) - }) - - it("supports redelegation with a `my` capability", async () => { - // alice -> bob - // alice delegates access to sending email as her to bob - // and bob delegates it further to mallory - const leafUcan = await token.build({ - issuer: alice, - audience: bob.did(), - capabilities: [ capability.my(capability.superUser.SUPERUSER) ] - }) - - const ucan = await token.build({ - issuer: bob, - audience: mallory.did(), - capabilities: [ emailCapability("alice@email.com") ], - proofs: [ token.encode(leafUcan) ] - }) - - const result = await verify(token.encode(ucan), { - audience: mallory.did(), - requiredCapabilities: [ alicesEmail ], - }) - - expect(result.ok).toEqual(true) - - if (!result.ok) return - - expect(result.value[ 0 ]?.rootIssuer).toEqual(alice.did()) - }) - - it("supports redelegation with a `my` & `as` capability", async () => { - // alice -> bob, bob -> mallory, mallory -> "someone" - // alice delegates access to sending email as her to bob - // and bob delegates it further to mallory - const leafUcan = await token.build({ - issuer: alice, - audience: bob.did(), - capabilities: [ capability.my(capability.superUser.SUPERUSER) ] - }) - - const middleUcan = await token.build({ - issuer: bob, - audience: mallory.did(), - capabilities: [ capability.as(alice.did(), SUPERUSER) ], - proofs: [ token.encode(leafUcan) ] - }) - - const ucan = await token.build({ - issuer: mallory, - audience: "did:key:someone", - capabilities: [ emailCapability("alice@email.com") ], - proofs: [ token.encode(middleUcan) ] - }) - - const result = await verify(token.encode(ucan), { - audience: "did:key:someone", - requiredCapabilities: [ alicesEmail ] - }) - - expect(result.ok).toEqual(true) - - if (!result.ok) return - - expect(result.value[ 0 ]?.rootIssuer).toEqual(alice.did()) - }) - - it("rejects an improper `my` redelegation", async () => { - // alice -> bob - // alice delegates access to sending email as her to bob - // and bob delegates it further to mallory - const leafUcan = await token.build({ - issuer: alice, - audience: bob.did(), - capabilities: [] - }) - - const ucan = await token.build({ - issuer: bob, - audience: mallory.did(), - capabilities: [ emailCapability("alice@email.com") ], - proofs: [ token.encode(leafUcan) ] - }) - - const result = await verify(token.encode(ucan), { - audience: mallory.did(), - requiredCapabilities: [ alicesEmail ] - }) - - expect(result.ok).toEqual(false) - }) - - it("rejects an improper `as` redelegation - no `my`", async () => { - // alice -> bob - // alice delegates access to sending email as her to bob - // and bob delegates it further to mallory - const leafUcan = await token.build({ - issuer: alice, - audience: bob.did(), - capabilities: [ capability.as(bob.did(), SUPERUSER) ] - }) - - const ucan = await token.build({ - issuer: bob, - audience: mallory.did(), - capabilities: [ emailCapability("alice@email.com") ], - proofs: [ token.encode(leafUcan) ] - }) - - const result = await verify(token.encode(ucan), { - audience: mallory.did(), - requiredCapabilities: [ alicesEmail ] - }) - - expect(result.ok).toEqual(false) - }) + // async function aliceEmailDelegationExample(expiration?: number) { + // // alice -> bob, bob -> mallory + // // alice delegates access to sending email as her to bob + // // and bob delegates it further to mallory + // const leafUcan = await token.build({ + // issuer: alice, + // audience: bob.did(), + // expiration, + // capabilities: [ emailCapability("alice@email.com") ] + // }) + + // const ucan = await token.build({ + // issuer: bob, + // audience: mallory.did(), + // expiration, + // capabilities: [ emailCapability("alice@email.com") ], + // proofs: [ token.encode(leafUcan) ] + // }) + + // return token.encode(ucan) + // } + + // const alicesEmail = { + // capability: emailCapability("alice@email.com"), + // rootIssuer: alice.did(), + // } + + // it("verifies a delegation chain", async () => { + // const ucan = await aliceEmailDelegationExample() + + // const result = await verify(ucan, { + // audience: mallory.did(), + // requiredCapabilities: [ alicesEmail ] + // }) + + // if (result.ok === false) { + // console.log(result.error) + // } + + // expect(result.ok).toEqual(true) + + // if (!result.ok) return + + // expect(result.value[ 0 ]?.rootIssuer).toEqual(alice.did()) + // expect(result.value[ 0 ]?.capability).toEqual(emailCapability("alice@email.com")) + // }) + + // it("rejects an invalid escalation", async () => { + // const ucan = await aliceEmailDelegationExample() + + // const result = await verify(ucan, { + // audience: mallory.did(), + // requiredCapabilities: [ { + // capability: { + // ...emailCapability("alice@email.com"), + // can: SUPERUSER, + // }, + // rootIssuer: alice.did() + // } ] + // }) + + // expect(result.ok).toEqual(false) + // }) + + // it("rejects for an invalid audience", async () => { + // const ucan = await aliceEmailDelegationExample() + + // const result = await verify(ucan, { + // audience: bob.did(), + // requiredCapabilities: [ alicesEmail ] + // }) + + // expect(result.ok).toEqual(false) + // }) + + // it("rejects for an invalid rootIssuer", async () => { + // const ucan = await aliceEmailDelegationExample() + + // const result = await verify(ucan, { + // audience: mallory.did(), + // requiredCapabilities: [ { + // capability: emailCapability("alice@email.com"), + // // an invalid rootIssuer + // rootIssuer: "did:someone-else", + // } ] + // }) + + // expect(result.ok).toEqual(false) + // }) + + // it("rejects for an expired capability", async () => { + // // unix timestamp in seconds. Will be after + // const nowInSeconds = Math.floor(Date.now() / 1000) + // // expiry is in the past + // const ucan = await aliceEmailDelegationExample(nowInSeconds - 60 * 60 * 24) + + // const result = await verify(ucan, { + // audience: mallory.did(), + // requiredCapabilities: [ alicesEmail ] + // }) + + // expect(result.ok).toEqual(false) + // }) + + // it("supports redelegation with a `prf:*` capability", async () => { + // // alice -> bob + // // alice delegates access to sending email as her to bob + // // and bob delegates it further to mallory + // const leafUcan = await token.build({ + // issuer: alice, + // audience: bob.did(), + // capabilities: [ emailCapability("alice@email.com") ] + // }) + + // const ucan = await token.build({ + // issuer: bob, + // audience: mallory.did(), + // capabilities: [ capability.prf(capability.superUser.SUPERUSER, REDELEGATE) ], + // proofs: [ token.encode(leafUcan) ] + // }) + + // const result = await verify(token.encode(ucan), { + // audience: mallory.did(), + // requiredCapabilities: [ alicesEmail ] + // }) + + // expect(result.ok).toEqual(true) + + // if (!result.ok) return + + // expect(result.value[ 0 ]?.rootIssuer).toEqual(alice.did()) + // expect(result.value[ 0 ]?.capability).toEqual(emailCapability("alice@email.com")) + // }) + + // it("supports redelegation with a `prf:1` capability", async () => { + // // alice -> bob + // // alice delegates access to sending email as her to bob + // // and bob delegates it further to mallory + // const leafUcanA = await token.build({ + // issuer: alice, + // audience: bob.did(), + // capabilities: [ emailCapability("ignore-me@email.com") ] + // }) + + // const leafUcanB = await token.build({ + // issuer: alice, + // audience: bob.did(), + // capabilities: [ emailCapability("alice@email.com") ] + // }) + + // const ucan = await token.build({ + // issuer: bob, + // audience: mallory.did(), + // capabilities: [ capability.prf(1, REDELEGATE) ], + // proofs: [ token.encode(leafUcanA), token.encode(leafUcanB) ] + // }) + + // const result = await verify(token.encode(ucan), { + // audience: mallory.did(), + // requiredCapabilities: [ alicesEmail ] + // }) + + // expect(result.ok).toEqual(true) + + // if (!result.ok) return + + // expect(result.value[ 0 ]?.rootIssuer).toEqual(alice.did()) + // expect(result.value[ 0 ]?.capability).toEqual(emailCapability("alice@email.com")) + // }) + + // it("ignores other proofs not referred to by `prf:0`", async () => { + // const leafUcanA = await token.build({ + // issuer: alice, + // audience: bob.did(), + // capabilities: [ emailCapability("ignore-me@email.com") ] + // }) + + // const leafUcanB = await token.build({ + // issuer: alice, + // audience: bob.did(), + // capabilities: [ emailCapability("alice@email.com") ] + // }) + + // const faultyUcan = await token.build({ + // issuer: bob, + // audience: mallory.did(), + // capabilities: [ capability.prf(0, REDELEGATE) ], + // proofs: [ token.encode(leafUcanA), token.encode(leafUcanB) ] + // }) + + // const result = await verify(token.encode(faultyUcan), { + // audience: mallory.did(), + // requiredCapabilities: [ alicesEmail ] + // }) + + // expect(result.ok).toEqual(false) + // }) + + // it("rejects an improper `prf` redelegation", async () => { + // // alice -> bob + // // alice delegates access to sending email as her to bob + // // and bob delegates it further to mallory + // const leafUcan = await token.build({ + // issuer: alice, + // audience: bob.did(), + // capabilities: [ emailCapability("invalid@email.com") ] + // }) + + // const ucan = await token.build({ + // issuer: bob, + // audience: mallory.did(), + // capabilities: [ capability.prf(capability.superUser.SUPERUSER, REDELEGATE) ], + // proofs: [ token.encode(leafUcan) ] + // }) + + // const result = await verify(token.encode(ucan), { + // audience: mallory.did(), + // requiredCapabilities: [ alicesEmail ] + // }) + + // expect(result.ok).toEqual(false) + // }) + + // it("supports redelegation with a `my` capability", async () => { + // // alice -> bob + // // alice delegates access to sending email as her to bob + // // and bob delegates it further to mallory + // const leafUcan = await token.build({ + // issuer: alice, + // audience: bob.did(), + // capabilities: [ capability.my(capability.superUser.SUPERUSER) ] + // }) + + // const ucan = await token.build({ + // issuer: bob, + // audience: mallory.did(), + // capabilities: [ emailCapability("alice@email.com") ], + // proofs: [ token.encode(leafUcan) ] + // }) + + // const result = await verify(token.encode(ucan), { + // audience: mallory.did(), + // requiredCapabilities: [ alicesEmail ], + // }) + + // expect(result.ok).toEqual(true) + + // if (!result.ok) return + + // expect(result.value[ 0 ]?.rootIssuer).toEqual(alice.did()) + // }) + + // it("supports redelegation with a `my` & `as` capability", async () => { + // // alice -> bob, bob -> mallory, mallory -> "someone" + // // alice delegates access to sending email as her to bob + // // and bob delegates it further to mallory + // const leafUcan = await token.build({ + // issuer: alice, + // audience: bob.did(), + // capabilities: [ capability.my(capability.superUser.SUPERUSER) ] + // }) + + // const middleUcan = await token.build({ + // issuer: bob, + // audience: mallory.did(), + // capabilities: [ capability.as(alice.did(), SUPERUSER) ], + // proofs: [ token.encode(leafUcan) ] + // }) + + // const ucan = await token.build({ + // issuer: mallory, + // audience: "did:key:someone", + // capabilities: [ emailCapability("alice@email.com") ], + // proofs: [ token.encode(middleUcan) ] + // }) + + // const result = await verify(token.encode(ucan), { + // audience: "did:key:someone", + // requiredCapabilities: [ alicesEmail ] + // }) + + // expect(result.ok).toEqual(true) + + // if (!result.ok) return + + // expect(result.value[ 0 ]?.rootIssuer).toEqual(alice.did()) + // }) + + // it("rejects an improper `my` redelegation", async () => { + // // alice -> bob + // // alice delegates access to sending email as her to bob + // // and bob delegates it further to mallory + // const leafUcan = await token.build({ + // issuer: alice, + // audience: bob.did(), + // capabilities: [] + // }) + + // const ucan = await token.build({ + // issuer: bob, + // audience: mallory.did(), + // capabilities: [ emailCapability("alice@email.com") ], + // proofs: [ token.encode(leafUcan) ] + // }) + + // const result = await verify(token.encode(ucan), { + // audience: mallory.did(), + // requiredCapabilities: [ alicesEmail ] + // }) + + // expect(result.ok).toEqual(false) + // }) + + // it("rejects an improper `as` redelegation - no `my`", async () => { + // // alice -> bob + // // alice delegates access to sending email as her to bob + // // and bob delegates it further to mallory + // const leafUcan = await token.build({ + // issuer: alice, + // audience: bob.did(), + // capabilities: [ capability.as(bob.did(), SUPERUSER) ] + // }) + + // const ucan = await token.build({ + // issuer: bob, + // audience: mallory.did(), + // capabilities: [ emailCapability("alice@email.com") ], + // proofs: [ token.encode(leafUcan) ] + // }) + + // const result = await verify(token.encode(ucan), { + // audience: mallory.did(), + // requiredCapabilities: [ alicesEmail ] + // }) + + // expect(result.ok).toEqual(false) + // }) }) diff --git a/packages/defaults/.eslintrc.js b/packages/defaults/.eslintrc.js new file mode 100644 index 0000000..227ccc1 --- /dev/null +++ b/packages/defaults/.eslintrc.js @@ -0,0 +1,32 @@ +export default { + root: true, + parser: "@typescript-eslint/parser", + parserOptions: { + project: `./tsconfig.eslint.json` + }, + plugins: [ + "@typescript-eslint", + ], + extends: [ + "eslint:recommended", + "plugin:@typescript-eslint/eslint-recommended", + "plugin:@typescript-eslint/recommended", + ], + rules: { + "@typescript-eslint/member-delimiter-style": [ "error", { + "multiline": { + "delimiter": "none", + "requireLast": false + }, + } ], + "@typescript-eslint/no-use-before-define": [ "off" ], + "@typescript-eslint/semi": [ "error", "never" ], + "@typescript-eslint/ban-ts-comment": 1, + "@typescript-eslint/quotes": [ "error", "double", { + allowTemplateLiterals: true + } ], + // If you want to *intentionally* run a promise without awaiting, prepend it with "void " instead of "await " + "@typescript-eslint/no-floating-promises": [ "error" ], + "@typescript-eslint/no-inferrable-types": [ "off" ], + } +} diff --git a/packages/defaults/jest.config.js b/packages/defaults/jest.config.js new file mode 100644 index 0000000..1a5935b --- /dev/null +++ b/packages/defaults/jest.config.js @@ -0,0 +1,21 @@ +module.exports = { // eslint-disable-line + transform: { + ".(ts|tsx)": "ts-jest" + }, + testEnvironment: "node", + testRegex: "(/__tests__/.*|\\.(test|spec))\\.(ts|tsx|js)$", + moduleFileExtensions: [ + "ts", + "tsx", + "js" + ], + preset: "ts-jest/presets/default-esm", + globals: { + "ts-jest": { + useESM: true, + }, + }, + moduleNameMapper: { + "^(\\.{1,2}/.*)\\.js$": "$1", + }, +} diff --git a/packages/defaults/package.json b/packages/defaults/package.json new file mode 100644 index 0000000..ac64e20 --- /dev/null +++ b/packages/defaults/package.json @@ -0,0 +1,88 @@ +{ + "name": "@ucans/defaults", + "version": "0.9.1", + "description": "Typescript implementation of UCANs", + "author": "Daniel Holmgren ", + "repository": { + "type": "git", + "url": "https://github.com/fission-suite/ucan" + }, + "homepage": "https://guide.fission.codes", + "license": "Apache-2.0", + "engines": { + "node": ">=15" + }, + "scripts": { + "prebuild": "rimraf dist", + "build": "yarn run dist", + "dev": "tsc --watch --module commonjs --outDir ./dist/cjs/ --sourceMap", + "dist": "yarn run dist:prep && yarn run dist:src && yarn run dist:cjs && yarn run dist:esm && yarn run dist:types && yarn run dist:pkg", + "dist:cjs": "tsc --project ./dist/ --module commonjs --outDir ./dist/cjs/ --sourceMap", + "dist:esm": "tsc --project ./dist/ --module es2020 --outDir ./dist/esm/ --sourceMap", + "dist:pkg": "node ../../scripts/package.js", + "dist:prep": "copyfiles --error tsconfig.json ./dist/", + "dist:src": "copyfiles --error --up 1 \"./src/**/*\" ./dist/src/", + "dist:types": "tsc --project ./dist/ --emitDeclarationOnly --declaration --declarationDir ./dist/types/", + "lint": "eslint src/**/*.ts src/*.ts tests/**/*.ts tests/*.ts", + "prepare": "yarn build", + "publish-alpha": "yarn publish --tag alpha", + "publish-stable": "yarn publish --tag latest", + "test": "jest", + "test:watch": "jest --watch" + }, + "exports": { + ".": { + "import": "./dist/esm/index.js", + "require": "./dist/cjs/index.js", + "types": "./dist/types/index.d.ts" + }, + "./*.js": { + "import": "./dist/esm/*.js", + "require": "./dist/cjs/*.js", + "types": "./dist/types/*.d.ts" + }, + "./*": { + "import": "./dist/esm/*.js", + "require": "./dist/cjs/*.js", + "types": "./dist/types/*.d.ts" + } + }, + "typesVersions": { + "*": { + "index.d.ts": [ + "dist/types/index.d.ts" + ], + "*": [ + "dist/types/*" + ] + } + }, + "files": [ + "dist", + "docs", + "CHANGELOG.md", + "LICENSE", + "README.md" + ], + "dependencies": { + "@ucans/core": "*", + "@stablelib/ed25519": "^1.0.2", + "big-integer": "^1.6.51", + "one-webcrypto": "^1.0.3", + "uint8arrays": "^3.0.0" + }, + "devDependencies": { + "@types/jest": "^27.4.1", + "@types/node": "^17.0.23", + "@typescript-eslint/eslint-plugin": "^5.18.0", + "@typescript-eslint/parser": "^5.18.0", + "copyfiles": "^2.4.1", + "eslint": "^8.12.0", + "fast-check": "^2.24.0", + "jest": "^27.5.1", + "rimraf": "^3.0.2", + "ts-jest": "^27.1.4", + "typescript": "^4.6.3", + "yarn": "^1.22.18" + } +} diff --git a/packages/default/src/crypto/ecdsa.ts b/packages/defaults/src/crypto/ecdsa.ts similarity index 77% rename from packages/default/src/crypto/ecdsa.ts rename to packages/defaults/src/crypto/ecdsa.ts index 91df05a..ab1a63f 100644 --- a/packages/default/src/crypto/ecdsa.ts +++ b/packages/defaults/src/crypto/ecdsa.ts @@ -1,17 +1,15 @@ import { webcrypto } from "one-webcrypto" -import { NamedCurve, KeyType, PrivateKeyJwk } from "../types.js" +import { AvailableCryptoKeyPair, PrivateKeyJwk } from "../types.js" export const ALG = "ECDSA" export const DEFAULT_CURVE = "P-256" export const DEFAULT_HASH_ALG = "SHA-256" -export const generateKeypair = async ( - namedCurve: NamedCurve = DEFAULT_CURVE -): Promise => { +export const generateKeypair = async (): Promise => { return await webcrypto.subtle.generateKey( { name: ALG, - namedCurve, + namedCurve: DEFAULT_CURVE, }, false, [ "sign", "verify" ] @@ -20,15 +18,14 @@ export const generateKeypair = async ( export const importKeypairJwk = async ( privKeyJwk: PrivateKeyJwk, - namedCurve: NamedCurve = DEFAULT_CURVE, exportable = false -): Promise => { +): Promise => { const privateKey = await webcrypto.subtle.importKey( "jwk", privKeyJwk, { name: ALG, - namedCurve, + namedCurve: DEFAULT_CURVE, }, exportable, ["sign" ] @@ -40,7 +37,7 @@ export const importKeypairJwk = async ( pubKeyJwk, { name: ALG, - namedCurve, + namedCurve: DEFAULT_CURVE, }, true, [ "verify" ] @@ -90,13 +87,4 @@ export const verify = async ( sig.buffer, msg.buffer ) -} - -export const toKeyType = (namedCurve: NamedCurve): KeyType => { - switch (namedCurve) { - case "P-256": - return "p256" - default: - throw new Error(`Unsupported namedCurve: ${namedCurve}`) - } -} +} \ No newline at end of file diff --git a/packages/default/src/crypto/index.ts b/packages/defaults/src/crypto/index.ts similarity index 100% rename from packages/default/src/crypto/index.ts rename to packages/defaults/src/crypto/index.ts diff --git a/packages/default/src/crypto/rsa.ts b/packages/defaults/src/crypto/rsa.ts similarity index 100% rename from packages/default/src/crypto/rsa.ts rename to packages/defaults/src/crypto/rsa.ts diff --git a/packages/defaults/src/did/index.ts b/packages/defaults/src/did/index.ts new file mode 100644 index 0000000..4f1c71a --- /dev/null +++ b/packages/defaults/src/did/index.ts @@ -0,0 +1,3 @@ +export * from "./prefix.js" +export * from "./transformers.js" +export * from "./validation.js" diff --git a/packages/default/src/did/prefix.ts b/packages/defaults/src/did/prefix.ts similarity index 98% rename from packages/default/src/did/prefix.ts rename to packages/defaults/src/did/prefix.ts index c0063c5..09071c6 100644 --- a/packages/default/src/did/prefix.ts +++ b/packages/defaults/src/did/prefix.ts @@ -37,8 +37,6 @@ export function magicBytes(keyType: KeyType): Uint8Array | null { return P256_DID_PREFIX case "rsa": return RSA_DID_PREFIX - case "bls12-381": - return BLS_DID_PREFIX default: return null } diff --git a/packages/default/src/did/pubkey-compress.ts b/packages/defaults/src/did/pubkey-compress.ts similarity index 100% rename from packages/default/src/did/pubkey-compress.ts rename to packages/defaults/src/did/pubkey-compress.ts diff --git a/packages/default/src/did/transformers.ts b/packages/defaults/src/did/transformers.ts similarity index 97% rename from packages/default/src/did/transformers.ts rename to packages/defaults/src/did/transformers.ts index 65fb2a0..07a51cd 100644 --- a/packages/default/src/did/transformers.ts +++ b/packages/defaults/src/did/transformers.ts @@ -3,7 +3,8 @@ import * as uint8arrays from "uint8arrays" import * as compression from "./pubkey-compress.js" import * as rsa from "../crypto/rsa.js" import { BASE58_DID_PREFIX, RSA_DID_PREFIX_OLD, magicBytes, parseMagicBytes, hasPrefix } from "./prefix.js" -import { KeyType, Encodings } from "../types.js" +import { KeyType } from "../types.js" +import { Encodings } from "@ucans/core" // DID → PUBLIC KEY diff --git a/packages/default/src/did/validation.ts b/packages/defaults/src/did/validation.ts similarity index 100% rename from packages/default/src/did/validation.ts rename to packages/defaults/src/did/validation.ts diff --git a/packages/defaults/src/index.ts b/packages/defaults/src/index.ts new file mode 100644 index 0000000..77b233c --- /dev/null +++ b/packages/defaults/src/index.ts @@ -0,0 +1,4 @@ +export * from "./keypair/ed25519.js" +export * from "./keypair/rsa.js" +export * from "./did/index.js" +export * as keypair from "./keypair/index.js" \ No newline at end of file diff --git a/packages/default/src/keypair/base.ts b/packages/defaults/src/keypair/base.ts similarity index 86% rename from packages/default/src/keypair/base.ts rename to packages/defaults/src/keypair/base.ts index f4081cf..c08d6d5 100644 --- a/packages/default/src/keypair/base.ts +++ b/packages/defaults/src/keypair/base.ts @@ -1,7 +1,10 @@ import * as uint8arrays from "uint8arrays" +import { Keypair, Didable, Encodings, ExportableKey } from '@ucans/core' import { publicKeyBytesToDid } from "../did/transformers.js" -import { Keypair, KeyType, Encodings, Didable, ExportableKey } from "../types.js" +import { KeyType } from "../types.js" + + export default abstract class BaseKeypair implements Keypair, Didable, ExportableKey { diff --git a/packages/default/src/keypair/ecdsa.ts b/packages/defaults/src/keypair/ecdsa.ts similarity index 72% rename from packages/default/src/keypair/ecdsa.ts rename to packages/defaults/src/keypair/ecdsa.ts index 8ae1a72..a304fbf 100644 --- a/packages/default/src/keypair/ecdsa.ts +++ b/packages/defaults/src/keypair/ecdsa.ts @@ -1,12 +1,11 @@ import { webcrypto } from "one-webcrypto" import * as uint8arrays from "uint8arrays" +import { Encodings } from '@ucans/core' import * as ecdsa from "../crypto/ecdsa.js" import { AvailableCryptoKeyPair, - Encodings, isAvailableCryptoKeyPair, - NamedCurve, PrivateKeyJwk, } from "../types.js" import BaseKeypair from "./base.js" @@ -17,43 +16,40 @@ export class EcdsaKeypair extends BaseKeypair { constructor( keypair: AvailableCryptoKeyPair, publicKey: Uint8Array, - namedCurve: NamedCurve, exportable: boolean ) { - super(publicKey, ecdsa.toKeyType(namedCurve), exportable) + super(publicKey, "p256", exportable) this.keypair = keypair } static async create(params?: { - namedCurve?: NamedCurve exportable?: boolean }): Promise { - const { namedCurve = "P-256", exportable = false } = params || {} - const keypair = await ecdsa.generateKeypair(namedCurve) + const { exportable = false } = params || {} + const keypair = await ecdsa.generateKeypair() if (!isAvailableCryptoKeyPair(keypair)) { throw new Error(`Couldn't generate valid keypair`) } const publicKey = await ecdsa.exportKey(keypair.publicKey) - return new EcdsaKeypair(keypair, publicKey, namedCurve, exportable) + return new EcdsaKeypair(keypair, publicKey, exportable) } static async importFromJwk( jwk: PrivateKeyJwk, params?: { - namedCurve?: NamedCurve exportable?: boolean }): Promise { - const { namedCurve = "P-256", exportable = false } = params || {} - const keypair = await ecdsa.importKeypairJwk(jwk, namedCurve, exportable) + const { exportable = false } = params || {} + const keypair = await ecdsa.importKeypairJwk(jwk, exportable) if (!isAvailableCryptoKeyPair(keypair)) { throw new Error(`Couldn't generate valid keypair`) } const publicKey = await ecdsa.exportKey(keypair.publicKey) - return new EcdsaKeypair(keypair, publicKey, namedCurve, exportable) + return new EcdsaKeypair(keypair, publicKey, exportable) } async sign(msg: Uint8Array): Promise { diff --git a/packages/default/src/keypair/ed25519.ts b/packages/defaults/src/keypair/ed25519.ts similarity index 97% rename from packages/default/src/keypair/ed25519.ts rename to packages/defaults/src/keypair/ed25519.ts index a4c49a7..fc7661a 100644 --- a/packages/default/src/keypair/ed25519.ts +++ b/packages/defaults/src/keypair/ed25519.ts @@ -2,7 +2,7 @@ import * as ed25519 from "@stablelib/ed25519" import * as uint8arrays from "uint8arrays" import BaseKeypair from "./base.js" -import { Encodings } from "../types.js" +import { Encodings } from "@ucans/core" export class EdKeypair extends BaseKeypair { diff --git a/packages/default/src/keypair/index.ts b/packages/defaults/src/keypair/index.ts similarity index 100% rename from packages/default/src/keypair/index.ts rename to packages/defaults/src/keypair/index.ts diff --git a/packages/default/src/keypair/rsa.ts b/packages/defaults/src/keypair/rsa.ts similarity index 91% rename from packages/default/src/keypair/rsa.ts rename to packages/defaults/src/keypair/rsa.ts index 296b1bf..61acd20 100644 --- a/packages/default/src/keypair/rsa.ts +++ b/packages/defaults/src/keypair/rsa.ts @@ -3,7 +3,8 @@ import * as uint8arrays from "uint8arrays" import * as rsa from "../crypto/rsa.js" import BaseKeypair from "./base.js" -import { Encodings, AvailableCryptoKeyPair, isAvailableCryptoKeyPair } from "../types.js" +import { AvailableCryptoKeyPair, isAvailableCryptoKeyPair } from "../types.js" +import { Encodings } from "@ucans/core" export class RsaKeypair extends BaseKeypair { diff --git a/packages/defaults/src/types.ts b/packages/defaults/src/types.ts new file mode 100644 index 0000000..082388f --- /dev/null +++ b/packages/defaults/src/types.ts @@ -0,0 +1,24 @@ +export interface AvailableCryptoKeyPair { + privateKey: CryptoKey + publicKey: CryptoKey +} + +export type PublicKeyJwk = { + kty: string + crv: string + x: string + y: string +} + +export type KeyType = + | "rsa" + | "p256" + | "ed25519" + | "bls12-381" + +export type PrivateKeyJwk = PublicKeyJwk & { d: string } + + +export function isAvailableCryptoKeyPair(keypair: CryptoKeyPair): keypair is AvailableCryptoKeyPair { + return keypair.publicKey != null && keypair.privateKey != null +} \ No newline at end of file diff --git a/packages/core/tests/did-key.ts b/packages/defaults/tests/did-key.test.ts similarity index 100% rename from packages/core/tests/did-key.ts rename to packages/defaults/tests/did-key.test.ts diff --git a/packages/core/tests/did.test.ts b/packages/defaults/tests/did.test.ts similarity index 100% rename from packages/core/tests/did.test.ts rename to packages/defaults/tests/did.test.ts diff --git a/packages/core/tests/ecdsa.test.ts b/packages/defaults/tests/ecdsa.test.ts similarity index 100% rename from packages/core/tests/ecdsa.test.ts rename to packages/defaults/tests/ecdsa.test.ts diff --git a/packages/core/tests/ed25519.test.ts b/packages/defaults/tests/ed25519.test.ts similarity index 100% rename from packages/core/tests/ed25519.test.ts rename to packages/defaults/tests/ed25519.test.ts diff --git a/packages/core/tests/rsa.test.ts b/packages/defaults/tests/rsa.test.ts similarity index 100% rename from packages/core/tests/rsa.test.ts rename to packages/defaults/tests/rsa.test.ts diff --git a/packages/defaults/tsconfig.eslint.json b/packages/defaults/tsconfig.eslint.json new file mode 100644 index 0000000..e662081 --- /dev/null +++ b/packages/defaults/tsconfig.eslint.json @@ -0,0 +1,5 @@ +{ + "extends": "./tsconfig.json", + "include": ["**/*.ts"], + "exclude": [] +} diff --git a/packages/defaults/tsconfig.json b/packages/defaults/tsconfig.json new file mode 100644 index 0000000..b59f12b --- /dev/null +++ b/packages/defaults/tsconfig.json @@ -0,0 +1,12 @@ +{ + "compilerOptions": { + "target": "ES2015", + "strict": true, + "moduleResolution": "node", + "esModuleInterop": true, + "noFallthroughCasesInSwitch": true + }, + "include": [ + "src/**/*" + ] +} \ No newline at end of file diff --git a/packages/ucans/package.json b/packages/ucans/package.json index aa5c3dd..8b0a422 100644 --- a/packages/ucans/package.json +++ b/packages/ucans/package.json @@ -19,7 +19,7 @@ "dist": "yarn run dist:prep && yarn run dist:src && yarn run dist:cjs && yarn run dist:esm && yarn run dist:types && yarn run dist:pkg", "dist:cjs": "tsc --project ./dist/ --module commonjs --outDir ./dist/cjs/ --sourceMap", "dist:esm": "tsc --project ./dist/ --module es2020 --outDir ./dist/esm/ --sourceMap", - "dist:pkg": "node ./scripts/package.js", + "dist:pkg": "node ../../scripts/package.js", "dist:prep": "copyfiles --error tsconfig.json ./dist/", "dist:src": "copyfiles --error --up 1 \"./src/**/*\" ./dist/src/", "dist:types": "tsc --project ./dist/ --emitDeclarationOnly --declaration --declarationDir ./dist/types/", @@ -65,10 +65,8 @@ "README.md" ], "dependencies": { - "@stablelib/ed25519": "^1.0.2", - "big-integer": "^1.6.51", - "one-webcrypto": "^1.0.3", - "uint8arrays": "^3.0.0" + "@ucans/core": "*", + "@ucans/defaults": "*" }, "devDependencies": { "@types/jest": "^27.4.1", diff --git a/packages/ucans/src/attenuation.ts b/packages/ucans/src/attenuation.ts deleted file mode 100644 index 26ee68b..0000000 --- a/packages/ucans/src/attenuation.ts +++ /dev/null @@ -1,419 +0,0 @@ -import * as token from "./token.js" -import { Capability } from "./capability/index.js" -import { Ucan } from "./types.js" -import { ResourcePointer } from "./capability/resource-pointer.js" -import { Ability } from "./capability/ability.js" -import { SUPERUSER, Superuser } from "./capability/super-user.js" - - -// TYPES - - -/** - * UCAN capabilities can have arbitrary semantics for delegation. - * These semantics can be configured via this record of functions. - * - * In most cases you may just want to use `equalCanDelegate` as your semantics, - * but sometimes you want e.g. path behavior for a file-system-like resource: - * `path:/parent/` should be able to delegate access to `path:/parent/child/`. - */ -export interface DelegationSemantics { - /** - * Whether a parent resource can delegate a child resource. - * - * An implementation may for example decide to return true for - * `canDelegateResource(resourcePointer.parse("path:/parent/"), resourcePointer.parse("path:/parent/child/"))` - */ - canDelegateResource(parentResource: ResourcePointer, childResource: ResourcePointer): boolean - /** - * Whether a parent ability can delegate a child ability. - * - * An implementation may for example decide to return true for - * `canDelegateAbility(ability.parse("crud/UPDATE"), ability.parse("crud/CREATE"))` - */ - canDelegateAbility(parentAbility: Ability, childAbility: Ability): boolean -} - - -/** - * A delegation chain for a delegated capability or delegated ownership. - * - * This type represents a valid path of delegations through a UCAN. - * - * It can be cached as a sort of "witness" that a UCAN actually delegates a particular capability. - * - * Or it can be scanned to look for UCANs that may have become invalid due to revocation. - */ -export type DelegationChain - = DelegatedCapability - | DelegatedOwnership - - -/** - * A delegation chain that ends with a concrete capability. - */ -export interface DelegatedCapability { - /** - * The capability that the end of the chain grants. - */ - capability: Capability - /** - * The specific UCAN in the chain witnessing the delegated capability. - */ - ucan: Ucan - // will probably become an array in the future due to rights amplification - /** - * The rest of the delegation chain. This may include entries - * for `DelegatedOwnership`. - */ - chainStep?: DelegationChain -} - -/** - * A delegation chain that ends with delegated ownership. - * - * This is ownership over a specific DID at a certain resource and ability scope. - */ -export interface DelegatedOwnership { - /** - * The DID that ownership is delegated for. - */ - ownershipDID: string - /** - * The kinds of capabilites that can be delegated from the ownership. - */ - scope: OwnershipScope - /** - * The specific UCAN in the chain witnessing the delegated ownership. - */ - ucan: Ucan - /** - * The rest of the ownership delegation chain. - */ - chainStep?: DelegatedOwnership -} - - -/** - * This describes the scope of capabilities that are allowed to be delegated - * from delegated ownership. - */ -export type OwnershipScope - = Superuser - | { scheme: string; ability: Ability } - - - -// FUNCTIONS - - -/** - * This computes all possible delegations from given UCAN with given - * capability delegation semantics. - * - * For each entry in the attenuations array of the UCAN there will be at least - * one delegation chain. - * - * These delegation chains are computed lazily, so that if parts of the UCAN have - * been revoked or can't be loaded, this doesn't keep this function from figuring - * out different ways of delegating a capability from the attenuations. - * It also makes it possible to return early if a valid delegation chain has been found. - */ -export async function* delegationChains( - semantics: DelegationSemantics, - ucan: Ucan, - isRevoked: (ucan: Ucan) => Promise = async () => false -): AsyncIterable { - - if (await isRevoked(ucan)) { - yield new Error(`UCAN Revoked: ${token.encode(ucan)}`) - return - } - - yield* capabilitiesFromParenthood(ucan) - yield* capabilitiesFromDelegation(semantics, ucan, isRevoked) -} - - -/** - * Figures out the implied root issuer from a delegation chain. - * - * For a given delegation chain this will give you the DID of who - * "started" the chain, so who claims to be the "owner" of said capability. - */ -export function rootIssuer(delegationChain: DelegationChain): string { - if ("capability" in delegationChain) { - return delegationChain.chainStep == null - ? delegationChain.ucan.payload.iss - : rootIssuer(delegationChain.chainStep) - } - return delegationChain.ownershipDID -} - - -/** - * The default delegation semantics. - * This will just allow equal capabilities to be delegated, - * except that it also accounts for superuser abilities. - */ -export const equalCanDelegate: DelegationSemantics = { - canDelegateResource(parentResource, childResource) { - if (parentResource.scheme !== childResource.scheme) { - return false - } - - return parentResource.hierPart === childResource.hierPart - }, - - canDelegateAbility(parentAbility, childAbility) { - if (parentAbility === SUPERUSER) { - return true - } - if (childAbility === SUPERUSER) { - return false - } - - if (parentAbility.namespace !== childAbility.namespace) { - return false - } - - // Array equality - if (parentAbility.segments.length !== childAbility.segments.length) { - return false - } - return parentAbility.segments.reduce( - (acc, v, i) => acc && childAbility.segments[ i ] === v, - true as boolean - ) - }, -} - - -export function capabilityCanBeDelegated( - semantics: DelegationSemantics, - capability: Capability, - fromDelegationChain: DelegationChain, -): boolean { - if ("capability" in fromDelegationChain) { - return canDelegate(semantics, fromDelegationChain.capability, capability) - } - const ownershipScope = fromDelegationChain.scope - if (ownershipScope === SUPERUSER) { - return true - } - return ownershipScope.scheme == capability.with.scheme - && semantics.canDelegateAbility(ownershipScope.ability, capability.can) -} - - -export function ownershipCanBeDelegated( - semantics: DelegationSemantics, - did: string, - scope: OwnershipScope, - fromDelegationChain: DelegatedOwnership -): boolean { - if (did !== fromDelegationChain.ownershipDID) { - return false - } - - const parentScope = fromDelegationChain.scope - - // parent OwnershipScope can delegate child OwnershipScope - - if (parentScope === SUPERUSER) { - return true - } - if (scope === SUPERUSER) { - return false - } - return parentScope.scheme === scope.scheme - && semantics.canDelegateAbility(parentScope.ability, scope.ability) -} - - - -// ㊙️ Internal - - -function* capabilitiesFromParenthood(ucan: Ucan): Iterable { - for (const capability of ucan.payload.att) { - switch (capability.with.scheme.toLowerCase()) { - // If it's a "my" capability, it'll indicate an ownership delegation - case "my": { - const scope = capability.with.hierPart === SUPERUSER - ? SUPERUSER - : { scheme: capability.with.hierPart, ability: capability.can } - - yield { - ownershipDID: ucan.payload.iss, - scope, - ucan, - } - break - } - // if it's another known capability, we can ignore them - // (they're not introduced by parenthood) - case "as": - case "prf": - break - // otherwise we assume it's a normal parenthood capability introduction - default: - yield { capability, ucan } - } - } -} - - -async function* capabilitiesFromDelegation( - semantics: DelegationSemantics, - ucan: Ucan, - isRevoked: (ucan: Ucan) => Promise -): AsyncIterable { - - let proofIndex = 0 - - for await (const proof of token.validateProofs(ucan)) { - if (proof instanceof Error) { - yield proof - continue - } - - for (const capability of ucan.payload.att) { - try { - switch (capability.with.scheme.toLowerCase()) { - case "my": continue // cannot be delegated, only introduced by parenthood. - case "as": { - yield* handleAsDelegation(semantics, capability, ucan, proof, isRevoked) - break - } - case "prf": { - yield* handlePrfDelegation(semantics, capability, ucan, proof, proofIndex, isRevoked) - break - } - default: { - yield* handleNormalDelegation(semantics, capability, ucan, proof, isRevoked) - } - } - } catch (e) { - yield error(e) - } - } - - proofIndex++ - } - - function error(e: unknown): Error { - if (e instanceof Error) { - return e - } else { - return new Error(`Error during capability delegation checking: ${e}`) - } - } -} - - -async function* handleAsDelegation( - semantics: DelegationSemantics, - capability: Capability, - ucan: Ucan, - proof: Ucan, - isRevoked: (ucan: Ucan) => Promise -): AsyncIterable { - const split = capability.with.hierPart.split(":") - const scheme = split[ split.length - 1 ] - const ownershipDID = split.slice(0, -1).join(":") - const scope = scheme === SUPERUSER - ? SUPERUSER - : { scheme, ability: capability.can } - - for await (const delegationChain of delegationChains(semantics, proof, isRevoked)) { - if (delegationChain instanceof Error) { - yield delegationChain - continue - } - if (!("ownershipDID" in delegationChain)) { - continue - } - if (ownershipCanBeDelegated( - semantics, - ownershipDID, - scope, - delegationChain - )) { - yield { - ownershipDID, - scope, - ucan, - chainStep: delegationChain - } - } - } -} - - -async function* handlePrfDelegation( - semantics: DelegationSemantics, - capability: Capability, - ucan: Ucan, - proof: Ucan, - proofIndex: number, - isRevoked: (ucan: Ucan) => Promise -): AsyncIterable { - if ( - capability.with.hierPart !== SUPERUSER - && parseInt(capability.with.hierPart, 10) !== proofIndex - ) { - // if it's something like prf:2, we need to make sure that - // we only process the delegation if proofIndex === 2 - return - } - for await (const delegationChain of delegationChains(semantics, proof, isRevoked)) { - if (delegationChain instanceof Error) { - yield delegationChain - continue - } - if (!("capability" in delegationChain)) { - continue - } - yield { - capability: delegationChain.capability, - ucan, - chainStep: delegationChain - } - } -} - - -async function* handleNormalDelegation( - semantics: DelegationSemantics, - capability: Capability, - ucan: Ucan, - proof: Ucan, - isRevoked: (ucan: Ucan) => Promise -): AsyncIterable { - for await (const delegationChain of delegationChains(semantics, proof, isRevoked)) { - if (delegationChain instanceof Error) { - yield delegationChain - continue - } - if (!capabilityCanBeDelegated(semantics, capability, delegationChain)) { - continue - } - yield { - capability, - ucan, - chainStep: delegationChain - } - } -} - - -function canDelegate( - semantics: DelegationSemantics, - parentCapability: Capability, - childCapability: Capability, -): boolean { - return semantics.canDelegateResource(parentCapability.with, childCapability.with) - && semantics.canDelegateAbility(parentCapability.can, childCapability.can) -} - diff --git a/packages/ucans/src/builder.ts b/packages/ucans/src/builder.ts deleted file mode 100644 index b5831d7..0000000 --- a/packages/ucans/src/builder.ts +++ /dev/null @@ -1,289 +0,0 @@ -import * as token from "./token.js" -import * as util from "./util.js" - -import { Keypair, Fact, UcanPayload, isKeypair, Ucan } from "./types.js" -import { Capability, isCapability } from "./capability/index.js" -import { capabilityCanBeDelegated, DelegationSemantics, DelegationChain } from "./attenuation.js" -import { Store } from "./store.js" -import { publicKeyBytesToDid } from "./did/transformers.js" - - -export interface BuildableState { - issuer: Keypair - audience: string - expiration: number -} - -function isBuildableState(obj: unknown): obj is BuildableState { - return util.isRecord(obj) - && util.hasProp(obj, "issuer") && isKeypair(obj.issuer) - && util.hasProp(obj, "audience") && typeof obj.audience === "string" - && util.hasProp(obj, "expiration") && typeof obj.expiration === "number" -} - -interface DefaultableState { - capabilities: Capability[] - facts: Fact[] - proofs: Ucan[] - addNonce: boolean - notBefore?: number -} - -// the state neccessary for being able to lookup fitting capabilities in the UCAN store -export interface CapabilityLookupCapableState { - issuer: Keypair - expiration: number -} - -function isCapabilityLookupCapableState(obj: unknown): obj is CapabilityLookupCapableState { - return util.isRecord(obj) - && util.hasProp(obj, "issuer") && isKeypair(obj.issuer) - && util.hasProp(obj, "expiration") && typeof obj.expiration === "number" -} - -/** - * A builder API for UCANs. - * - * Supports grabbing UCANs from a UCAN `Store` for proofs (see `delegateCapability`). - * - * Example usage: - * - * ```ts - * const ucan = await Builder.create() - * .issuedBy(aliceKeypair) - * .toAudience(bobDID) - * .withLifetimeInSeconds(30) - * .claimCapability({ email: "my@email.com", cap: "SEND" }) - * .delegateCapability(emailSemantics, { email: "my-friends@email.com", cap: "SEND" }, proof) - * .build() - * ``` - */ -export class Builder> { - - private state: State // portion of the state that's required to be set before building - private defaultable: DefaultableState // portion of the state that has sensible defaults - - private constructor(state: State, defaultable: DefaultableState) { - this.state = state - this.defaultable = defaultable - } - - /** - * Create an empty builder. - * Before finalising the builder, you need to at least call - * - `issuedBy` - * - `toAudience` and - * - `withLifetimeInSeconds` or `withExpiration`. - * To finalise the builder, call its `build` or `buildPayload` method. - */ - static create(): Builder> { - return new Builder({}, { capabilities: [], facts: [], proofs: [], addNonce: false }) - } - - /** - * @param issuer The issuer as a DID string ("did:key:..."). - * - * The UCAN must be signed with the private key of the issuer to be valid. - */ - issuedBy(issuer: Keypair): Builder { - if (!isKeypair(issuer)) { - throw new TypeError(`Expected a Keypair, but got ${issuer}`) - } - return new Builder({ ...this.state, issuer }, this.defaultable) - } - - /** - * @param audience The audience as a DID string ("did:key:..."). - * - * This is the identity this UCAN transfers rights to. - * It could e.g. be the DID of a service you're posting this UCAN as a JWT to, - * or it could be the DID of something that'll use this UCAN as a proof to - * continue the UCAN chain as an issuer. - */ - toAudience(audience: string): Builder { - if (typeof audience !== "string") { - throw new TypeError(`Expected audience DID as string, but got ${audience}`) - } - return new Builder({ ...this.state, audience }, this.defaultable) - } - - /** - * @param seconds The number of seconds from the calltime of this function - * to set the expiry timestamp to. - */ - withLifetimeInSeconds(seconds: number): Builder { - if (typeof seconds !== "number") { - throw new TypeError(`Expected seconds as number, but got ${seconds}`) - } - if (!isFinite(seconds) || seconds <= 0) { - throw new TypeError(`Expected seconds to be a positive number, but got ${seconds}`) - } - return this.withExpiration(Math.floor(Date.now() / 1000) + seconds) - } - - /** - * @param expiration The UTCTime timestamp (in seconds) for when the UCAN should expire. - */ - withExpiration(expiration: number): Builder { - if (typeof expiration !== "number" || !isFinite(expiration)) { - throw new TypeError(`Expected expiration as number, but got ${expiration}`) - } - if (this.defaultable.notBefore != null && expiration < this.defaultable.notBefore) { - throw new Error(`Can't set expiration to ${expiration} which is before 'notBefore': ${this.defaultable.notBefore}`) - } - return new Builder({ ...this.state, expiration }, this.defaultable) - } - - /** - * @param notBeforeTimestamp The UTCTime timestamp (in seconds) of when the UCAN becomes active. - */ - withNotBefore(notBeforeTimestamp: number): Builder { - if (typeof notBeforeTimestamp !== "number" || !isFinite(notBeforeTimestamp)) { - throw new TypeError(`Expected notBeforeTimestamp as number, but got ${notBeforeTimestamp}`) - } - if (util.hasProp(this.state, "expiration") && typeof this.state.expiration === "number" && this.state.expiration < notBeforeTimestamp) { - throw new Error(`Can't set 'notBefore' to ${notBeforeTimestamp} which is after expiration: ${this.state.expiration}`) - } - return new Builder(this.state, { ...this.defaultable, notBefore: notBeforeTimestamp }) - } - - /** - * @param fact Any fact or proof of knowledge in this UCAN as a record. - * @param facts Arbitrary more facts or proofs of knowledge. - */ - withFact(fact: Fact): Builder - withFact(fact: Fact, ...facts: Fact[]): Builder - withFact(fact: Fact, ...facts: Fact[]): Builder { - if (!util.isRecord(fact) || facts.some(fct => !util.isRecord(fct))) { - throw new TypeError(`Expected fact(s) to be a record, but got ${fact}`) - } - return new Builder(this.state, { - ...this.defaultable, - facts: [ ...this.defaultable.facts, fact, ...facts ] - }) - } - - /** - * Will ensure that the built UCAN includes a number used once. - */ - withNonce(): Builder { - return new Builder(this.state, { ...this.defaultable, addNonce: true }) - } - - /** - * Claim capabilities 'by parenthood'. - */ - claimCapability(capability: Capability): Builder - claimCapability(capability: Capability, ...capabilities: Capability[]): Builder - claimCapability(capability: Capability, ...capabilities: Capability[]): Builder { - if (!isCapability(capability)) { - throw new TypeError(`Expected capability, but got ${JSON.stringify(capability, null, " ")}`) - } - return new Builder(this.state, { - ...this.defaultable, - capabilities: [ ...this.defaultable.capabilities, capability, ...capabilities ] - }) - } - - /** - * Delegate capabilities from a given proof to the audience of the UCAN you're building. - * - * @param semantics The rules for which delegations of capabilities are allowed. - * @param requiredCapability The capability you want to delegate. - * - * Then, one of - * @param proof The proof chain that grants the issuer of this UCAN at least the capabilities you want to delegate, or - * @param store The UCAN store in which to try to find a UCAN granting you enough capabilities to delegate given capabilities. - * - * @throws If given store can't provide a UCAN for delegating given capability - * @throws If given proof can't be used to delegate given capability - * @throws If the builder hasn't set an issuer and expiration yet - */ - delegateCapability(requiredCapability: Capability, store: Store): State extends CapabilityLookupCapableState ? Builder : never - delegateCapability(requiredCapability: Capability, proof: DelegationChain, semantics: DelegationSemantics): State extends CapabilityLookupCapableState ? Builder : never - delegateCapability(requiredCapability: Capability, storeOrProof: Store | DelegationChain, semantics?: DelegationSemantics): Builder { - if (!isCapability(requiredCapability)) { - throw new TypeError(`Expected 'requiredCapability' as a second argument, but got ${requiredCapability}`) - } - if (!isCapabilityLookupCapableState(this.state)) { - throw new Error(`Can't delegate capabilities without having these paramenters set in the builder: issuer and expiration.`) - } - - function isProof(proof: Store | DelegationChain): proof is DelegationChain { - return util.hasProp(proof, "capability") || util.hasProp(proof, "ownershipDID") - } - - if (isProof(storeOrProof)) { - if (semantics == null) { - throw new TypeError(`Expected 'semantics' as third argument if a 'proof' DelegationChain was passed as second.`) - } - const proof: DelegationChain = storeOrProof - const ucan = proof.ucan - if (!capabilityCanBeDelegated(semantics, requiredCapability, proof)) { - throw new Error(`Can't add capability to UCAN: Given proof doesn't give required rights to delegate.`) - } - return new Builder(this.state, { - ...this.defaultable, - capabilities: [ ...this.defaultable.capabilities, requiredCapability ], - proofs: this.defaultable.proofs.find(p => token.encode(p) === token.encode(ucan)) == null - ? [ ...this.defaultable.proofs, ucan ] - : this.defaultable.proofs - }) - } else { - const store: Store = storeOrProof - const issuer = publicKeyBytesToDid(this.state.issuer.publicKey, this.state.issuer.keyType) - // we look up a proof that has our issuer as an audience - const result = util.first(store.findWithCapability(issuer, requiredCapability, issuer)) - if (result != null) { - const ucan = result.ucan - const ucanEncoded = token.encode(ucan) - return new Builder(this.state, { - ...this.defaultable, - capabilities: [ ...this.defaultable.capabilities, requiredCapability ], - proofs: this.defaultable.proofs.find(proof => token.encode(proof) === ucanEncoded) == null - ? [ ...this.defaultable.proofs, ucan ] - : this.defaultable.proofs - }) - } else { - throw new Error(`Couldn't add capability to UCAN. Couldn't find anything providing this capability in given store.`) - } - } - } - - /** - * Build the UCAN body. This can be used if you want to sign the UCAN yourself afterwards. - */ - buildPayload(): State extends BuildableState ? UcanPayload : never - buildPayload(): UcanPayload { - if (!isBuildableState(this.state)) { - throw new Error(`Builder is missing one of the required properties before it can be built: issuer, audience and expiration.`) - } - return token.buildPayload({ - issuer: publicKeyBytesToDid(this.state.issuer.publicKey, this.state.issuer.keyType), - audience: this.state.audience, - - expiration: this.state.expiration, - notBefore: this.defaultable.notBefore, - addNonce: this.defaultable.addNonce, - - capabilities: this.defaultable.capabilities, - facts: this.defaultable.facts, - proofs: this.defaultable.proofs.map(proof => token.encode(proof)), - }) - } - - /** - * Finalize: Build and sign the UCAN. - * - * @throws If the builder hasn't yet been set an issuer, audience and expiration. - */ - async build(): Promise - async build(): Promise { - if (!isBuildableState(this.state)) { - throw new Error(`Builder is missing one of the required properties before it can be built: issuer, audience and expiration.`) - } - const payload = this.buildPayload() - return await token.signWithKeypair(payload, this.state.issuer) - } - -} diff --git a/packages/ucans/src/capability/ability.ts b/packages/ucans/src/capability/ability.ts deleted file mode 100644 index 873c5b4..0000000 --- a/packages/ucans/src/capability/ability.ts +++ /dev/null @@ -1,94 +0,0 @@ -import { Superuser, SUPERUSER } from "./super-user.js" -import * as util from "../util.js" - - -// 💎 - - -export type Ability - = Superuser - | { namespace: string; segments: string[] } - - -/** - * Separator for an ability's segments. - */ -export const SEPARATOR: string = "/" - - -/** - * Ability that can be used with a `prf` resource-pointer. - * This redelegates all capabilities of the proof(s). - */ -export const REDELEGATE: Ability = { namespace: "ucan", segments: [ "DELEGATE" ] } - - - -// TYPE CHECKS - - -export function isAbility(obj: unknown): obj is Ability { - return obj === SUPERUSER - || ( - util.isRecord(obj) - && util.hasProp(obj, "namespace") && typeof obj.namespace === "string" - && util.hasProp(obj, "segments") && Array.isArray(obj.segments) && obj.segments.every(s => typeof s === "string") - ) -} - - - -// 🛠 - - -export function isEqual(a: Ability, b: Ability): boolean { - if (a === SUPERUSER && b === SUPERUSER) return true - if (a === SUPERUSER || b === SUPERUSER) return false - - return ( - a.namespace.toLowerCase() === - b.namespace.toLowerCase() - ) && - ( - joinSegments(a.segments).toLowerCase() === - joinSegments(b.segments).toLowerCase() - ) -} - - -export function joinSegments(segments: string[]): string { - return segments.join(SEPARATOR) -} - - - -// ENCODING - - -/** - * Encode an ability. - * - * @param ability The ability to encode - */ -export function encode(ability: Ability): string { - switch (ability) { - case SUPERUSER: return ability - default: return joinSegments([ ability.namespace, ...ability.segments ]) - } -} - -/** - * Parse an encoded ability. - * - * @param ability The encoded ability - */ -export function parse(ability: string): Ability { - switch (ability) { - case SUPERUSER: - return SUPERUSER - default: { - const [ namespace, ...segments ] = ability.split(SEPARATOR) - return { namespace, segments } - } - } -} \ No newline at end of file diff --git a/packages/ucans/src/capability/index.ts b/packages/ucans/src/capability/index.ts deleted file mode 100644 index 148306d..0000000 --- a/packages/ucans/src/capability/index.ts +++ /dev/null @@ -1,120 +0,0 @@ -import * as ability from "./ability.js" -import * as resourcePointer from "./resource-pointer.js" -import * as superUser from "./super-user.js" -import * as util from "../util.js" - -import { Ability, isAbility } from "./ability.js" -import { ResourcePointer, isResourcePointer } from "./resource-pointer.js" -import { Superuser, SUPERUSER } from "./super-user.js" - - -// RE-EXPORTS - - -export { ability, resourcePointer, superUser } - - - -// 💎 - - -export type Capability = { - with: ResourcePointer - can: Ability -} - -export type EncodedCapability = { - with: string - can: string -} - - - -// TYPE CHECKS - - -export function isCapability(obj: unknown): obj is Capability { - return util.isRecord(obj) - && util.hasProp(obj, "with") && isResourcePointer(obj.with) - && util.hasProp(obj, "can") && isAbility(obj.can) -} - -export function isEncodedCapability(obj: unknown): obj is EncodedCapability { - return util.isRecord(obj) - && util.hasProp(obj, "with") && typeof obj.with === "string" - && util.hasProp(obj, "can") && typeof obj.can === "string" -} - - - -// 🌸 - - -export function as(did: string, resource: Superuser | string): Capability { - return { - with: resourcePointer.as(did, resource), - can: SUPERUSER - } -} - - -export function my(resource: Superuser | string): Capability { - return { - with: resourcePointer.my(resource), - can: SUPERUSER - } -} - - -export function prf(selector: Superuser | number, ability: Ability): Capability { - return { - with: resourcePointer.prf(selector), - can: ability - } -} - - - -// 🛠 - - -/** - * Check if two capabilities are equal. - * - * This is not the same as `JSON.stringify(capA) === JSON.stringify(capB)`. - * Specifically: - * - For resource pointers, it does case-insensitive matching of the `scheme`. - * - For abilities, it does case-insensitive matching of the namespace and segments. - */ -export function isEqual(a: Capability, b: Capability): boolean { - return resourcePointer.isEqual(a.with, b.with) && ability.isEqual(a.can, b.can) -} - - - -// ENCODING - - -/** - * Encode the individual parts of a capability. - * - * @param cap The capability to encode - */ -export function encode(cap: Capability): EncodedCapability { - return { - with: resourcePointer.encode(cap.with), - can: ability.encode(cap.can) - } -} - -/** - * Parse an encoded capability. - * - * @param cap The encoded capability - */ -export function parse(cap: EncodedCapability): Capability { - return { - with: resourcePointer.parse(cap.with), - can: ability.parse(cap.can) - } -} \ No newline at end of file diff --git a/packages/ucans/src/capability/resource-pointer.ts b/packages/ucans/src/capability/resource-pointer.ts deleted file mode 100644 index 60781da..0000000 --- a/packages/ucans/src/capability/resource-pointer.ts +++ /dev/null @@ -1,89 +0,0 @@ -import { Superuser, SUPERUSER } from "./super-user.js" -import * as util from "../util.js" - - -// 💎 - - -export type ResourcePointer = { - scheme: string - hierPart: Superuser | string -} - - -/** - * Separator for pieces of a URI. - */ -export const SEPARATOR: string = ":" - - - -// TYPE CHECKS - - -export function isResourcePointer(obj: unknown): obj is ResourcePointer { - return util.isRecord(obj) - && util.hasProp(obj, "scheme") && typeof obj.scheme === "string" - && util.hasProp(obj, "hierPart") && (obj.hierPart === SUPERUSER || typeof obj.hierPart === "string") -} - - - -// 🌸 - - -export function as(did: string, resource: Superuser | string): ResourcePointer { - return { - scheme: "as", - hierPart: `${did}:${resource}` - } -} - - -export function my(resource: Superuser | string): ResourcePointer { - return { - scheme: "my", - hierPart: resource - } -} - - -export function prf(selector: Superuser | number): ResourcePointer { - return { - scheme: "prf", - hierPart: selector.toString() - } -} - - - -// 🛠 - - -export function isEqual(a: ResourcePointer, b: ResourcePointer): boolean { - return a.scheme.toLowerCase() === a.scheme.toLowerCase() && a.hierPart === b.hierPart -} - - - -// ENCODING - - -/** - * Encode a resource pointer. - * - * @param pointer The resource pointer to encode - */ -export function encode(pointer: ResourcePointer): string { - return `${pointer.scheme}${SEPARATOR}${pointer.hierPart}` -} - -/** - * Parse an encoded resource pointer. - * - * @param pointer The encoded resource pointer - */ -export function parse(pointer: string): ResourcePointer { - const [ scheme, ...hierPart ] = pointer.split(SEPARATOR) - return { scheme, hierPart: hierPart.join(SEPARATOR) } -} \ No newline at end of file diff --git a/packages/ucans/src/capability/super-user.ts b/packages/ucans/src/capability/super-user.ts deleted file mode 100644 index 5432133..0000000 --- a/packages/ucans/src/capability/super-user.ts +++ /dev/null @@ -1,10 +0,0 @@ -export const SUPERUSER: Superuser = "*" -export type Superuser = "*" // maximum ability - - -// TYPE CHECKS - - -export function isSuperuser(obj: unknown): obj is Superuser { - return obj === SUPERUSER -} \ No newline at end of file diff --git a/packages/ucans/src/compatibility.ts b/packages/ucans/src/compatibility.ts deleted file mode 100644 index 2f447fe..0000000 --- a/packages/ucans/src/compatibility.ts +++ /dev/null @@ -1,114 +0,0 @@ -// A module to hold all the ugly compatibility logic -// for getting from old UCANs to newer version UCANs. - -import * as semver from "./semver.js" - -import * as util from "./util.js" -import { SUPERUSER } from "./capability/super-user.js" -import { UcanParts, isUcanHeader, isUcanPayload } from "./types.js" -import { my } from "./capability/index.js" - - -const VERSION_0_3 = { major: 0, minor: 3, patch: 0 } - -type UcanHeader_0_3_0 = { - alg: string - typ: string - uav: string -} - -type UcanPayload_0_3_0 = { - iss: string - aud: string - nbf?: number - exp: number - rsc: string | Record - ptc: string - prf?: string -} - -function isUcanHeader_0_3_0(obj: unknown): obj is UcanHeader_0_3_0 { - return util.isRecord(obj) - && util.hasProp(obj, "alg") && typeof obj.alg === "string" - && util.hasProp(obj, "typ") && typeof obj.typ === "string" - && util.hasProp(obj, "uav") && typeof obj.uav === "string" -} - -function isUcanPayload_0_3_0(obj: unknown): obj is UcanPayload_0_3_0 { - return util.isRecord(obj) - && util.hasProp(obj, "iss") && typeof obj.iss === "string" - && util.hasProp(obj, "aud") && typeof obj.aud === "string" - && (!util.hasProp(obj, "nbf") || typeof obj.nbf === "number") - && util.hasProp(obj, "exp") && typeof obj.exp === "number" - && util.hasProp(obj, "rsc") && (typeof obj.rsc === "string" || util.isRecord(obj)) - && util.hasProp(obj, "ptc") && typeof obj.ptc === "string" - && (!util.hasProp(obj, "prf") || typeof obj.prf === "string") -} - - -export function handleCompatibility(header: unknown, payload: unknown): UcanParts { - const fail = (place: string, reason: string) => new Error(`Can't parse UCAN ${place}: ${reason}`) - - if (!util.isRecord(header)) throw fail("header", "Invalid format: Expected a record") - - // parse either the "ucv" or "uav" as a version in the header - // we translate 'uav: 1.0.0' into 'ucv: 0.3.0' - let version: "0.8.1" | "0.3.0" = "0.8.1" - if (!util.hasProp(header, "ucv") || typeof header.ucv !== "string") { - if (!util.hasProp(header, "uav") || typeof header.uav !== "string") { - throw fail("header", "Invalid format: Missing version indicator") - } else if (header.uav !== "1.0.0") { - throw fail("header", `Unsupported version 'uav: ${header.uav}'`) - } - version = "0.3.0" - } else if (semver.lt(header.ucv, "0.8.0")) { - throw fail("header", `Unsupported version 'ucv: ${header.ucv}'`) - } - - if (semver.gte(version, "0.8.0")) { - if (typeof header.ucv !== "string") { - throw fail("header", "Invalid format: Missing 'ucv' key or 'ucv' is not a string") - } - header.ucv = semver.parse(header.ucv) - if (header.ucv == null) { - throw fail("header", "Invalid format: 'ucv' string cannot be parsed into a semantic version") - } - if (!isUcanHeader(header)) throw fail("header", "Invalid format") - if (!isUcanPayload(payload)) throw fail("payload", "Invalid format") - return { header, payload } - } - - // we know it's version 0.3.0 - if (!isUcanHeader_0_3_0(header)) throw fail("header", "Invalid version 0.3.0 format") - if (!isUcanPayload_0_3_0(payload)) throw fail("payload", "Invalid version 0.3.0 format") - - return { - header: { - alg: header.alg, - typ: header.typ, - ucv: VERSION_0_3, - }, - payload: { - iss: payload.iss, - aud: payload.aud, - nbf: payload.nbf, - exp: payload.exp, - att: (() => { - if (payload.rsc === SUPERUSER || typeof payload.rsc === "string") return [ - my(SUPERUSER) - ] - - const resources: Record = payload.rsc - return Object.keys(resources).map(rscKey => { - return { - with: { scheme: rscKey, hierPart: resources[ rscKey ] }, - can: payload.ptc === SUPERUSER - ? SUPERUSER - : { namespace: rscKey, segments: [ payload.ptc ] } - } - }) - })(), - prf: payload.prf != null ? [ payload.prf ] : [] - }, - } -} diff --git a/packages/ucans/src/did.ts b/packages/ucans/src/did.ts deleted file mode 100644 index 3161ab5..0000000 --- a/packages/ucans/src/did.ts +++ /dev/null @@ -1,3 +0,0 @@ -export * from "./did/prefix.js" -export * from "./did/transformers.js" -export * from "./did/validation.js" diff --git a/packages/ucans/src/did/prefix.ts b/packages/ucans/src/did/prefix.ts deleted file mode 100644 index c0063c5..0000000 --- a/packages/ucans/src/did/prefix.ts +++ /dev/null @@ -1,106 +0,0 @@ -import * as uint8arrays from "uint8arrays" -import * as compression from "./pubkey-compress.js" -import { KeyType } from "../types.js" - -// Each prefix is varint-encoded. So e.g. 0x1205 gets varint-encoded to 0x8524 -// The varint encoding is described here: https://github.com/multiformats/unsigned-varint -// These varints are encoded big-endian in 7-bit pieces. -// So 0x1205 is split up into 0x12 and 0x05 -// Because there's another byte to be read, the MSB of 0x05 is set: 0x85 -// The next 7 bits encode as 0x24 (instead of 0x12) => 0x8524 - -/** https://github.com/multiformats/multicodec/blob/e9ecf587558964715054a0afcc01f7ace220952c/table.csv#L94 */ -export const EDWARDS_DID_PREFIX = new Uint8Array([ 0xed, 0x01 ]) -/** https://github.com/multiformats/multicodec/blob/e9ecf587558964715054a0afcc01f7ace220952c/table.csv#L91 */ -export const BLS_DID_PREFIX = new Uint8Array([ 0xea, 0x01 ]) -/** https://github.com/multiformats/multicodec/blob/e9ecf587558964715054a0afcc01f7ace220952c/table.csv#L141 */ -export const P256_DID_PREFIX = new Uint8Array([ 0x80, 0x24 ]) -/** https://github.com/multiformats/multicodec/blob/e9ecf587558964715054a0afcc01f7ace220952c/table.csv#L142 */ -export const P384_DID_PREFIX = new Uint8Array([ 0x81, 0x24 ]) -/** https://github.com/multiformats/multicodec/blob/e9ecf587558964715054a0afcc01f7ace220952c/table.csv#L143 */ -export const P521_DID_PREFIX = new Uint8Array([ 0x82, 0x24 ]) -/** https://github.com/multiformats/multicodec/blob/e9ecf587558964715054a0afcc01f7ace220952c/table.csv#L146 */ -export const RSA_DID_PREFIX = new Uint8Array([ 0x85, 0x24 ]) -/** Old RSA DID prefix, used pre-standardisation */ -export const RSA_DID_PREFIX_OLD = new Uint8Array([ 0x00, 0xf5, 0x02 ]) - -export const BASE58_DID_PREFIX = "did:key:z" // z is the multibase prefix for base58btc byte encoding - -/** - * Magic bytes. - */ -export function magicBytes(keyType: KeyType): Uint8Array | null { - switch (keyType) { - case "ed25519": - return EDWARDS_DID_PREFIX - case "p256": - return P256_DID_PREFIX - case "rsa": - return RSA_DID_PREFIX - case "bls12-381": - return BLS_DID_PREFIX - default: - return null - } -} - -/** - * Parse magic bytes on prefixed key-bytes - * to determine cryptosystem & the unprefixed key-bytes. - */ -export const parseMagicBytes = ( - prefixedKey: Uint8Array -): { - keyBytes: Uint8Array - type: KeyType -} => { - // RSA - if (hasPrefix(prefixedKey, RSA_DID_PREFIX)) { - return { - keyBytes: prefixedKey.slice(RSA_DID_PREFIX.byteLength), - type: "rsa", - } - - // RSA OLD - } else if (hasPrefix(prefixedKey, RSA_DID_PREFIX_OLD)) { - return { - keyBytes: prefixedKey.slice(RSA_DID_PREFIX_OLD.byteLength), - type: "rsa", - } - - // EC P-256 - } else if (hasPrefix(prefixedKey, P256_DID_PREFIX)) { - const compressedKey = prefixedKey.slice(P256_DID_PREFIX.byteLength) - const keyBytes = compression.decompressNistP256Pubkey(compressedKey) - return { - keyBytes, - type: "p256", - } - - // EDWARDS - } else if (hasPrefix(prefixedKey, EDWARDS_DID_PREFIX)) { - return { - keyBytes: prefixedKey.slice(EDWARDS_DID_PREFIX.byteLength), - type: "ed25519", - } - - // BLS - } else if (hasPrefix(prefixedKey, BLS_DID_PREFIX)) { - return { - keyBytes: prefixedKey.slice(BLS_DID_PREFIX.byteLength), - type: "bls12-381", - } - } - - throw new Error("Unsupported key algorithm. Try using RSA.") -} - -/** - * Determines if a Uint8Array has a given indeterminate length-prefix. - */ -export const hasPrefix = ( - prefixedKey: Uint8Array, - prefix: Uint8Array -): boolean => { - return uint8arrays.equals(prefix, prefixedKey.subarray(0, prefix.byteLength)) -} diff --git a/packages/ucans/src/did/pubkey-compress.ts b/packages/ucans/src/did/pubkey-compress.ts deleted file mode 100644 index fe9f7c2..0000000 --- a/packages/ucans/src/did/pubkey-compress.ts +++ /dev/null @@ -1,78 +0,0 @@ -import bigInt from "big-integer" -import * as uint8arrays from "uint8arrays" - -// Compression & Decompression algos from: -// https://stackoverflow.com/questions/48521840/biginteger-to-a-uint8array-of-bytes - -// Public key compression for NIST P-256 -export const compressNistP256Pubkey = (pubkeyBytes: Uint8Array): Uint8Array => { - if (pubkeyBytes.length !== 65) { - throw new Error("Expected 65 byte pubkey") - } else if (pubkeyBytes[0] !== 0x04) { - throw new Error("Expected first byte to be 0x04") - } - // first byte is a prefix - const x = pubkeyBytes.slice(1, 33) - const y = pubkeyBytes.slice(33, 65) - const out = new Uint8Array(x.length + 1) - - out[0] = 2 + (y[y.length - 1] & 1) - out.set(x, 1) - - return out -} - -// Public key decompression for NIST P-256 -export const decompressNistP256Pubkey = (compressed: Uint8Array): Uint8Array => { - if (compressed.length !== 33) { - throw new Error("Expected 33 byte compress pubkey") - } else if (compressed[0] !== 0x02 && compressed[0] !== 0x03) { - throw new Error("Expected first byte to be 0x02 or 0x03") - } - // Consts for P256 curve - const two = bigInt(2) - // 115792089210356248762697446949407573530086143415290314195533631308867097853951 - const prime = two - .pow(256) - .subtract(two.pow(224)) - .add(two.pow(192)) - .add(two.pow(96)) - .subtract(1) - const b = bigInt( - "41058363725152142129326129780047268409114441015993725554835256314039467401291", - ) - - // Pre-computed value, or literal - const pIdent = prime.add(1).divide(4) // 28948022302589062190674361737351893382521535853822578548883407827216774463488 - - // This value must be 2 or 3. 4 indicates an uncompressed key, and anything else is invalid. - const signY = bigInt(compressed[0] - 2) - const x = compressed.slice(1) - const xBig = bigInt(uint8arrays.toString(x, "base10")) - - // y^2 = x^3 - 3x + b - const maybeY = xBig - .pow(3) - .subtract(xBig.multiply(3)) - .add(b) - .modPow(pIdent, prime) - - let yBig - // If the parity matches, we found our root, otherwise it's the other root - if (maybeY.mod(2).equals(signY)) { - yBig = maybeY - } else { - // y = prime - y - yBig = prime.subtract(maybeY) - } - const y = uint8arrays.fromString(yBig.toString(10), "base10") - - // left-pad for smaller than 32 byte y - const offset = 32 - y.length - const yPadded = new Uint8Array(32) - yPadded.set(y, offset) - - // concat coords & prepend P-256 prefix - const publicKey = uint8arrays.concat([[0x04], x, yPadded]) - return publicKey -} diff --git a/packages/ucans/src/did/transformers.ts b/packages/ucans/src/did/transformers.ts deleted file mode 100644 index 65fb2a0..0000000 --- a/packages/ucans/src/did/transformers.ts +++ /dev/null @@ -1,105 +0,0 @@ -import * as uint8arrays from "uint8arrays" - -import * as compression from "./pubkey-compress.js" -import * as rsa from "../crypto/rsa.js" -import { BASE58_DID_PREFIX, RSA_DID_PREFIX_OLD, magicBytes, parseMagicBytes, hasPrefix } from "./prefix.js" -import { KeyType, Encodings } from "../types.js" - - -// DID → PUBLIC KEY - - -/** - * Convert a DID (did:key) to a base64 public key. - */ -export function didToPublicKey(did: string, encoding: Encodings = "base64pad"): { - publicKey: string - type: KeyType -} { - const { publicKey, type } = didToPublicKeyBytes(did) - return { - publicKey: uint8arrays.toString(publicKey, encoding), - type - } -} - -/** - * Convert a DID (did:key) to the public key into bytes in SubjectPublicKeyInfo (spki) format. - * - * For consumption e.g. in the WebCrypto API. - */ -export function didToPublicKeyBytes(did: string): { - publicKey: Uint8Array - type: KeyType -} { - if (!did.startsWith(BASE58_DID_PREFIX)) { - throw new Error("Please use a base58-encoded DID formatted `did:key:z...`") - } - - const didWithoutPrefix = did.slice(BASE58_DID_PREFIX.length) - const magicBytes = uint8arrays.fromString(didWithoutPrefix, "base58btc") - const parsed = parseMagicBytes(magicBytes) - - if (parsed.type === "rsa" && !hasPrefix(magicBytes, RSA_DID_PREFIX_OLD)) { - // DID RSA keys are ASN.1 DER encoded "RSAPublicKeys" (PKCS #1). - // But the WebCrypto API mostly works with "SubjectPublicKeyInfo" (SPKI), - // which wraps RSAPublicKey with some metadata. - // In an unofficial RSA multiformat we were using, we used SPKI, - // so we have to be careful not to transform *every* RSA DID to SPKI, but - // only newer DIDs. - parsed.keyBytes = rsa.convertRSAPublicKeyToSubjectPublicKeyInfo(parsed.keyBytes) - } - - return { - publicKey: parsed.keyBytes, - type: parsed.type, - } -} - - - -// PUBLIC KEY → DID - - -/** - * Convert a base64 public key to a DID (did:key). - */ -export function publicKeyToDid( - publicKey: string, - type: KeyType, - encoding: Encodings = "base64pad" -): string { - const pubKeyBytes = uint8arrays.fromString(publicKey, encoding) - return publicKeyBytesToDid(pubKeyBytes, type) -} - -/** - * Convert a public key in bytes to a DID (did:key). - */ -export function publicKeyBytesToDid( - publicKeyBytes: Uint8Array, - type: KeyType, -): string { - // Prefix public-write key - const prefix = magicBytes(type) - if (prefix === null) { - throw new Error(`Key type '${type}' not supported`) - } - - if (type === "rsa") { - // See also the comment in didToPublicKeyBytes - // In this library, we're assuming a single byte encoding for all types of keys. - // For RSA that is "SubjectPublicKeyInfo", because that's what the WebCrypto API understands. - // But DIDs assume that all public keys are encoded as "RSAPublicKey". - publicKeyBytes = rsa.convertSubjectPublicKeyInfoToRSAPublicKey(publicKeyBytes) - } - - if(type === "p256") { - publicKeyBytes = compression.compressNistP256Pubkey(publicKeyBytes) - } - - const prefixedBytes = uint8arrays.concat([ prefix, publicKeyBytes ]) - - // Encode prefixed - return BASE58_DID_PREFIX + uint8arrays.toString(prefixedBytes, "base58btc") -} diff --git a/packages/ucans/src/did/validation.ts b/packages/ucans/src/did/validation.ts deleted file mode 100644 index f472617..0000000 --- a/packages/ucans/src/did/validation.ts +++ /dev/null @@ -1,44 +0,0 @@ -import * as ed25519 from "@stablelib/ed25519" -import * as uint8arrays from "uint8arrays" - -import * as rsa from "../crypto/rsa.js" -import * as ecdsa from "../crypto/ecdsa.js" - -import { didToPublicKeyBytes } from "./transformers.js" - - -/** - * Verify the signature of some data (Uint8Array), given a DID. - */ -export async function verifySignature(data: Uint8Array, signature: Uint8Array, did: string): Promise { - try { - const { type, publicKey } = didToPublicKeyBytes(did) - - switch (type) { - - case "ed25519": - return ed25519.verify(publicKey, data, signature) - - case "rsa": - return await rsa.verify(data, signature, publicKey) - - case "p256": - return await ecdsa.verify(data, signature, publicKey, "P-256") - - default: return false - } - - } catch (_) { - return false - - } -} - -/** - * Verify the signature of some data (string encoded as utf8), given a DID. - */ -export async function verifySignatureUtf8(data: string, signature: string, did: string): Promise { - const dataBytes = uint8arrays.fromString(data, "utf8") - const sigBytes = uint8arrays.fromString(signature, "base64url") - return await verifySignature(dataBytes, sigBytes, did) -} diff --git a/packages/ucans/src/index.ts b/packages/ucans/src/index.ts index 7f0180e..9d9e80f 100644 --- a/packages/ucans/src/index.ts +++ b/packages/ucans/src/index.ts @@ -1,14 +1,19 @@ -export * from "./attenuation.js" -export * from "./builder.js" -export * from "./did.js" -export * from "./keypair/ed25519.js" -export * from "./keypair/rsa.js" -export * from "./store.js" -export * from "./token.js" -export * from "./types.js" -export * from "./verify.js" - -export * as keypair from "./keypair/index.js" -export * as capability from "./capability/index.js" - -export { Capability, EncodedCapability, isCapability } from "./capability/index.js" \ No newline at end of file +export * from "@ucans/core" +export * from "@ucans/defaults" + + + +// export * from "./attenuation.js" +// export * from "./builder.js" +// export * from "./did.js" +// export * from "./keypair/ed25519.js" +// export * from "./keypair/rsa.js" +// export * from "./store.js" +// export * from "./token.js" +// export * from "./types.js" +// export * from "./verify.js" + +// export * as keypair from "./keypair/index.js" +// export * as capability from "./capability/index.js" + +// export { Capability, EncodedCapability, isCapability } from "./capability/index.js" \ No newline at end of file diff --git a/packages/ucans/src/semver.ts b/packages/ucans/src/semver.ts deleted file mode 100644 index ae9d7aa..0000000 --- a/packages/ucans/src/semver.ts +++ /dev/null @@ -1,117 +0,0 @@ -import { hasProp, isRecord } from "./util" - - -// Types - -export interface SemVer { - major: number - minor: number - patch: number -} - -export function isSemVer(obj: unknown): obj is SemVer { - return isRecord(obj) - && hasProp(obj, "major") && typeof obj.major === "number" - && hasProp(obj, "minor") && typeof obj.minor === "number" - && hasProp(obj, "patch") && typeof obj.patch === "number" -} - - -// Parsing - -const NUM_REGEX = /^0|[1-9]\d*/ - -const matchesRegex = (regex: RegExp) => (str: string) => { - const m = str.match(regex) - if (!m) return false - return m[0].length === str.length -} - -export function parse(version: string): SemVer | null { - const parts = version.split(".") - - if (parts.length !== 3) { - return null - } - - if (!parts.every(matchesRegex(NUM_REGEX))) { - return null - } - - const [ major, minor, patch ] = parts.map(part => parseInt(part, 10)) - - if (!Number.isSafeInteger(major) || !Number.isSafeInteger(minor) || !Number.isSafeInteger(patch)) { - return null - } - if (major < 0 || minor < 0 || patch < 0) { - return null - } - - return { major, minor, patch } -} - - -// Formatting/Prettyprinting - -export function format(semver: SemVer): string { - return `${semver.major}.${semver.minor}.${semver.patch}` -} - - -// Comparison - -export const GT = 1 -export const EQ = 0 -export const LT = -1 -export type GT = typeof GT -export type EQ = typeof EQ -export type LT = typeof LT - -function comparePart(left?: number, right?: number): GT | EQ | LT { - // when at least one of them is null - if (left == null) { - if (right == null) return EQ - return LT - } else if (right == null) { - return GT - } - - // when none of them are null - if (left > right) return GT - if (left < right) return LT - return EQ -} - -export function compare(left: SemVer | string, right: SemVer | string): GT | EQ | LT { - const l = typeof left === "string" ? parse(left) : left - const r = typeof right === "string" ? parse(right) : right - return ( - comparePart(l?.major, r?.major) || - comparePart(l?.minor, r?.minor) || - comparePart(l?.patch, r?.patch) - ) -} - -export function lt(left: SemVer | string, right: SemVer | string): boolean { - return compare(left, right) === LT -} - -export function gte(left: SemVer | string, right: SemVer | string): boolean { - return !lt(left, right) -} - -export function gt(left: SemVer | string, right: SemVer | string): boolean { - return compare(left, right) === GT -} - -export function lte(left: SemVer | string, right: SemVer | string): boolean { - return !gt(left, right) -} - -export function eq(left: SemVer | string, right: SemVer | string): boolean { - return compare(left, right) === EQ -} - -export function neq(left: SemVer | string, right: SemVer | string): boolean { - return !eq(left, right) -} diff --git a/packages/ucans/src/store.ts b/packages/ucans/src/store.ts deleted file mode 100644 index aa9560d..0000000 --- a/packages/ucans/src/store.ts +++ /dev/null @@ -1,92 +0,0 @@ -import * as token from "./token.js" -import { capabilityCanBeDelegated, DelegationSemantics, DelegationChain, delegationChains, rootIssuer } from "./attenuation.js" -import { Ucan } from "./types.js" -import { Capability } from "./capability/index.js" - - -export interface IndexByAudience { - [ audienceDID: string ]: Array<{ - processedUcan: Ucan - capabilities: DelegationChain[] - }> -} - -export class Store { - - private index: IndexByAudience - private knownSemantics: DelegationSemantics - - constructor(knownSemantics: DelegationSemantics, index: IndexByAudience) { - this.index = index - this.knownSemantics = knownSemantics - } - - static async fromTokens(knownSemantics: DelegationSemantics, tokens: Iterable | AsyncIterable): Promise { - const store = new Store(knownSemantics, {}) - for await (const encodedUcan of tokens) { - const ucan = await token.validate(encodedUcan) - await store.add(ucan) - } - return store - } - - async add(ucan: Ucan): Promise { - const audience = ucan.payload.aud - const byAudience = this.index[ audience ] ?? [] - const encoded = token.encode(ucan) - - if (byAudience.find(stored => token.encode(stored.processedUcan) === encoded) != null) { - return - } - - const chains = [] - for await (const delegationChain of delegationChains(this.knownSemantics, ucan)) { - if (delegationChain instanceof Error) { - console.warn(`Delegation chain error while storing UCAN:`, delegationChain) - continue - } - chains.push(delegationChain) - } - - // Also do this *after* the all awaits to prevent races. - if (byAudience.find(stored => token.encode(stored.processedUcan) === encoded) != null) { - return - } - - byAudience.push({ - processedUcan: ucan, - capabilities: chains - }) - this.index[ audience ] = byAudience - } - - getByAudience(audience: string): Ucan[] { - return (this.index[ audience ] ?? []).map(elem => elem.processedUcan) - } - - findByAudience(audience: string, predicate: (ucan: Ucan) => boolean): Ucan | null { - return this.index[ audience ]?.find(elem => predicate(elem.processedUcan))?.processedUcan ?? null - } - - *findWithCapability( - audience: string, - requiredCapability: Capability, - requiredIssuer: string, - ): Iterable { - const cache = this.index[ audience ] - - if (cache == null) { - return - } - - for (const cacheElement of cache) { - for (const delegationChain of cacheElement.capabilities) { - if (capabilityCanBeDelegated(this.knownSemantics, requiredCapability, delegationChain) - && rootIssuer(delegationChain) === requiredIssuer) { - yield delegationChain - } - } - } - } - -} diff --git a/packages/ucans/src/token.ts b/packages/ucans/src/token.ts deleted file mode 100644 index 638f9f3..0000000 --- a/packages/ucans/src/token.ts +++ /dev/null @@ -1,460 +0,0 @@ -import * as uint8arrays from "uint8arrays" // @IMPORT - -import * as semver from "./semver.js" -import * as capability from "./capability/index.js" -import * as util from "./util.js" - -import { Capability, isCapability, isEncodedCapability } from "./capability/index.js" -import { Fact, KeyType, Keypair, Didable } from "./types.js" -import { Ucan, UcanHeader, UcanParts, UcanPayload } from "./types.js" -import { handleCompatibility } from "./compatibility.js" -import { verifySignatureUtf8 } from "./did/validation.js" - - -// CONSTANTS - - -const TYPE = "JWT" -const VERSION = { major: 0, minor: 8, patch: 1 } - - - -// COMPOSING - - -/** - * Create a UCAN, User Controlled Authorization Networks, JWT. - * - * ### Header - * - * `alg`, Algorithm, the type of signature. - * `typ`, Type, the type of this data structure, JWT. - * `ucv`, UCAN version. - * - * ### Payload - * - * `att`, Attenuation, a list of resources and capabilities that the ucan grants. - * `aud`, Audience, the ID of who it's intended for. - * `exp`, Expiry, unix timestamp of when the jwt is no longer valid. - * `fct`, Facts, an array of extra facts or information to attach to the jwt. - * `iss`, Issuer, the ID of who sent this. - * `nbf`, Not Before, unix timestamp of when the jwt becomes valid. - * `nnc`, Nonce, a randomly generated string, used to ensure the uniqueness of the jwt. - * `prf`, Proofs, nested tokens with equal or greater privileges. - * - */ -export async function build(params: { - // from/to - issuer: Keypair & Didable - audience: string - - // capabilities - capabilities?: Array - - // time bounds - lifetimeInSeconds?: number // expiration overrides lifetimeInSeconds - expiration?: number - notBefore?: number - - // proofs / other info - facts?: Array - proofs?: Array - addNonce?: boolean -}): Promise { - const keypair = params.issuer - const payload = buildPayload({ ...params, issuer: keypair.did() }) - return signWithKeypair(payload, keypair) -} - -/** - * Construct the payload for a UCAN. - */ -export function buildPayload(params: { - // from/to - issuer: string - audience: string - - // capabilities - capabilities?: Array - - // time bounds - lifetimeInSeconds?: number // expiration overrides lifetimeInSeconds - expiration?: number - notBefore?: number - - // proofs / other info - facts?: Array - proofs?: Array - addNonce?: boolean -}): UcanPayload { - const { - issuer, - audience, - capabilities = [], - lifetimeInSeconds = 30, - expiration, - notBefore, - facts, - proofs = [], - addNonce = false - } = params - - // Validate - if (!issuer.startsWith("did:")) throw new Error("The issuer must be a DID") - if (!audience.startsWith("did:")) throw new Error("The audience must be a DID") - - // Timestamps - const currentTimeInSeconds = Math.floor(Date.now() / 1000) - const exp = expiration || (currentTimeInSeconds + lifetimeInSeconds) - - // 📦 - return { - aud: audience, - att: capabilities, - exp, - fct: facts, - iss: issuer, - nbf: notBefore, - nnc: addNonce ? util.generateNonce() : undefined, - prf: proofs, - } -} - -/** - * Encloses a UCAN payload as to form a finalised UCAN. - */ -export async function sign( - payload: UcanPayload, - keyType: KeyType, - signFn: (data: Uint8Array) => Promise -): Promise { - const header: UcanHeader = { - alg: jwtAlgorithm(keyType), - typ: TYPE, - ucv: VERSION, - } - - // @TODO what about this?? - // Issuer key type must match UCAN algorithm - if (did.didToPublicKey(payload.iss).type !== keyType) { - throw new Error("The issuer's key type must match the given key type.") - } - - // Encode parts - const encodedHeader = encodeHeader(header) - const encodedPayload = encodePayload(payload) - - // Sign - const signedData = `${encodedHeader}.${encodedPayload}` - const toSign = uint8arrays.fromString(signedData, "utf8") - const sig = await signFn(toSign) - - // 📦 - // we freeze the object to make it more unlikely - // for signedData & header/payload to get out of sync - return Object.freeze({ - header, - payload, - signedData, - signature: uint8arrays.toString(sig, "base64url") - }) -} - -/** - * `sign` with a `Keypair`. - */ -export async function signWithKeypair( - payload: UcanPayload, - keypair: Keypair -): Promise { - return sign( - payload, - keypair.keyType, - data => keypair.sign(data) - ) -} - - - -// ENCODING - - -/** - * Encode a UCAN. - * - * @param ucan The UCAN to encode - */ -export function encode(ucan: Ucan): string { - return `${ucan.signedData}.${ucan.signature}` -} - -/** - * Encode the header of a UCAN. - * - * @param header The UcanHeader to encode - * @returns The header of a UCAN encoded as url-safe base64 JSON - */ -export function encodeHeader(header: UcanHeader): string { - const headerFormatted = { - ...header, - ucv: semver.format(header.ucv) - } - return uint8arrays.toString( - uint8arrays.fromString(JSON.stringify(headerFormatted), "utf8"), - "base64url" - ) -} - -/** - * Encode the payload of a UCAN. - * - * NOTE: This will encode capabilities as well, so that it matches the UCAN spec. - * In other words, `{ with: { scheme, hierPart }, can: { namespace, segments } }` - * becomes `{ with: "${scheme}:${hierPart}", can: "${namespace}/${segment}" }` - * - * @param payload The UcanPayload to encode - */ -export function encodePayload(payload: UcanPayload): string { - const payloadWithEncodedCaps = { - ...payload, - att: payload.att.map(capability.encode) - } - - return uint8arrays.toString( - uint8arrays.fromString(JSON.stringify(payloadWithEncodedCaps), "utf8"), - "base64url" - ) -} - -/** - * Parse an encoded UCAN. - * - * @param encodedUcan The encoded UCAN. - */ -export function parse(encodedUcan: string): UcanParts { - const [ encodedHeader, encodedPayload, signature ] = encodedUcan.split(".") - - if (encodedHeader == null || encodedPayload == null || signature == null) { - throw new Error(`Can't parse UCAN: ${encodedUcan}: Expected JWT format: 3 dot-separated base64url-encoded values.`) - } - - // Header - let headerJson: string - let headerObject: unknown - - try { - headerJson = uint8arrays.toString( - uint8arrays.fromString(encodedHeader, "base64url"), - "utf8" - ) - } catch { - throw new Error(`Can't parse UCAN header: ${encodedHeader}: Can't parse as base64url.`) - } - - try { - headerObject = JSON.parse(headerJson) - } catch { - throw new Error(`Can't parse UCAN header: ${encodedHeader}: Can't parse encoded JSON inside.`) - } - - // Payload - let payloadJson: string - let payloadObject: unknown - - try { - payloadJson = uint8arrays.toString( - uint8arrays.fromString(encodedPayload, "base64url"), - "utf8" - ) - } catch { - throw new Error(`Can't parse UCAN payload: ${encodedPayload}: Can't parse as base64url.`) - } - - try { - payloadObject = JSON.parse(payloadJson) - } catch { - throw new Error(`Can't parse UCAN payload: ${encodedPayload}: Can't parse encoded JSON inside.`) - } - - // Compatibility layer - const { header, payload } = handleCompatibility(headerObject, payloadObject) - - // Ensure proper types/structure - const parsedAttenuations = payload.att.reduce((acc: Capability[], cap: unknown): Capability[] => { - return isEncodedCapability(cap) - ? [ ...acc, capability.parse(cap) ] - : isCapability(cap) ? [ ...acc, cap ] : acc - }, []) - - // Fin - return { - header: header, - payload: { ...payload, att: parsedAttenuations } - } -} - - - -// VALIDATION - - -/** - * Validation options - */ -export interface ValidateOptions { - checkIssuer?: boolean - checkIsExpired?: boolean - checkIsTooEarly?: boolean - checkSignature?: boolean -} - -/** - * Parse & Validate **one layer** of a UCAN. - * This doesn't validate attenutations and doesn't validate the whole UCAN chain. - * - * By default, this will check the signature and time bounds. - * - * @param encodedUcan the JWT-encoded UCAN to validate - * @param options an optional parameter to configure turning off some validation options - * @returns the parsed & validated UCAN (one layer) - * @throws Error if the UCAN is invalid - */ -export async function validate(encodedUcan: string, options?: ValidateOptions): Promise { - const checkIssuer = options?.checkIssuer ?? true - const checkIsExpired = options?.checkIsExpired ?? true - const checkIsTooEarly = options?.checkIsTooEarly ?? true - const checkSignature = options?.checkSignature ?? true - - const { header, payload } = parse(encodedUcan) - const [ encodedHeader, encodedPayload, signature ] = encodedUcan.split(".") - - if (checkIssuer) { - const issuerKeyType = did.didToPublicKey(payload.iss).type - if (jwtAlgorithm(issuerKeyType) !== header.alg) { - throw new Error(`Invalid UCAN: ${encodedUcan}: Issuer key type does not match UCAN's \`alg\` property.`) - } - } - - if (checkSignature) { - if (!await verifySignatureUtf8(`${encodedHeader}.${encodedPayload}`, signature, payload.iss)) { - throw new Error(`Invalid UCAN: ${encodedUcan}: Signature invalid.`) - } - } - - const signedData = `${encodedHeader}.${encodedPayload}` - const ucan: Ucan = { header, payload, signedData, signature } - - if (checkIsExpired && isExpired(ucan)) { - throw new Error(`Invalid UCAN: ${encodedUcan}: Expired.`) - } - - if (checkIsTooEarly && isTooEarly(ucan)) { - throw new Error(`Invalid UCAN: ${encodedUcan}: Not active yet (too early).`) - } - - return ucan -} - -/** - * Proof validation options. - */ -export interface ValidateProofsOptions { - /** - * Whether to check if the ucan's issuer matches its proofs audiences. - */ - checkAddressing?: boolean - /** - * Whether to check if a ucan's time bounds are a subset of its proofs time bounds. - */ - checkTimeBoundsSubset?: boolean - /** - * Whether to check if a ucan's version is bigger or equal to its proofs version. - */ - checkVersionMonotonic?: boolean -} - -/** - * Iterates over all proofs and parses & validates them at the same time. - * - * If there's an audience/issuer mismatch, the iterated item will contain an `Error`. - * Otherwise the iterated out will contain a `Ucan`. - * - * @param ucan a parsed UCAN - * @param options optional ValidateOptions to use for validating each proof - * @return an async iterator of the given ucan's proofs parsed & validated, or an `Error` - * for each proof that couldn't be validated or parsed. - */ -export async function* validateProofs( - ucan: Ucan, - options?: ValidateOptions & ValidateProofsOptions -): AsyncIterable { - const checkAddressing = options?.checkAddressing ?? true - const checkTimeBoundsSubset = options?.checkTimeBoundsSubset ?? true - const checkVersionMonotonic = options?.checkVersionMonotonic ?? true - - for (const prf of ucan.payload.prf) { - try { - const proof = await validate(prf, options) - - if (checkAddressing && ucan.payload.iss !== proof.payload.aud) { - throw new Error(`Invalid Proof: Issuer ${ucan.payload.iss} doesn't match parent's audience ${proof.payload.aud}`) - } - - if (checkTimeBoundsSubset && proof.payload.nbf != null && ucan.payload.exp > proof.payload.nbf) { - throw new Error(`Invalid Proof: 'Not before' (${proof.payload.nbf}) is after parent's expiration (${ucan.payload.exp})`) - } - - if (checkTimeBoundsSubset && ucan.payload.nbf != null && ucan.payload.nbf > proof.payload.exp) { - throw new Error(`Invalid Proof: Expiration (${proof.payload.exp}) is before parent's 'not before' (${ucan.payload.nbf})`) - } - - if (checkVersionMonotonic && semver.lt(ucan.header.ucv, proof.header.ucv)) { - throw new Error(`Invalid Proof: Version (${proof.header.ucv}) is higher than parent's version (${ucan.header.ucv})`) - } - - yield proof - } catch (e) { - if (e instanceof Error) { - yield e - } else { - yield new Error(`Error when trying to parse UCAN proof: ${e}`) - } - } - } -} - -/** - * Check if a UCAN is expired. - * - * @param ucan The UCAN to validate - */ -export function isExpired(ucan: Ucan): boolean { - return ucan.payload.exp <= Math.floor(Date.now() / 1000) -} - -/** - * Check if a UCAN is not active yet. - * - * @param ucan The UCAN to validate - */ -export const isTooEarly = (ucan: Ucan): boolean => { - if (ucan.payload.nbf == null) return false - return ucan.payload.nbf > Math.floor(Date.now() / 1000) -} - - - -// ㊙️ - - -/** - * JWT algorithm to be used in a JWT header. - */ -function jwtAlgorithm(keyType: KeyType): string { - switch (keyType) { - case "bls12-381": throw new Error(`Unknown KeyType "${keyType}"`) - case "ed25519": return "EdDSA" - case "rsa": return "RS256" - default: throw new Error(`Unknown KeyType "${keyType}"`) - } -} diff --git a/packages/ucans/src/types.ts b/packages/ucans/src/types.ts deleted file mode 100644 index 7ad62e9..0000000 --- a/packages/ucans/src/types.ts +++ /dev/null @@ -1,143 +0,0 @@ -import * as semver from "./semver.js" -import { SemVer } from "./semver.js" -import { SupportedEncodings } from "uint8arrays/util/bases.js" // @IMPORT -import { Capability, isCapability, isEncodedCapability } from "./capability/index.js" -import * as util from "./util.js" - - -// 💎 - - -export type Ucan = { - header: UcanHeader - payload: UcanPayload - // We need to keep the encoded version around to preserve the signature - signedData: string - signature: string -} - - - -// CHUNKS - - -export interface UcanParts { - header: UcanHeader - payload: UcanPayload -} - -export type UcanHeader = { - alg: string - typ: string - ucv: SemVer -} - -export type UcanPayload = { - iss: string - aud: string - exp: number - nbf?: number - nnc?: string - att: Array - fct?: Array - prf: Array -} - - - -// FRAGMENTS - - -export type Fact = Record - - - -// CRYPTOGRAPHY - - -/** Unlike tslib's CryptoKeyPair, this requires the `privateKey` and `publicKey` fields */ -export interface AvailableCryptoKeyPair { - privateKey: CryptoKey - publicKey: CryptoKey -} - -export interface Didable { - publicKeyStr: (format?: Encodings) => string - did: () => string -} - -export interface ExportableKey { - export: (format?: Encodings) => Promise -} - -export interface Keypair { - publicKey: Uint8Array - keyType: KeyType - sign: (msg: Uint8Array) => Promise -} - -export type KeyType = - | "rsa" - | "p256" - | "ed25519" - | "bls12-381" - -// https://developer.mozilla.org/en-US/docs/Web/API/EcKeyGenParams -export type NamedCurve = "P-256" - -export type PublicKeyJwk = { - kty: string - crv: string - x: string - y: string -} - -export type PrivateKeyJwk = PublicKeyJwk & { d: string } - - -// MISC - - -export type Encodings = SupportedEncodings - - - -// TYPE CHECKS - - -export function isAvailableCryptoKeyPair(keypair: CryptoKeyPair): keypair is AvailableCryptoKeyPair { - return keypair.publicKey != null && keypair.privateKey != null -} - -export function isKeypair(obj: unknown): obj is Keypair { - return util.isRecord(obj) - && util.hasProp(obj, "publicKey") && obj.publicKey instanceof Uint8Array - && util.hasProp(obj, "keyType") && typeof obj.keyType === "string" - && util.hasProp(obj, "sign") && typeof obj.sign === "function" -} - -export function isUcanHeader(obj: unknown): obj is UcanHeader { - return util.isRecord(obj) - && util.hasProp(obj, "alg") && typeof obj.alg === "string" - && util.hasProp(obj, "typ") && typeof obj.typ === "string" - && util.hasProp(obj, "ucv") && semver.isSemVer(obj.ucv) -} - -export function isUcanPayload(obj: unknown): obj is UcanPayload { - return util.isRecord(obj) - && util.hasProp(obj, "iss") && typeof obj.iss === "string" - && util.hasProp(obj, "aud") && typeof obj.aud === "string" - && util.hasProp(obj, "exp") && typeof obj.exp === "number" - && (!util.hasProp(obj, "nbf") || typeof obj.nbf === "number") - && (!util.hasProp(obj, "nnc") || typeof obj.nnc === "string") - && util.hasProp(obj, "att") && Array.isArray(obj.att) && obj.att.every(a => isCapability(a) || isEncodedCapability(a)) - && (!util.hasProp(obj, "fct") || Array.isArray(obj.fct) && obj.fct.every(util.isRecord)) - && util.hasProp(obj, "prf") && Array.isArray(obj.prf) && obj.prf.every(str => typeof str === "string") -} - - - -// @TODO Sort these - -export type DidSignatureCheck = (did: string, signature: Uint8Array) => Promise -export type DidToJwtAlg = (did: string) => string \ No newline at end of file diff --git a/packages/ucans/src/util.ts b/packages/ucans/src/util.ts deleted file mode 100644 index ef2297f..0000000 --- a/packages/ucans/src/util.ts +++ /dev/null @@ -1,70 +0,0 @@ -const CHARS = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" - - -export const generateNonce = (len = 6): string => { - let nonce = "" - for (let i = 0; i < len; i++) { - nonce += CHARS[ Math.floor(Math.random() * CHARS.length) ] - } - return nonce -} - -export function hasProp(data: unknown, prop: K): data is Record { - return typeof data === "object" && data != null && prop in data -} - -export function isRecord(data: unknown): data is Record { - return typeof data === "object" && data != null -} - -export function isIterable(obj: unknown): obj is Iterable { - return typeof obj === "object" && obj != null && Symbol.iterator in obj -} - -export function isAsyncIterable(obj: unknown): obj is AsyncIterable { - return typeof obj === "object" && obj != null && Symbol.asyncIterator in obj -} - - -export function all(it: Iterable): T[] -export function all(it: AsyncIterable): Promise -export function all(it: Iterable | AsyncIterable): T[] | Promise { - if (isIterable(it)) { - const arr = [] - for (const elem of it) { - arr.push(elem) - } - return arr - } else if (isAsyncIterable(it)) { - return (async () => { - const arr = [] - for await (const elem of it) { - arr.push(elem) - } - return arr - })() - } else { - throw new TypeError(`Expected either Iterable or AsyncIterable, but got ${it}`) - } -} - -export function first(it: Iterable): T | undefined -export function first(it: AsyncIterable): Promise -export function first(it: Iterable | AsyncIterable): T | undefined | Promise { - if (isIterable(it)) { - for (const elem of it) { - return elem - } - return undefined - } else if (isAsyncIterable(it)) { - return (async () => { - for await (const elem of it) { - return elem - } - return undefined - })() - } else { - throw new TypeError(`Expected either Iterable or AsyncIterable, but got ${it}`) - } -} - diff --git a/packages/ucans/src/verify.ts b/packages/ucans/src/verify.ts deleted file mode 100644 index e6a5c1e..0000000 --- a/packages/ucans/src/verify.ts +++ /dev/null @@ -1,138 +0,0 @@ -import * as token from "./token.js" -import { capabilityCanBeDelegated, DelegationSemantics, DelegationChain, delegationChains, equalCanDelegate, rootIssuer } from "./attenuation.js" -import { Capability, isCapability } from "./capability/index.js" -import { Fact, Ucan } from "./types.js" - - -export type Result - = { ok: true; value: Ok } - | { ok: false; error: Err } - -const ok: (k: T) => Result = k => ({ ok: true, value: k }) -const err: (e: E) => Result = e => ({ ok: false, error: e }) - - -export interface VerifyOptions { - /** - * the DID of the callee of this function. The expected audience of the outermost level of the UCAN. - * NOTE: This DID should not be hardcoded in production calls to this function. - */ - audience: string - /** - * a non-empty list of capabilities required for this UCAN invocation. The root issuer and capability - * should be derived from something like your HTTP request parameters. They identify the resource - * that's access-controlled. - */ - requiredCapabilities: { capability: Capability; rootIssuer: string }[] - /** - * an optional record of functions that specify what the rules for delegating capabilities are. - * If not provided, the default semantics will be `equalCanDelegate`. - */ - semantics?: DelegationSemantics - /** - * an async predicate on UCANs to figure out whether they've been revoked or not. - * Usually that means checking whether the hash of the UCAN is in a list of revoked UCANs. - * If not provided, it will assume no UCAN to be revoked. - */ - isRevoked?: (ucan: Ucan) => Promise - /** - * an optional function that's given the list of facts in the root UCAN and returns a boolean indicating - * whether the facts include everything you expect for the UCAN invocation to check. - * By default this will ignore all facts in the UCAN and just return true. - */ - checkFacts?: (facts: Fact[]) => boolean -} - - -/** - * Verify a UCAN for an invocation. - * - * @param ucan a UCAN to verify for invocation in JWT format. (starts with 'eyJ...' and has two '.' in it) - * - * @param options required and optional verification options see {@link VerifyOptions} - * - * @throws TypeError if the passed arguments don't match what is expected - */ -export async function verify(ucan: string, options: VerifyOptions): Promise> { - const { audience, requiredCapabilities } = options - const semantics = options.semantics ?? equalCanDelegate - const isRevoked = options.isRevoked ?? (async () => false) - const checkFacts = options.checkFacts ?? (() => true) - // type-check arguments - if (typeof ucan !== "string") { - throw new TypeError(`Expected an encoded UCAN string as first argument, but got ${ucan}`) - } - if (typeof audience !== "string" || !audience.startsWith("did:")) { - throw new TypeError(`Expected a DID string as second argument, but got ${audience}`) - } - if (typeof isRevoked !== "function") { - throw new TypeError(`Expected a function returning a promise as third argument, but got ${isRevoked}`) - } - if (!Array.isArray(requiredCapabilities)) { - throw new TypeError(`Expected an array as fourth argument, but got ${requiredCapabilities}`) - } - if (requiredCapabilities.length < 1) { - throw new TypeError(`Expected a non-empty list of required capabilities as 4th argument.`) - } - if (requiredCapabilities.some(req => !isCapability(req.capability) || typeof req.rootIssuer !== "string" || !req.rootIssuer.startsWith("did:"))) { - throw new TypeError(`Expected an array of records of capabilities and rootIssuers as DID strings as 4th argument, but got ${requiredCapabilities}`) - } - if (typeof semantics.canDelegateResource !== "function" || typeof semantics.canDelegateAbility !== "function") { - throw new TypeError(`Expected a record with two functions 'canDelegateResource' and 'canDelegateAbility' as 5th argument, but got ${semantics}`) - } - if (typeof checkFacts !== "function") { - throw new TypeError(`Expected a function as 6th argument, but got ${checkFacts}`) - } - - try { - // Verify the UCAN - const decoded = await token.validate(ucan) - - // Check that it's addressed to us - if (decoded.payload.aud !== audience) { - return err([ new Error(`Invalid UCAN: Expected audience to be ${audience}, but it's ${decoded.payload.aud}`) ]) - } - - const errors: Error[] = [] - const remaining = new Set(requiredCapabilities) - const proven: Verification[] = [] - - // Check that all required capabilities are verified - loop: for await (const delegationChain of delegationChains(semantics, decoded, isRevoked)) { - if (delegationChain instanceof Error) { - errors.push(delegationChain) - continue - } - - // Try to look for capabilities from given delegation chain - for (const expected of remaining) { - if ( - capabilityCanBeDelegated(semantics, expected.capability, delegationChain) - && rootIssuer(delegationChain) === expected.rootIssuer - ) { - remaining.delete(expected) - proven.push({ - ...expected, - proof: delegationChain - }) - } - } - - // If we've already verified all, we don't need to keep looking - if (remaining.size === 0) { - break loop - } - } - - return remaining.size > 0 ? err(errors) : ok(proven) - - } catch (e) { - return err([ e instanceof Error ? e : new Error(`Unknown error during UCAN verification: ${e}`) ]) - } -} - -export interface Verification { - capability: Capability - rootIssuer: string - proof: DelegationChain -} diff --git a/packages/ucans/tests/attenuation.test.ts b/packages/ucans/tests/attenuation.test.ts new file mode 100644 index 0000000..a798d30 --- /dev/null +++ b/packages/ucans/tests/attenuation.test.ts @@ -0,0 +1,155 @@ +import * as token from "../src/token" +import { emailCapabilities, emailCapability } from "./capability/email" + +import { alice, bob, mallory } from "./fixtures" +import { all } from "../src/util" + + +describe("attenuation.emailCapabilities", () => { + + it("works with a simple example", async () => { + // alice -> bob, bob -> mallory + // alice delegates access to sending email as her to bob + // and bob delegates it further to mallory + const leafUcan = await token.build({ + issuer: alice, + audience: bob.did(), + capabilities: [ emailCapability("alice@email.com") ] + }) + + const ucan = await token.build({ + issuer: bob, + audience: mallory.did(), + capabilities: [ emailCapability("alice@email.com") ], + proofs: [ token.encode(leafUcan) ] + }) + + expect(await all(emailCapabilities(ucan))).toEqual([ + { + rootIssuer: bob.did(), + capability: emailCapability("alice@email.com") + }, + { + rootIssuer: alice.did(), + capability: emailCapability("alice@email.com") + } + ]) + }) + + it("reports the first issuer in the chain as originator", async () => { + // alice -> bob, bob -> mallory + // alice delegates nothing to bob + // and bob delegates his email to mallory + const leafUcan = await token.build({ + issuer: alice, + audience: bob.did(), + }) + + const ucan = await token.build({ + issuer: bob, + audience: mallory.did(), + capabilities: [ emailCapability("bob@email.com") ], + proofs: [ token.encode(leafUcan) ] + }) + + // we implicitly expect the originator to become bob + expect(await all(emailCapabilities(ucan))).toEqual([ { + rootIssuer: bob.did(), + capability: emailCapability("bob@email.com"), + } ]) + }) + + it("finds the right proof chain for the originator", async () => { + // alice -> mallory, bob -> mallory, mallory -> alice + // both alice and bob delegate their email access to mallory + // mallory then creates a UCAN with capability to send both + const leafUcanAlice = await token.build({ + issuer: alice, + audience: mallory.did(), + capabilities: [ emailCapability("alice@email.com") ] + }) + + const leafUcanBob = await token.build({ + issuer: bob, + audience: mallory.did(), + capabilities: [ emailCapability("bob@email.com") ] + }) + + const ucan = await token.build({ + issuer: mallory, + audience: alice.did(), + capabilities: [ + emailCapability("alice@email.com"), + emailCapability("bob@email.com") + ], + proofs: [ token.encode(leafUcanAlice), token.encode(leafUcanBob) ] + }) + + const chains = await all(emailCapabilities(ucan)) + + expect(chains).toEqual([ + // We expect two capabilities from parenthood: + { + rootIssuer: mallory.did(), + capability: emailCapability("alice@email.com") + }, + { + rootIssuer: mallory.did(), + capability: emailCapability("bob@email.com") + }, + // Then there's also the delegations + { + rootIssuer: alice.did(), + capability: emailCapability("alice@email.com") + }, + { + rootIssuer: bob.did(), + capability: emailCapability("bob@email.com") + } + ]) + }) + + it("reports all chain options", async () => { + // alice -> mallory, bob -> mallory, mallory -> alice + // both alice and bob claim to have access to alice@email.com + // and both grant that capability to mallory + // a verifier needs to know both to verify valid email access + + const aliceEmail = emailCapability("alice@email.com") + + const leafUcanAlice = await token.build({ + issuer: alice, + audience: mallory.did(), + capabilities: [ aliceEmail ] + }) + + const leafUcanBob = await token.build({ + issuer: bob, + audience: mallory.did(), + capabilities: [ aliceEmail ] + }) + + const ucan = await token.build({ + issuer: mallory, + audience: alice.did(), + capabilities: [ aliceEmail ], + proofs: [ token.encode(leafUcanAlice), token.encode(leafUcanBob) ] + }) + + expect(await all(emailCapabilities(ucan))).toEqual([ + { + rootIssuer: mallory.did(), + capability: aliceEmail + }, + { + rootIssuer: alice.did(), + capability: aliceEmail + }, + { + rootIssuer: bob.did(), + capability: aliceEmail + } + ]) + }) + +}) diff --git a/packages/ucans/tests/builder.test.ts b/packages/ucans/tests/builder.test.ts new file mode 100644 index 0000000..144ec0f --- /dev/null +++ b/packages/ucans/tests/builder.test.ts @@ -0,0 +1,135 @@ +import * as token from "../src/token" +import { Builder } from "../src/builder" +import { emailCapability } from "./capability/email" +import { wnfsCapability, wnfsPublicSemantics } from "./capability/wnfs" +import { EMAIL_SEMANTICS } from "./capability/email" +import { alice, bob, mallory } from "./fixtures" +import { delegationChains } from "../src/attenuation" +import { first } from "../src/util" + + +describe("Builder", () => { + + it("builds with a simple example", async () => { + const fact1 = { test: true } + const fact2 = { preimage: "abc", hash: "sth" } + const cap1 = emailCapability("alice@email.com") + const cap2 = wnfsCapability("alice.fission.name/public/", "SUPER_USER") + const expiration = Math.floor(Date.now() / 1000) + 30 + const notBefore = Math.floor(Date.now() / 1000) - 30 + + const ucan = await Builder.create() + .issuedBy(alice) + .toAudience(bob.did()) + .withExpiration(expiration) + .withNotBefore(notBefore) + .withFact(fact1, fact2) + .withNonce() + .claimCapability(cap1, cap2) + .build() + + expect(ucan.payload.iss).toEqual(alice.did()) + expect(ucan.payload.aud).toEqual(bob.did()) + expect(ucan.payload.exp).toEqual(expiration) + expect(ucan.payload.nbf).toEqual(notBefore) + expect(ucan.payload.fct).toEqual([ fact1, fact2 ]) + expect(ucan.payload.att).toEqual([ cap1, cap2 ]) + expect(ucan.payload.nnc).toBeDefined() + }) + + it("builds with lifetimeInSeconds", async () => { + const payload = Builder.create() + .issuedBy(alice) + .toAudience(bob.did()) + .withLifetimeInSeconds(300) + .buildPayload() + + expect(payload.exp).toBeGreaterThan(Date.now() / 1000 + 290) + }) + + it("prevents duplicate proofs", async () => { + const ucan = await Builder.create() + .issuedBy(alice) + .toAudience(bob.did()) + .withLifetimeInSeconds(30) + .claimCapability(wnfsCapability("alice.fission.name/public/", "SUPER_USER")) + .build() + + const publicCapability = await first(delegationChains(wnfsPublicSemantics, ucan)) + + if (publicCapability == null) { + throw "no capabilities" + } + + if (publicCapability instanceof Error) { + throw publicCapability + } + + const payload = Builder.create() + .issuedBy(bob) + .toAudience(mallory.did()) + .withLifetimeInSeconds(30) + .delegateCapability(wnfsCapability("alice.fission.name/public/Apps", "CREATE"), publicCapability, wnfsPublicSemantics) + .delegateCapability(wnfsCapability("alice.fission.name/public/Documents", "OVERWRITE"), publicCapability, wnfsPublicSemantics) + .buildPayload() + + expect(payload.prf).toEqual([ token.encode(ucan) ]) + }) + + it("throws when it's not ready to be built", () => { + expect(() => { + Builder.create() + .buildPayload() + }).toThrow() + // issuer missing + expect(() => { + Builder.create() + .toAudience(bob.did()) + .withLifetimeInSeconds(1) + .buildPayload() + }).toThrow() + // audience missing + expect(() => { + Builder.create() + .issuedBy(alice) + .withLifetimeInSeconds(1) + .buildPayload() + }).toThrow() + // expiration missing + expect(() => { + Builder.create() + .issuedBy(alice) + .toAudience(bob.did()) + .buildPayload() + }).toThrow() + }) + + it("throws when trying to delegate unproven capabilities", async () => { + const ucan = await Builder.create() + .issuedBy(alice) + .toAudience(bob.did()) + .withLifetimeInSeconds(30) + .claimCapability(emailCapability("alice@email.com")) + .build() + + const delegationChain = await first(delegationChains(EMAIL_SEMANTICS, ucan)) + + if (delegationChain == null) { + throw "no capabilities" + } + + if (delegationChain instanceof Error) { + throw delegationChain + } + + expect(() => { + Builder.create() + .issuedBy(bob) + .toAudience(mallory.did()) + .withLifetimeInSeconds(30) + .delegateCapability(emailCapability("bob@email.com"), delegationChain, EMAIL_SEMANTICS) + .buildPayload() + }).toThrow() + }) + +}) diff --git a/packages/ucans/tests/capability.test.ts b/packages/ucans/tests/capability.test.ts new file mode 100644 index 0000000..671469c --- /dev/null +++ b/packages/ucans/tests/capability.test.ts @@ -0,0 +1,22 @@ +import * as capability from "../src/capability" + + +describe("capability.isEqual", () => { + + it("is able to compare two equal capabilities", () => { + const a = { + with: { scheme: "scheme", hierPart: "hierPart" }, + can: { namespace: "namespace", segments: [ "a", "B" ] } + } + + const b = { + with: { scheme: "SCHEME", hierPart: "hierPart" }, + can: { namespace: "NAMESPACE", segments: [ "A", "b" ] } + } + + expect(capability.isEqual(a, b)).toBe(true) + expect(capability.resourcePointer.isEqual(a.with, b.with)).toBe(true) + expect(capability.ability.isEqual(a.can, b.can)).toBe(true) + }) + +}) \ No newline at end of file diff --git a/packages/ucans/tests/capability/email.ts b/packages/ucans/tests/capability/email.ts new file mode 100644 index 0000000..63ad868 --- /dev/null +++ b/packages/ucans/tests/capability/email.ts @@ -0,0 +1,82 @@ +import { Ucan } from "../../src/types" +import { DelegationSemantics, delegationChains, rootIssuer } from "../../src/attenuation" +import { Ability } from "../../src/capability/ability" +import { Capability } from "../../src/capability" +import { SUPERUSER } from "../../src/capability/super-user" +import { ResourcePointer } from "../../src/capability/resource-pointer" + + +// 🌸 + + +export interface EmailCapability { + with: ResourcePointer + can: Ability +} + + + +// 🏔 + + +export const SEND_ABILITY: Ability = { namespace: "msg", segments: [ "SEND" ] } + + +export const EMAIL_SEMANTICS: DelegationSemantics = { + + canDelegateResource(parentResource, resource) { + if (parentResource.scheme !== "email") { + return false + } + if (resource.scheme !== "email") { + return false + } + return parentResource.hierPart === resource.hierPart + }, + + canDelegateAbility(parentAbility, ability) { + if (parentAbility === SUPERUSER) { + return true + } + if (ability === SUPERUSER) { + return false + } + return parentAbility.namespace === "msg" + && parentAbility.segments.length === 1 + && parentAbility.segments[0] === "SEND" + && ability.namespace === "msg" + && ability.segments.length === 1 + && ability.segments[0] === "SEND" + } + +} + + + +// 🛠 + + +export function emailResourcePointer(emailAddress: string): ResourcePointer { + return { scheme: "email", hierPart: emailAddress } +} + + +export function emailCapability(emailAddress: string): Capability { + return { + with: emailResourcePointer(emailAddress), + can: SEND_ABILITY + } +} + + +export async function * emailCapabilities(ucan: Ucan): AsyncIterable<{ capability: EmailCapability; rootIssuer: string }> { + for await (const delegationChain of delegationChains(EMAIL_SEMANTICS, ucan)) { + if (delegationChain instanceof Error || "ownershipDID" in delegationChain) { + continue + } + yield { + rootIssuer: rootIssuer(delegationChain), + capability: delegationChain.capability + } + } +} diff --git a/packages/ucans/tests/capability/wnfs.test.ts b/packages/ucans/tests/capability/wnfs.test.ts new file mode 100644 index 0000000..58f9bdb --- /dev/null +++ b/packages/ucans/tests/capability/wnfs.test.ts @@ -0,0 +1,238 @@ +import * as token from "../../src/token" +import { Capability } from "../../src/capability" +import { wnfsCapability, wnfsPrivateCapabilities, wnfsPublicCapabilities } from "./wnfs" + +import { alice, bob, mallory } from "../fixtures" +import { all } from "../../src/util" + + + +describe("wnfs public capability", () => { + + it("works with a simple example", async () => { + const { ucan } = await makeSimpleDelegation( + [ wnfsCapability("//boris.fission.name/public/Apps/", "OVERWRITE") ], + [ wnfsCapability("//boris.fission.name/public/Apps/appinator/", "REVISE") ] + ) + + expect(await all(wnfsPublicCapabilities(ucan))).toEqual([ + { + rootIssuer: bob.did(), + capability: wnfsCapability("//boris.fission.name/public/Apps/appinator/", "REVISE") + }, + { + rootIssuer: alice.did(), + capability: wnfsCapability("//boris.fission.name/public/Apps/appinator/", "REVISE") + } + ]) + }) + + it("detects capability escalations", async () => { + const { ucan } = await makeSimpleDelegation( + [ wnfsCapability("//boris.fission.name/public/Apps/", "CREATE") ], + [ wnfsCapability("//boris.fission.name/public/Apps/appinator/", "OVERWRITE") ] + ) + + expect(await all(wnfsPublicCapabilities(ucan))).toEqual([ + { + rootIssuer: bob.did(), + capability: wnfsCapability("//boris.fission.name/public/Apps/appinator/", "OVERWRITE") + } + ]) + }) + + it("detects capability escalations, even if there's valid capabilities", async () => { + const { ucan } = await makeSimpleDelegation( + [ + wnfsCapability("//boris.fission.name/public/Apps/", "CREATE"), + wnfsCapability("//boris.fission.name/public/Apps/", "SUPER_USER") + ], + [ wnfsCapability("//boris.fission.name/public/Apps/appinator/", "OVERWRITE") + ] + ) + + expect(await all(wnfsPublicCapabilities(ucan))).toEqual([ + { + rootIssuer: bob.did(), + capability: wnfsCapability("//boris.fission.name/public/Apps/appinator/", "OVERWRITE") + }, + { + rootIssuer: alice.did(), + capability: wnfsCapability("//boris.fission.name/public/Apps/appinator/", "OVERWRITE") + } + ]) + }) + +}) + +describe("wnfs private capability", () => { + + it("works with a simple example", async () => { + const { ucan } = await makeSimpleDelegation( + [ wnfsCapability("//boris.fission.name/private/abc", "OVERWRITE") ], + [ wnfsCapability("//boris.fission.name/private/def", "REVISE") ] + ) + + expect(await all(wnfsPrivateCapabilities(ucan))).toEqual([ + { + rootIssuer: bob.did(), + capability: wnfsCapability("//boris.fission.name/private/def", "REVISE"), + requiredINumbers: new Set(["def"]) + }, + { + rootIssuer: alice.did(), + capability: wnfsCapability("//boris.fission.name/private/def", "REVISE"), + requiredINumbers: new Set(["abc", "def"]) + } + ]) + }) + + it("detects capability escalations", async () => { + const { ucan } = await makeSimpleDelegation( + [ wnfsCapability("//boris.fission.name/private/abc", "OVERWRITE") ], + [ wnfsCapability("//boris.fission.name/private/def", "SUPER_USER") ] + ) + + expect(await all(wnfsPrivateCapabilities(ucan))).toEqual([ + { + rootIssuer: bob.did(), + capability: wnfsCapability("//boris.fission.name/private/def", "SUPER_USER"), + requiredINumbers: new Set(["def"]) + }, + ]) + }) + + it("detects capability escalations, but still returns valid delegations", async () => { + const { ucan } = await makeSimpleDelegation( + [ wnfsCapability("//boris.fission.name/private/abc", "OVERWRITE") ], + [ + wnfsCapability("//boris.fission.name/private/def", "SUPER_USER"), + wnfsCapability("//boris.fission.name/private/ghi", "CREATE") + ] + ) + + expect(await all(wnfsPrivateCapabilities(ucan))).toEqual([ + { + rootIssuer: bob.did(), + capability: wnfsCapability("//boris.fission.name/private/def", "SUPER_USER"), + requiredINumbers: new Set(["def"]) + }, + { + rootIssuer: bob.did(), + capability: wnfsCapability("//boris.fission.name/private/ghi", "CREATE"), + requiredINumbers: new Set(["ghi"]) + }, + { + rootIssuer: alice.did(), + capability: wnfsCapability("//boris.fission.name/private/ghi", "CREATE"), + requiredINumbers: new Set(["ghi", "abc"]) + } + ]) + }) + + it("lists all possible inumber combinations", async () => { + const { ucan } = await makeComplexDelegation( + { + alice: [ wnfsCapability("//boris.fission.name/private/inumalice", "OVERWRITE") ], + bob: [ wnfsCapability("//boris.fission.name/private/inumbob", "OVERWRITE") ] + }, + [ wnfsCapability("//boris.fission.name/private/subinum", "OVERWRITE") ] + ) + + expect(await all(wnfsPrivateCapabilities(ucan))).toEqual([ + { + rootIssuer: mallory.did(), + capability: wnfsCapability("//boris.fission.name/private/subinum", "OVERWRITE"), + requiredINumbers: new Set(["subinum"]) + }, + { + rootIssuer: alice.did(), + capability: wnfsCapability("//boris.fission.name/private/subinum", "OVERWRITE"), + requiredINumbers: new Set(["subinum", "inumalice"]) + }, + { + rootIssuer: bob.did(), + capability: wnfsCapability("//boris.fission.name/private/subinum", "OVERWRITE"), + requiredINumbers: new Set(["subinum", "inumbob"]) + } + ]) + }) + + it("lists all possible inumber combinations except escalations", async () => { + const { ucan } = await makeComplexDelegation( + { + alice: [ wnfsCapability("//boris.fission.name/private/inumalice", "CREATE") ], + bob: [ wnfsCapability("//boris.fission.name/private/inumbob", "OVERWRITE") ] + }, + [ wnfsCapability("//boris.fission.name/private/subinum", "OVERWRITE") ] + ) + + expect(await all(wnfsPrivateCapabilities(ucan))).toEqual([ + { + rootIssuer: mallory.did(), + capability: wnfsCapability("//boris.fission.name/private/subinum", "OVERWRITE"), + requiredINumbers: new Set(["subinum"]) + }, + { + rootIssuer: bob.did(), + capability: wnfsCapability("//boris.fission.name/private/subinum", "OVERWRITE"), + requiredINumbers: new Set(["subinum", "inumbob"]) + } + ]) + }) + +}) + +/** + * A linear delegation chain: + * alice -> bob -> mallory + * + * The arguments are the capabilities delegated in the first and second arrow, respectively. + */ +async function makeSimpleDelegation(aliceCapabilities: Capability[], bobCapabilities: Capability[]) { + const leaf = await token.build({ + issuer: alice, + audience: bob.did(), + capabilities: aliceCapabilities + }) + + const ucan = await token.build({ + issuer: bob, + audience: mallory.did(), + capabilities: bobCapabilities, + proofs: [ token.encode(leaf) ] + }) + + return { leaf, ucan } +} + + +/** + * A tree-like delegation ucan: + * alice & bob => mallory -> alice + * + * The first argument are the capabilities delegated in the first two arrows, + * the second argument are the capabilities delegated in the last arrow. + */ +async function makeComplexDelegation(proofs: { alice: Capability[]; bob: Capability[] }, final: Capability[]) { + const leafAlice = await token.build({ + issuer: alice, + audience: mallory.did(), + capabilities: proofs.alice, + }) + + const leafBob = await token.build({ + issuer: bob, + audience: mallory.did(), + capabilities: proofs.bob, + }) + + const ucan = await token.build({ + issuer: mallory, + audience: alice.did(), + capabilities: final, + proofs: [ token.encode(leafAlice), token.encode(leafBob) ], + }) + + return { leafAlice, leafBob, ucan } +} diff --git a/packages/ucans/tests/capability/wnfs.ts b/packages/ucans/tests/capability/wnfs.ts new file mode 100644 index 0000000..68b189f --- /dev/null +++ b/packages/ucans/tests/capability/wnfs.ts @@ -0,0 +1,213 @@ +import { Ability, isAbility } from "../../src/capability/ability" +import { Capability } from "../../src/capability" +import { DelegationSemantics, DelegatedCapability, DelegatedOwnership, delegationChains, rootIssuer } from "../../src/attenuation" +import { SUPERUSER } from "../../src/capability/super-user" +import { Ucan } from "../../src/types" +import { ResourcePointer } from "../../src/capability/resource-pointer" + + +export const WNFS_ABILITY_LEVELS = { + "SUPER_USER": 0, + "OVERWRITE": -1, + "SOFT_DELETE": -2, + "REVISE": -3, + "CREATE": -4, +} + +export const WNFS_ABILITIES: string[] = Object.keys(WNFS_ABILITY_LEVELS) + +export type WnfsAbility = keyof typeof WNFS_ABILITY_LEVELS + +export function isWnfsCap(cap: Capability): boolean { + return cap.with.scheme === "wnfs" && isWnfsAbility(cap.can) +} + +export function isWnfsAbility(ability: unknown): ability is WnfsAbility { + if (!isAbility(ability)) return false + if (ability === SUPERUSER) return true + const abilitySegment = ability.segments[ 0 ] + const isWnfsAbilitySegment = !!abilitySegment && WNFS_ABILITIES.includes(abilitySegment) + return isWnfsAbilitySegment && ability.namespace.toLowerCase() === "wnfs" +} + +export function wnfsAbilityFromAbility(ability: Ability): WnfsAbility | null { + if (ability === SUPERUSER) return "SUPER_USER" + if (isWnfsAbility(ability)) return ability.segments[ 0 ] as WnfsAbility + return null +} + +export function wnfsCapability(path: string, ability: WnfsAbility): Capability { + return { + with: { scheme: "wnfs", hierPart: path }, + can: { namespace: "wnfs", segments: [ ability ] } + } +} + + + +////////////////////////////// +// Public WNFS Capabilities // +////////////////////////////// + + +interface WnfsPublicResourcePointer { + user: string // e.g. matheus23.fission.name + publicPath: string[] +} + +function tryParseWnfsPublicResource(pointer: ResourcePointer): WnfsPublicResourcePointer | null { + if (pointer.scheme !== "wnfs") return null + + // remove trailing slash + const path = pointer.hierPart.replace(/^\/\//, "") + const trimmed = path.endsWith("/") ? path.slice(0, -1) : path + const split = trimmed.split("/") + const user = split[ 0 ] + const publicPath = split.slice(2) // drop first two: matheus23.fission.name/public/keep/this + if (user == null || split[ 1 ] !== "public") return null + + return { + user, + publicPath, + } +} + +export const wnfsPublicSemantics: DelegationSemantics = { + + canDelegateResource(parentResource, childResource) { + const parent = tryParseWnfsPublicResource(parentResource) + const child = tryParseWnfsPublicResource(childResource) + + if (parent == null || child == null) { + return false + } + + if (parent.user !== child.user) { + return false + } + + // parentCap path must be a prefix of childCap path + if (child.publicPath.length < parent.publicPath.length) { + return false + } + + for (let i = 0; i < parent.publicPath.length; i++) { + if (child.publicPath[ i ] !== parent.publicPath[ i ]) { + return false + } + } + + return true + }, + + canDelegateAbility(parentAbility, childAbility) { + const parent = wnfsAbilityFromAbility(parentAbility) + const child = wnfsAbilityFromAbility(childAbility) + + if (parent == null || child == null) { + return false + } + + if (WNFS_ABILITY_LEVELS[ child ] > WNFS_ABILITY_LEVELS[ parent ]) { + return false + } + + return true + } + +} + +export async function * wnfsPublicCapabilities(ucan: Ucan) { + for await (const delegationChain of delegationChains(wnfsPublicSemantics, ucan)) { + if (delegationChain instanceof Error || "ownershipDID" in delegationChain) { + continue + } + yield { + capability: delegationChain.capability, + rootIssuer: rootIssuer(delegationChain), + } + } +} + + + +/////////////////////////////// +// Private WNFS Capabilities // +/////////////////////////////// + + +interface WnfsPrivateResourcePointer { + user: string + requiredINumbers: Set +} + +function tryParseWnfsPrivateResource(pointer: ResourcePointer): WnfsPrivateResourcePointer | null { + if (pointer.scheme !== "wnfs") return null + + // split up "boris.fission.name/private/fccXmZ8HYmpwxkvPSjwW9A" into "/private/" + const split = pointer.hierPart.replace(/^\/\//, "").split("/") + const user = split[ 0 ] + const inumberBase64url = split[ 2 ] + + if (user == null || split[ 1 ] !== "private" || inumberBase64url == null) return null + + return { + user, + requiredINumbers: new Set([ inumberBase64url ]), + } +} + +const wnfsPrivateSemantics: DelegationSemantics = { + + canDelegateResource(parentResource, childResource) { + const parent = tryParseWnfsPrivateResource(parentResource) + const child = tryParseWnfsPrivateResource(childResource) + + if (parent == null || child == null) { + return false + } + + // There's more tests that need to be run on the resulting delegation chain. + return true + }, + + canDelegateAbility(parentAbility, childAbility) { + const parent = wnfsAbilityFromAbility(parentAbility) + const child = wnfsAbilityFromAbility(childAbility) + + if (parent == null || child == null) { + return false + } + + if (WNFS_ABILITY_LEVELS[ child ] > WNFS_ABILITY_LEVELS[ parent ]) { + return false + } + + return true + } + +} + +export async function * wnfsPrivateCapabilities(ucan: Ucan) { + for await (const delegationChain of delegationChains(wnfsPrivateSemantics, ucan)) { + if (delegationChain instanceof Error || "ownershipDID" in delegationChain) { + continue + } + + const requiredINumbers = new Set() + let chainStep: DelegatedCapability | DelegatedOwnership | undefined = delegationChain + + while (chainStep != null && "capability" in chainStep) { + const hierSplit = chainStep.capability.with.hierPart.split("/") + const inumber = hierSplit[hierSplit.length - 1] + requiredINumbers.add(inumber) + chainStep = chainStep.chainStep + } + + yield { + capability: delegationChain.capability, + requiredINumbers, + rootIssuer: rootIssuer(delegationChain), + } + } +} diff --git a/packages/ucans/tests/compatibility.test.ts b/packages/ucans/tests/compatibility.test.ts new file mode 100644 index 0000000..fca9af9 --- /dev/null +++ b/packages/ucans/tests/compatibility.test.ts @@ -0,0 +1,39 @@ +import * as uint8arrays from "uint8arrays" +import * as token from "../src/token" + + +const oldUcan = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsInVhdiI6IjEuMC4wIn0.eyJhdWQiOiJkaWQ6a2V5OnoxM1YzU29nMllhVUtoZEdDbWd4OVVadVcxbzFTaEZKWWM2RHZHWWU3TlR0Njg5Tm9MMXRrZUd3NGMydGFQa2dBdWloUjh0cmg2azg2VHRVaTNIR2ZrNEh1NDg3czNiTWY4V1MzWjJoU3VwRktiNmhnV3VwajFIRzhheUxRdDFmeWJSdThjTGdBMkNKanFRYm16YzRFOEFKU0tKeDNndVFYa2F4c3R2Um5RRGN1eDFkZzhVR1BRS3haN2lLeUFKWkFuQlcyWXJUM2o0TVQxdTJNcWZQWG9RYU01WFZQMk04clBFN0FCSEREOXdMbWlKdjkzUUFDRFR5MllnZkVSS3JualNWaTdFb3RNOFR3NHg3M1pNUXJEQnZRRW01Zm9tTWZVaTZVSmJUTmVaaldDTUJQYllNbXRKUDZQZlRpaWZYZG0zdXprVFg5NnExUkVFOExodkU2Rzg2cUR0Wjg5MzdFYUdXdXFpNkRHVDFvc2FRMUVnR3NFN3Jac2JSdDFLNnRXeTZpYktlNTlKZWtnTWFlNW9XNER2IiwiZXhwIjozMjc0NDE5MTQyMywiZmN0IjpbXSwiaXNzIjoiZGlkOmtleTp6MTNWM1NvZzJZYVVLaGRHQ21neDlVWnVXMW8xU2hGSlljNkR2R1llN05UdDY4OU5vTDJWanZBR2JXdTFrdmZWUWFyVTVWMXBTUnNjOWFwR2h2dDdaODJmUWg1QWE1NW41Zm0zZGs2SnFuTXczZGU4WG91dWZUV2Z1eHpEVkhrSFNGV0sxOW1SWWI4d205d1VwZkxtUWl4QVdtMndFWVZqU2dENEd6YzhVUDlDSjFxMkY4ZXlpVXViMThGbld4Y2djUWhqdXB3OTNxUlMzWDlXUDViemlSYjE4TTZ0Vm8zaUJ4ZUozb2lrRTNaa3RScEtTZDlkcHU5WWNXZFhoeDZDQmY5NTZ1UXhkTDZoTkppNmVMbmZ1eFY2NEhpZU1rZFVoTTJSeThRd3lqZjQ4ZnZWMVhFVU1zeEM5YWFjNEtCcGJONDJHR3U4UmFkRDU3cjZuMWFOc2IyTjU3RkNOYnFIMXVLdHhNTmVHZHJ2QWlUUGRzVjJBRmppczJvN243ajhMNW41YmJ4TFl4VThNVHB3QVphdFpkSiIsIm5iZiI6MTY0MDE5MTQ1NywicHJmIjoiZXlKaGJHY2lPaUpTVXpJMU5pSXNJblI1Y0NJNklrcFhWQ0lzSW5WaGRpSTZJakV1TUM0d0luMC5leUpoZFdRaU9pSmthV1E2YTJWNU9ub3hNMVl6VTI5bk1sbGhWVXRvWkVkRGJXZDRPVlZhZFZjeGJ6RlRhRVpLV1dNMlJIWkhXV1UzVGxSME5qZzVUbTlNTWxacWRrRkhZbGQxTVd0MlpsWlJZWEpWTlZZeGNGTlNjMk01WVhCSGFIWjBOMW80TW1aUmFEVkJZVFUxYmpWbWJUTmthelpLY1c1TmR6TmtaVGhZYjNWMVpsUlhablY0ZWtSV1NHdElVMFpYU3pFNWJWSlpZamgzYlRsM1ZYQm1URzFSYVhoQlYyMHlkMFZaVm1wVFowUTBSM3BqT0ZWUU9VTktNWEV5UmpobGVXbFZkV0l4T0VadVYzaGpaMk5SYUdwMWNIYzVNM0ZTVXpOWU9WZFFOV0o2YVZKaU1UaE5OblJXYnpOcFFuaGxTak52YVd0Rk0xcHJkRkp3UzFOa09XUndkVGxaWTFka1dHaDROa05DWmprMU5uVlJlR1JNTm1oT1NtazJaVXh1Wm5WNFZqWTBTR2xsVFd0a1ZXaE5NbEo1T0ZGM2VXcG1ORGhtZGxZeFdFVlZUWE40UXpsaFlXTTBTMEp3WWs0ME1rZEhkVGhTWVdSRU5UZHlObTR4WVU1ellqSk9OVGRHUTA1aWNVZ3hkVXQwZUUxT1pVZGtjblpCYVZSUVpITldNa0ZHYW1sek1tODNiamRxT0V3MWJqVmlZbmhNV1hoVk9FMVVjSGRCV21GMFdtUktJaXdpWlhod0lqb3pNamMwTkRFNU1UUXlNeXdpWm1OMElqcGJYU3dpYVhOeklqb2laR2xrT210bGVUcDZNVE5XTTFOdlp6SlpZVlZMYUdSSFEyMW5lRGxWV25WWE1XOHhVMmhHU2xsak5rUjJSMWxsTjA1VWREWTRPVTV2VERKaE5VcE9hMlI0VmpabWJYVm9WbU5SWkRkSVIycHhkRXBRYVc1WlZWQTRRMUp4Y21veVkyVm5hVTFyT1RKUlNIazJRbWRXT1hveVVGQnJWMkZZU0RkUlRsQmlRekphZEUxNWFXbGFjWGRLUkVOd05sZG9VbkZVUzJodVFtaENUbWQ1WkRkTFJuUTNjRkkyTkhCa1ZIQjZUbXRNUlZKNGFHNTNUVUZqZURKcVJGZFlOelpDVG5SS04xUTFWVXQ0TTIxcWRHWTBaak0wWjJwVGRUaHJkME5UY0V0alFuQTRWV2RwU0hkdllVSkhkREUxVkZjNVUzQlNXVkoxYUZKdk1tdEljVFZ5Y0ROTmRFSnFSa2QyVUdZeVRsTlpZbUUzTmxoSGJYcFhlVEZyZUZOelEySTVUSGhqTW5welEwdG1lSEF5ZUd0VVFqWmtPVVJDUlVwVE5sUnhXbFo1WkhKU05GWmFNVkE1ZFhJeGRGcHBlbk5qYWtWd1kzVlViV1EzV0VRemRYSjZVelpqY0RSdU1sZHdSbFZNYjNsMk5tOW5ibWxaZEVOSGFUVlVlbWxEY2pKT1FWRjNWMEZYY25CMldVMWllbVEyVmt0a2RUVmpaekZZUWxoTVZFNWhUQ0lzSW01aVppSTZNVFkwTURFNU1UTTJNeXdpY0hSaklqb2lVMVZRUlZKZlZWTkZVaUlzSW5Kell5STZJaW9pZlEuQ0k5SjlOLVhUZUxQNEM5WTktUl9TcEE1aE80dHdpNUQxNFpTR2lwUzdjNS1jTlJWTVItc285Z0JZMlQzSFNaTHFmQ2xyMEtlQVJicFk2TFBwSm1NRGQ1ODdvck1TVVRnMndqN043eUNVeksxSWhOazhQMkQ3RGVlSHNxQ1lsTVotdXpjMHBSbnFJb3dPTWl6MVFkbHZXaTZ0UHNxZkZVYnl4bEx1bXRHdjV1a1hqc1FZcmYzdko3aU5DMkJibWotMGhTV25wNTNBN01TQTllLWFXVGpLUWEwSkpXVVVhWG5XS19CNjRaa3NyTWRXdW5mVFNuSE9lR2o3MFRuSXhieVcxbFhodk5pcnhIUV90ZVlKZ2xIZTRBbldEQXdUa2dnaVotdkp0WUhsYnVwQkt4S1YtNm9OMTlXS3dUT3U3QnpPX2QyUHAtWVVyY1RSSS1KZ0F2NUpnIiwicHRjIjoiU1VQRVJfVVNFUiIsInJzYyI6IioifQ.CRLB4gBBHhnsbfUhLALiCfo6mHnHSlEUczyZsWhh9TNjv9UxdgvsSWQsehGIT4XR0jQeYZo2OhasEVaF-Gtt_qqtUQIrducKngd0qzmpfjVbicsQPKVdJjlcTwm9dqhLSEtL195El0oucLzYdqMEZMf-txEmyhCd_Q8CaNExhAnwN32v1salnO6vrAw33ZJID7ZaFmBleoGUXBHQwnkv9_m_P6Fh-UGIKjaOuNmBkGXGn-4irm-eXrne2OPZCoPjhiaf0xTONu4ROrQQYykG8CppvsSXeiylOFY11Ot0sdAlHGSlyZk1_chJ3ud17K9S-CKWK9NtqiMNcUdQGFnNQQ" +const [ encodedHeader, encodedPayload, encodedSignature ] = oldUcan.split(".") +const signature = encodedSignature +const header = JSON.parse(uint8arrays.toString(uint8arrays.fromString(encodedHeader, "base64url"))) +const payload = JSON.parse(uint8arrays.toString(uint8arrays.fromString(encodedPayload, "base64url"))) + +describe("compatibility", () => { + + it("allows parsing UCANs with 'uav: 1.0.0' into 'ucv: 0.3.0'", async () => { + const ucan = await token.validate(oldUcan, { checkIsExpired: false, checkIsTooEarly: false, checkSignature: false }) + expect(ucan).toEqual({ + header: { + alg: header.alg, // "RS256", + typ: header.typ, // "JWT", + ucv: { major: 0, minor: 3, patch: 0 } // we translate uav: 1.0.0 to ucv: 0.3.0 + }, + payload: { + iss: payload.iss, // "did:key:z13V3Sog2YaUKhdGCmgx9UZuW1o1ShFJYc6DvGYe7NTt689NoL2VjvAGbWu1kvfVQarU5V1pSRsc9apGhvt7Z82fQh5Aa55n5fm3dk6JqnMw3de8XouufTWfuxzDVHkHSFWK19mRYb8wm9wUpfLmQixAWm2wEYVjSgD4Gzc8UP9CJ1q2F8eyiUub18FnWxcgcQhjupw93qRS3X9WP5bziRb18M6tVo3iBxeJ3oikE3ZktRpKSd9dpu9YcWdXhx6CBf956uQxdL6hNJi6eLnfuxV64HieMkdUhM2Ry8Qwyjf48fvV1XEUMsxC9aac4KBpbN42GGu8RadD57r6n1aNsb2N57FCNbqH1uKtxMNeGdrvAiTPdsV2AFjis2o7n7j8L5n5bbxLYxU8MTpwAZatZdJ", + aud: payload.aud, // "did:key:z13V3Sog2YaUKhdGCmgx9UZuW1o1ShFJYc6DvGYe7NTt689NoL1tkeGw4c2taPkgAuihR8trh6k86TtUi3HGfk4Hu487s3bMf8WS3Z2hSupFKb6hgWupj1HG8ayLQt1fybRu8cLgA2CJjqQbmzc4E8AJSKJx3guQXkaxstvRnQDcux1dg8UGPQKxZ7iKyAJZAnBW2YrT3j4MT1u2MqfPXoQaM5XVP2M8rPE7ABHDD9wLmiJv93QACDTy2YgfERKrnjSVi7EotM8Tw4x73ZMQrDBvQEm5fomMfUi6UJbTNeZjWCMBPbYMmtJP6PfTiifXdm3uzkTX96q1REE8LhvE6G86qDtZ8937EaGWuqi6DGT1osaQ1EgGsE7rZsbRt1K6tWy6ibKe59JekgMae5oW4Dv", + nbf: payload.nbf, // 1640191457, + exp: payload.exp, // 32744191423, + att: [ { + can: payload.rsc, // "*", + with: { scheme: "my", hierPart: payload.rsc } + } ], + prf: [ + payload.prf, // "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsInVhdiI6IjEuMC4wIn0.eyJhdWQiOiJkaWQ6a2V5OnoxM1YzU29nMllhVUtoZEdDbWd4OVVadVcxbzFTaEZKWWM2RHZHWWU3TlR0Njg5Tm9MMlZqdkFHYld1MWt2ZlZRYXJVNVYxcFNSc2M5YXBHaHZ0N1o4MmZRaDVBYTU1bjVmbTNkazZKcW5NdzNkZThYb3V1ZlRXZnV4ekRWSGtIU0ZXSzE5bVJZYjh3bTl3VXBmTG1RaXhBV20yd0VZVmpTZ0Q0R3pjOFVQOUNKMXEyRjhleWlVdWIxOEZuV3hjZ2NRaGp1cHc5M3FSUzNYOVdQNWJ6aVJiMThNNnRWbzNpQnhlSjNvaWtFM1prdFJwS1NkOWRwdTlZY1dkWGh4NkNCZjk1NnVReGRMNmhOSmk2ZUxuZnV4VjY0SGllTWtkVWhNMlJ5OFF3eWpmNDhmdlYxWEVVTXN4QzlhYWM0S0JwYk40MkdHdThSYWRENTdyNm4xYU5zYjJONTdGQ05icUgxdUt0eE1OZUdkcnZBaVRQZHNWMkFGamlzMm83bjdqOEw1bjViYnhMWXhVOE1UcHdBWmF0WmRKIiwiZXhwIjozMjc0NDE5MTQyMywiZmN0IjpbXSwiaXNzIjoiZGlkOmtleTp6MTNWM1NvZzJZYVVLaGRHQ21neDlVWnVXMW8xU2hGSlljNkR2R1llN05UdDY4OU5vTDJhNUpOa2R4VjZmbXVoVmNRZDdIR2pxdEpQaW5ZVVA4Q1JxcmoyY2VnaU1rOTJRSHk2QmdWOXoyUFBrV2FYSDdRTlBiQzJadE15aWlacXdKRENwNldoUnFUS2huQmhCTmd5ZDdLRnQ3cFI2NHBkVHB6TmtMRVJ4aG53TUFjeDJqRFdYNzZCTnRKN1Q1VUt4M21qdGY0ZjM0Z2pTdThrd0NTcEtjQnA4VWdpSHdvYUJHdDE1VFc5U3BSWVJ1aFJvMmtIcTVycDNNdEJqRkd2UGYyTlNZYmE3NlhHbXpXeTFreFNzQ2I5THhjMnpzQ0tmeHAyeGtUQjZkOURCRUpTNlRxWlZ5ZHJSNFZaMVA5dXIxdFppenNjakVwY3VUbWQ3WEQzdXJ6UzZjcDRuMldwRlVMb3l2Nm9nbmlZdENHaTVUemlDcjJOQVF3V0FXcnB2WU1iemQ2VktkdTVjZzFYQlhMVE5hTCIsIm5iZiI6MTY0MDE5MTM2MywicHRjIjoiU1VQRVJfVVNFUiIsInJzYyI6IioifQ.CI9J9N-XTeLP4C9Y9-R_SpA5hO4twi5D14ZSGipS7c5-cNRVMR-so9gBY2T3HSZLqfClr0KeARbpY6LPpJmMDd587orMSUTg2wj7N7yCUzK1IhNk8P2D7DeeHsqCYlMZ-uzc0pRnqIowOMiz1QdlvWi6tPsqfFUbyxlLumtGv5ukXjsQYrf3vJ7iNC2Bbmj-0hSWnp53A7MSA9e-aWTjKQa0JJWUUaXnWK_B64ZksrMdWunfTSnHOeGj70TnIxbyW1lXhvNirxHQ_teYJglHe4AnWDAwTkggiZ-vJtYHlbupBKxKV-6oN19WKwTOu7BzO_d2Pp-YUrcTRI-JgAv5Jg", + ], + }, + signedData: `${encodedHeader}.${encodedPayload}`, + signature // "CRLB4gBBHhnsbfUhLALiCfo6mHnHSlEUczyZsWhh9TNjv9UxdgvsSWQsehGIT4XR0jQeYZo2OhasEVaF-Gtt_qqtUQIrducKngd0qzmpfjVbicsQPKVdJjlcTwm9dqhLSEtL195El0oucLzYdqMEZMf-txEmyhCd_Q8CaNExhAnwN32v1salnO6vrAw33ZJID7ZaFmBleoGUXBHQwnkv9_m_P6Fh-UGIKjaOuNmBkGXGn-4irm-eXrne2OPZCoPjhiaf0xTONu4ROrQQYykG8CppvsSXeiylOFY11Ot0sdAlHGSlyZk1_chJ3ud17K9S-CKWK9NtqiMNcUdQGFnNQQ" + }) + }) + +}) diff --git a/packages/ucans/tests/did-key.ts b/packages/ucans/tests/did-key.ts new file mode 100644 index 0000000..9ad2824 --- /dev/null +++ b/packages/ucans/tests/did-key.ts @@ -0,0 +1,36 @@ +import EcdsaKeypair from "../src/keypair/ecdsa.js" + +// did:key test vectors from W3C +// https://github.com/w3c-ccg/did-method-key/blob/main/test-vectors/nist-curves.json +const testVectors = [ + { + id: "did:key:zDnaerx9CtbPJ1q36T5Ln5wYt3MQYeGRG5ehnPAmxcf5mDZpv", + jwk: { + kty: "EC", + crv: "P-256", + x: "igrFmi0whuihKnj9R3Om1SoMph72wUGeFaBbzG2vzns", + y: "efsX5b10x8yjyrj4ny3pGfLcY7Xby1KzgqOdqnsrJIM", + d: "gPh-VvVS8MbvKQ9LSVVmfnxnKjHn4Tqj0bmbpehRlpc" + } + }, + { + id: "did:key:zDnaerDaTF5BXEavCrfRZEk316dpbLsfPDZ3WJ5hRTPFU2169", + jwk: { + kty: "EC", + crv: "P-256", + x: "fyNYMN0976ci7xqiSdag3buk-ZCwgXU4kz9XNkBlNUI", + y: "hW2ojTNfH7Jbi8--CJUo3OCbH3y5n91g-IMA9MLMbTU", + d: "YjRs6vNvw4sYrzVVY8ipkEpDAD9PFqw1sUnvPRMA-WI" + } + } +] + +describe("did:key ECDSA P-256", () => { + it("derives the correct DID from the JWK", async () => { + for(const vector of testVectors) { + const keypair = await EcdsaKeypair.importFromJwk(vector.jwk) + const did = keypair.did() + expect(did).toEqual(vector.id) + } + }) +}) \ No newline at end of file diff --git a/packages/ucans/tests/did.test.ts b/packages/ucans/tests/did.test.ts new file mode 100644 index 0000000..02abca7 --- /dev/null +++ b/packages/ucans/tests/did.test.ts @@ -0,0 +1,78 @@ +import * as did from "../src/did" + +describe("publicKeyToDid", () => { + + it("handles RSA Keys", async () => { + const pubkey = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAnzyis1ZjfNB0bBgKFMSvvkTtwlvBsaJq7S5wA+kzeVOVpVWwkWdVha4s38XM/pa/yr47av7+z3VTmvDRyAHcaT92whREFpLv9cj5lTeJSibyr/Mrm/YtjCZVWgaOYIhwrXwKLqPr/11inWsAkfIytvHWTxZYEcXLgAXFuUuaS3uF9gEiNQwzGTU1v0FqkqTBr4B8nW3HCN47XUu0t8Y0e+lf4s4OxQawWD79J9/5d3Ry0vbV3Am1FtGJiJvOwRsIfVChDpYStTcHTCMqtvWbV6L11BWkpzGXSW4Hv43qa+GSYOD2QU68Mb59oSk2OB+BtOLpJofmbGEGgvmwyCI9MwIDAQAB" + // old: const expectedDid = "did:key:z13V3Sog2YaUKhdGCmgx9UZuW1o1ShFJYc6DvGYe7NTt689NoL2RtpVs65Zw899YrTN9WuxdEEDm54YxWuQHQvcKfkZwa8HTgokHxGDPEmNLhvh69zUMEP4zjuARQ3T8bMUumkSLGpxNe1bfQX624ef45GhWb3S9HM3gvAJ7Qftm8iqnDQVcxwKHjmkV4hveKMTix4bTRhieVHi1oqU4QCVy4QPWpAAympuCP9dAoJFxSP6TNBLY9vPKLazsg7XcFov6UuLWsEaxJ5SomCpDx181mEgW2qTug5oQbrJwExbD9CMgXHLVDE2QgLoQMmgsrPevX57dH715NXC2uY6vo2mYCzRY4KuDRUsrkuYCkewL8q2oK1BEDVvi3Sg8pbC9QYQ5mMiHf8uxiHxTAmPedv8" + const expectedDid = "did:key:z4MXj1wBzi9jUstyNvmiK5WLRRL4rr9UvzPxhry1CudCLKWLyMbP1WoTwDfttBTpxDKf5hAJEjqNbeYx2EEvrJmSWHAu7TJRPTrE3QodbMfRvRNRDyYvaN1FSQus2ziS1rWXwAi5Gpc16bY3JwjyLCPJLfdRWHZhRXiay5FWEkfoSKy6aftnzAvqNkKBg2AxgzGMinR6d1WiH4w5mEXFtUeZkeo4uwtRTd8rD9BoVaHVkGwJkksDybE23CsBNXiNfbweFVRcwfTMhcQsTsYhUWDcSC6QE3zt9h4Rsrj7XRYdwYSK5bc1qFRsg5HULKBp2uZ1gcayiW2FqHFcMRjBieC4LnSMSD1AZB1WUncVRbPpVkn1UGhCU" + const result = did.publicKeyToDid(pubkey, "rsa") + expect(result).toEqual(expectedDid) + }) + + it("handles Ed25519 Keys", async () => { + const pubkey = "Hv+AVRD2WUjUFOsSNbsmrp9fokuwrUnjBcr92f0kxw4=" + const expectedDid = "did:key:z6MkgYGF3thn8k1Fv4p4dWXKtsXCnLH7q9yw4QgNPULDmDKB" + const result = did.publicKeyToDid(pubkey, "ed25519") + expect(result).toEqual(expectedDid) + }) + + it("handles BLS12-381 Keys", async () => { + const pubkey = "Hv+AVRD2WUjUFOsSNbsmrp9fokuwrUnjBcr92f0kxw4=" + const expectedDid = "did:key:z6HpYD1br5P4QVh5rjRGAkBfKMWes44uhKmKdJ6dN2Nm9gHK" + const result = did.publicKeyToDid(pubkey, "bls12-381") + expect(result).toEqual(expectedDid) + }) + + it("handles NIST P-256 Keys", async () => { + const pubkey = "BEgg6smRlRQXKqzJHu0w5/nHcy7zYNxFXwTKUyyCOoDMztUvQn5VZg668v4MecHGUZTeHkaZhSraK7RtJuXfWp4=" + const expectedDid = "did:key:zDnaeVHXWgKUpJFc8AUfRDuRWfsAQejmZu5HrERh41iGumZVu" + const result = did.publicKeyToDid(pubkey, "p256") + expect(result).toEqual(expectedDid) + }) + +}) + +describe("didToPublicKey", () => { + + it("handles old RSA Keys", async () => { + const toDecode = "did:key:z13V3Sog2YaUKhdGCmgx9UZuW1o1ShFJYc6DvGYe7NTt689NoL2RtpVs65Zw899YrTN9WuxdEEDm54YxWuQHQvcKfkZwa8HTgokHxGDPEmNLhvh69zUMEP4zjuARQ3T8bMUumkSLGpxNe1bfQX624ef45GhWb3S9HM3gvAJ7Qftm8iqnDQVcxwKHjmkV4hveKMTix4bTRhieVHi1oqU4QCVy4QPWpAAympuCP9dAoJFxSP6TNBLY9vPKLazsg7XcFov6UuLWsEaxJ5SomCpDx181mEgW2qTug5oQbrJwExbD9CMgXHLVDE2QgLoQMmgsrPevX57dH715NXC2uY6vo2mYCzRY4KuDRUsrkuYCkewL8q2oK1BEDVvi3Sg8pbC9QYQ5mMiHf8uxiHxTAmPedv8" + const expectedKey = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAnzyis1ZjfNB0bBgKFMSvvkTtwlvBsaJq7S5wA+kzeVOVpVWwkWdVha4s38XM/pa/yr47av7+z3VTmvDRyAHcaT92whREFpLv9cj5lTeJSibyr/Mrm/YtjCZVWgaOYIhwrXwKLqPr/11inWsAkfIytvHWTxZYEcXLgAXFuUuaS3uF9gEiNQwzGTU1v0FqkqTBr4B8nW3HCN47XUu0t8Y0e+lf4s4OxQawWD79J9/5d3Ry0vbV3Am1FtGJiJvOwRsIfVChDpYStTcHTCMqtvWbV6L11BWkpzGXSW4Hv43qa+GSYOD2QU68Mb59oSk2OB+BtOLpJofmbGEGgvmwyCI9MwIDAQAB" + const { publicKey, type } = did.didToPublicKey(toDecode) + expect(publicKey).toEqual(expectedKey) + expect(type).toEqual("rsa") + }) + + it("handles standardized RSA Keys", async () => { + const toDecode = "did:key:z4MXj1wBzi9jUstyNvmiK5WLRRL4rr9UvzPxhry1CudCLKWLyMbP1WoTwDfttBTpxDKf5hAJEjqNbeYx2EEvrJmSWHAu7TJRPTrE3QodbMfRvRNRDyYvaN1FSQus2ziS1rWXwAi5Gpc16bY3JwjyLCPJLfdRWHZhRXiay5FWEkfoSKy6aftnzAvqNkKBg2AxgzGMinR6d1WiH4w5mEXFtUeZkeo4uwtRTd8rD9BoVaHVkGwJkksDybE23CsBNXiNfbweFVRcwfTMhcQsTsYhUWDcSC6QE3zt9h4Rsrj7XRYdwYSK5bc1qFRsg5HULKBp2uZ1gcayiW2FqHFcMRjBieC4LnSMSD1AZB1WUncVRbPpVkn1UGhCU" + const expectedKey = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAnzyis1ZjfNB0bBgKFMSvvkTtwlvBsaJq7S5wA+kzeVOVpVWwkWdVha4s38XM/pa/yr47av7+z3VTmvDRyAHcaT92whREFpLv9cj5lTeJSibyr/Mrm/YtjCZVWgaOYIhwrXwKLqPr/11inWsAkfIytvHWTxZYEcXLgAXFuUuaS3uF9gEiNQwzGTU1v0FqkqTBr4B8nW3HCN47XUu0t8Y0e+lf4s4OxQawWD79J9/5d3Ry0vbV3Am1FtGJiJvOwRsIfVChDpYStTcHTCMqtvWbV6L11BWkpzGXSW4Hv43qa+GSYOD2QU68Mb59oSk2OB+BtOLpJofmbGEGgvmwyCI9MwIDAQAB" + const { publicKey, type } = did.didToPublicKey(toDecode) + expect(publicKey).toEqual(expectedKey) + expect(type).toEqual("rsa") + }) + + it("handles Ed25519 Keys", async () => { + const toDecode = "did:key:z6MkgYGF3thn8k1Fv4p4dWXKtsXCnLH7q9yw4QgNPULDmDKB" + const expectedKey = "Hv+AVRD2WUjUFOsSNbsmrp9fokuwrUnjBcr92f0kxw4=" + const { publicKey, type } = did.didToPublicKey(toDecode) + expect(publicKey).toEqual(expectedKey) + expect(type).toEqual("ed25519") + }) + + it("handles BLS12-381 Keys", async () => { + const toDecode = "did:key:z6HpYD1br5P4QVh5rjRGAkBfKMWes44uhKmKdJ6dN2Nm9gHK" + const expectedKey = "Hv+AVRD2WUjUFOsSNbsmrp9fokuwrUnjBcr92f0kxw4=" + const { publicKey, type } = did.didToPublicKey(toDecode) + expect(publicKey).toEqual(expectedKey) + expect(type).toEqual("bls12-381") + }) + + it("handles NIST P-256 Keys", async () => { + const toDecode = "did:key:zDnaeVHXWgKUpJFc8AUfRDuRWfsAQejmZu5HrERh41iGumZVu" + const expectedKey = "BEgg6smRlRQXKqzJHu0w5/nHcy7zYNxFXwTKUyyCOoDMztUvQn5VZg668v4MecHGUZTeHkaZhSraK7RtJuXfWp4=" + const { publicKey, type } = did.didToPublicKey(toDecode) + expect(publicKey).toEqual(expectedKey) + expect(type).toEqual("p256") + }) + +}) diff --git a/packages/ucans/tests/ecdsa.test.ts b/packages/ucans/tests/ecdsa.test.ts new file mode 100644 index 0000000..3d74917 --- /dev/null +++ b/packages/ucans/tests/ecdsa.test.ts @@ -0,0 +1,34 @@ +import * as did from "../src/did" +import ECDSAKeyPair from "../src/keypair/ecdsa" + + +describe("ecdsa", () => { + + let p256Keypair: ECDSAKeyPair + + let p256Signature: Uint8Array + + const data = new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8, 9]) + + it("creates an ecdsa keypairs with different curves", async () => { + p256Keypair = await ECDSAKeyPair.create() + }) + + it("returns a publicKeyStr and did: curve P-256", () => { + const keyDid = p256Keypair.did() + const publicKey = p256Keypair.publicKeyStr() + const transformed = did.didToPublicKey(keyDid) + expect(transformed.publicKey).toEqual(publicKey) + expect(transformed.type).toEqual("p256") + }) + + it("signs data: curve P-256", async () => { + p256Signature = await p256Keypair.sign(data) + }) + + it("can verify signature: P-256", async () => { + const isValid = await did.verifySignature(data, p256Signature, p256Keypair.did()) + expect(isValid).toBeTruthy() + }) + +}) diff --git a/packages/ucans/tests/ed25519.test.ts b/packages/ucans/tests/ed25519.test.ts new file mode 100644 index 0000000..65769d9 --- /dev/null +++ b/packages/ucans/tests/ed25519.test.ts @@ -0,0 +1,31 @@ +import * as did from "../src/did" +import EdwardsKey from "../src/keypair/ed25519" + +describe("ed25519", () => { + + let keypair: EdwardsKey + let signature: Uint8Array + const data = new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8, 9]) + + it("creates an edwards curve keypair", async () => { + keypair = await EdwardsKey.create() + }) + + it("returns a publicKeyStr and did", () => { + const publicKey = keypair.publicKeyStr() + const keyDid = keypair.did() + const transformed = did.didToPublicKey(keyDid) + expect(transformed.publicKey).toEqual(publicKey) + expect(transformed.type).toEqual("ed25519") + }) + + it("signs data", async () => { + signature = await keypair.sign(data) + }) + + it("can verify signature", async () => { + const isValid = await did.verifySignature(data, signature, keypair.did()) + expect(isValid).toEqual(true) + }) + +}) diff --git a/packages/ucans/tests/fixtures.ts b/packages/ucans/tests/fixtures.ts new file mode 100644 index 0000000..fbdeeb1 --- /dev/null +++ b/packages/ucans/tests/fixtures.ts @@ -0,0 +1,19 @@ +import { EdKeypair } from "../src/keypair/ed25519" + + +/** did:key:z6Mkk89bC3JrVqKie71YEcc5M1SMVxuCgNx6zLZ8SYJsxALi */ +export const alice = EdKeypair.fromSecretKey("U+bzp2GaFQHso587iSFWPSeCzbSfn/CbNHEz7ilKRZ1UQMmMS7qq4UhTzKn3X9Nj/4xgrwa+UqhMOeo4Ki8JUw==") + +/** did:key:z6MkffDZCkCTWreg8868fG1FGFogcJj5X6PY93pPcWDn9bob */ +export const bob = EdKeypair.fromSecretKey("G4+QCX1b3a45IzQsQd4gFMMe0UB1UOx9bCsh8uOiKLER69eAvVXvc8P2yc4Iig42Bv7JD2zJxhyFALyTKBHipg==") + +/** did:key:z6MktafZTREjJkvV5mfJxcLpNBoVPwDLhTuMg9ng7dY4zMAL */ +export const mallory = EdKeypair.fromSecretKey("LR9AL2MYkMARuvmV3MJV8sKvbSOdBtpggFCW8K62oZDR6UViSXdSV/dDcD8S9xVjS61vh62JITx7qmLgfQUSZQ==") + + +export function didToName(did: string) { + if (did === alice.did()) return "alice" + if (did === bob.did()) return "bob" + if (did === mallory.did()) return "mallory" + return did +} diff --git a/packages/ucans/tests/rsa.test.ts b/packages/ucans/tests/rsa.test.ts new file mode 100644 index 0000000..70950ce --- /dev/null +++ b/packages/ucans/tests/rsa.test.ts @@ -0,0 +1,96 @@ +import * as fc from "fast-check" +import * as uint8arrays from "uint8arrays" +import * as did from "../src/did" +import * as rsaCrypto from "../src/crypto/rsa" +import RSAKeypair from "../src/keypair/rsa" + + +describe("rsa", () => { + + let keypair: RSAKeypair + let signature: Uint8Array + const data = new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8, 9]) + + it("creates an rsa keypair", async () => { + keypair = await RSAKeypair.create() + }) + + it("returns a publicKeyStr and did", () => { + const publicKey = keypair.publicKeyStr() + const keyDid = keypair.did() + const transformed = did.didToPublicKey(keyDid) + expect(transformed.publicKey).toEqual(publicKey) + expect(transformed.type).toEqual("rsa") + }) + + it("signs data", async () => { + signature = await keypair.sign(data) + }) + + it("can verify signature", async () => { + const isValid = await did.verifySignature(data, signature, keypair.did()) + expect(isValid).toEqual(true) + }) + +}) + +describe("ASN", () => { + + describe("asn1DERLengthEncode/Decode", () => { + + it("works with simple examples", () => { + // 82 - bigger than 127 & 2 length octets + // 01 - 1 * 256^1 + + // b3 - 179 * 256^0 + // = 435 + // Example from https://en.wikipedia.org/wiki/X.690#Length_octets + expect(uint8arrays.toString(rsaCrypto.asn1DERLengthEncode(435), "hex")).toEqual("8201b3") + }) + + it("round-trips", () => { + fc.assert(fc.property(fc.nat(), n => { + expect(rsaCrypto.asn1DERLengthDecode(rsaCrypto.asn1DERLengthEncode(n))).toEqual(n) + })) + }) + + it("encodes in a simple way until 127", () => { + for (let i = 0; i < 128; i++) { + expect(`Encoded ${i}: ${uint8arrays.toString(rsaCrypto.asn1DERLengthEncode(i), "hex")}`) + .toEqual(`Encoded ${i}: ${uint8arrays.toString(new Uint8Array([i]), "hex")}`) + } + }) + }) + + describe("SPKI/PKCS1 conversion", () => { + + it("round trips with webcrypto-generated spki keys", async () => { + await fc.assert( + fc.asyncProperty( + fc.constantFrom(1024, 2048, 3072, 4096), + async size => { + const key = await rsaCrypto.generateKeypair(size) + if (key.publicKey == null) { + expect(key.publicKey).toBeDefined() + throw "public key is undefined" + } + const spki = await rsaCrypto.exportKey(key.publicKey) + const converted = + rsaCrypto.convertRSAPublicKeyToSubjectPublicKeyInfo( + rsaCrypto.convertSubjectPublicKeyInfoToRSAPublicKey( + spki + ) + ) + + // I find hex dumps the most readable when it comes to ASN1 + expect(uint8arrays.toString(converted, "hex")).toEqual(uint8arrays.toString(spki, "hex")) + } + ), + { + numRuns: 5, // unfortunately, generating rsa keys is quite slow. Let's try to reliably keep below the 5s timeout + examples: [[1024], [2048], [3072], [4096]], // ensure we're testing each variant at least once + } + ) + }) + + }) +}) diff --git a/packages/ucans/tests/semver.test.ts b/packages/ucans/tests/semver.test.ts new file mode 100644 index 0000000..70667a8 --- /dev/null +++ b/packages/ucans/tests/semver.test.ts @@ -0,0 +1,89 @@ +import * as fc from "fast-check" + +import { SemVer, parse, format, compare, GT, EQ, LT } from "../src/semver" + +const arbitrarySemVer = fc.record({ + major: fc.nat(100), + minor: fc.nat(100), + patch: fc.nat(100) +}) + +const toNum = (semVer: SemVer) => + 100 * 100 * semVer.major + + 100 * semVer.minor + + semVer.patch + +describe("SemVer", () => { + + it("has the property parse(format(x)) == x", () => { + fc.property(arbitrarySemVer, semVer => { + expect(parse(format(semVer))).toEqual(semVer) + }) + }) + + it("has the property compare(x, y) == flip(compare(y, x))", () => { + fc.property(arbitrarySemVer, arbitrarySemVer, (l, r) => { + expect(compare(l, r)).toEqual(flip(compare(r, l))) + }) + }) + + it("has the property compare(x, x) == EQ", () => { + fc.property(arbitrarySemVer, semVer => { + expect(compare(semVer, semVer)).toEqual(EQ) + }) + }) + + it("has the property compare(x, y) == compareNum(toNum(x), toNum(y))", () => { + fc.property(arbitrarySemVer, arbitrarySemVer, (x, y) => { + expect(compare(x, y)).toEqual(compareNum(toNum(x), toNum(y))) + }) + }) + + describe("parse", () => { + + it("parses 0.1.2", () => { + expect(parse("0.1.2")).toEqual({ + major: 0, + minor: 1, + patch: 2, + }) + }) + + it("parses 11.22.33", () => { + expect(parse("11.22.33")).toEqual({ + major: 11, + minor: 22, + patch: 33, + }) + }) + + it("doesn't parse negative integers", () => { + expect(parse("0.-1.1")).toEqual(null) + + }) + + it("doesn't parse octal integers", () => { + expect(parse("0.010.0")).toEqual(null) + }) + + it("doesn't parse scientific integers", () => { + expect(parse("1e5.0.0")).toEqual(null) + }) + + }) + +}) + +function flip(sign: GT | EQ | LT): GT | EQ | LT { + switch (sign) { + case GT: return LT + case LT: return GT + default: return EQ + } +} + +function compareNum(l: number, r: number): LT | EQ | GT { + if (l < r) return LT + if (l > r) return GT + return EQ +} diff --git a/packages/ucans/tests/store.test.ts b/packages/ucans/tests/store.test.ts new file mode 100644 index 0000000..da7864f --- /dev/null +++ b/packages/ucans/tests/store.test.ts @@ -0,0 +1,142 @@ +import * as token from "../src/token" +import { Store } from "../src/store" +import { Builder } from "../src/builder" +import { alice, bob, mallory } from "./fixtures" +import { wnfsCapability, wnfsPublicSemantics } from "./capability/wnfs" +import { Ucan } from "../src/types" +import { equalCanDelegate } from "../src/attenuation" +import { all } from "../src/util" + + +describe("Store.add", () => { + + it("makes added items retrievable with findByAudience", async () => { + const ucan = await Builder.create() + .issuedBy(alice) + .toAudience(bob.did()) + .withLifetimeInSeconds(30) + .build() + + const encoded = token.encode(ucan) + + const store = await Store.fromTokens(equalCanDelegate, []) + await store.add(ucan) + expect(encodeOrNull(store.findByAudience(ucan.payload.aud, find => token.encode(find) === encoded))).toEqual(encoded) + }) + + it("makes added items retrievable with findByAudience among multiple others", async () => { + const ucan = await Builder.create() + .issuedBy(alice) + .toAudience(bob.did()) + .withLifetimeInSeconds(30) + .build() + + const ucan2 = await Builder.create() + .issuedBy(alice) + .toAudience(mallory.did()) + .withLifetimeInSeconds(30) + .build() + + const encoded = token.encode(ucan) + const store = await Store.fromTokens(equalCanDelegate, []) + await store.add(ucan2) + await store.add(ucan) + expect(encodeOrNull(store.findByAudience(ucan.payload.aud, find => token.encode(find) === encoded))).toEqual(encoded) + }) + + it("doesn't add items twice", async () => { + const ucan = await Builder.create() + .issuedBy(alice) + .toAudience(bob.did()) + .withLifetimeInSeconds(30) + .build() + + const store = await Store.fromTokens(equalCanDelegate, []) + await store.add(ucan) + await store.add(ucan) + expect(store.getByAudience(ucan.payload.aud)).toEqual([ ucan ]) + }) + +}) + +describe("Store.findByAudience", () => { + + it("only returns ucans with given audience", async () => { + const ucanBob = await Builder.create() + .issuedBy(alice) + .toAudience(bob.did()) + .withLifetimeInSeconds(30) + .build() + + const ucanAlice = await Builder.create() + .issuedBy(bob) + .toAudience(alice.did()) + .withLifetimeInSeconds(30) + .build() + + const store = await Store.fromTokens(equalCanDelegate, [ ucanBob, ucanAlice ].map(ucan => token.encode(ucan))) + expect(store.findByAudience(mallory.did(), () => true)).toEqual(null) + expect(encodeOrNull(store.findByAudience(bob.did(), () => true))).toEqual(token.encode(ucanBob)) + expect(encodeOrNull(store.findByAudience(alice.did(), () => true))).toEqual(token.encode(ucanAlice)) + }) + +}) + +describe("Store.findWithCapability", () => { + + it("finds ucans with more capabilities than the given", async () => { + const ucan = await Builder.create() + .issuedBy(alice) + .toAudience(bob.did()) + .withLifetimeInSeconds(30) + .claimCapability(wnfsCapability("alice.fission.name/public/", "SUPER_USER")) + .build() + + const store = await Store.fromTokens(wnfsPublicSemantics, [ token.encode(ucan) ]) + + const results = all(store.findWithCapability( + bob.did(), + wnfsCapability("alice.fission.name/public/Apps", "OVERWRITE"), + alice.did() + )) + + if (!("capability" in results[0])) { + throw "no capability" + } + + expect(encodeOrNull(results[0]?.ucan)).toEqual(token.encode(ucan)) + }) + + it("reports an error if the capability can't be found with given audience", async () => { + const ucanBob = await Builder.create() + .issuedBy(alice) + .toAudience(bob.did()) + .withLifetimeInSeconds(30) + .claimCapability(wnfsCapability("alice.fission.name/public/", "SUPER_USER")) + .build() + + const ucanAlice = await Builder.create() + .issuedBy(alice) + .toAudience(bob.did()) + .withLifetimeInSeconds(30) + .build() + + const store = await Store.fromTokens(wnfsPublicSemantics, [ token.encode(ucanAlice), token.encode(ucanBob) ]) + + const results = all(store.findWithCapability( + alice.did(), + wnfsCapability("alice.fission.name/public/Apps", "OVERWRITE"), + alice.did() + )) + + expect(results).toEqual([]) + }) + +}) + +function encodeOrNull(ucan: Ucan | null): string { + if (ucan == null) { + return "null" + } + return token.encode(ucan) +} diff --git a/packages/ucans/tests/token.test.ts b/packages/ucans/tests/token.test.ts new file mode 100644 index 0000000..b625a78 --- /dev/null +++ b/packages/ucans/tests/token.test.ts @@ -0,0 +1,175 @@ +import * as uint8arrays from "uint8arrays" + +import * as capability from "../src/capability" +import * as token from "../src/token" +import { verifySignatureUtf8 } from "../src/did" +import { alice, bob } from "./fixtures" + + +// COMPOSING + + +describe("token.build", () => { + + it("can build payloads without nbf", () => { + const payload = token.buildPayload({ + issuer: alice.did(), + audience: bob.did(), + }) + expect(payload.nbf).not.toBeDefined() + }) + + it("builds payloads that expire in the future", () => { + const payload = token.buildPayload({ + issuer: alice.did(), + audience: bob.did(), + + lifetimeInSeconds: 30, + }) + expect(payload.exp).toBeGreaterThan(Date.now() / 1000) + }) + + it("throws when enclosing tokens with an invalid key type", async () => { + await expect(() => { + const payload = token.buildPayload({ + issuer: alice.did(), + audience: bob.did(), + }) + + return token.sign( + payload, + "rsa", + data => alice.sign(data) + ) + }).rejects.toBeDefined() + }) + +}) + + + +// ENCODING + + +describe("token.encodePayload", () => { + + it("encodes capabilities", () => { + const encodedCaps = { + with: "wnfs://boris.fission.name/public/photos/", + can: "crud/DELETE" + } + + const payload = token.buildPayload({ + issuer: alice.did(), + audience: bob.did(), + capabilities: [ capability.parse(encodedCaps) ] + }) + + const encoded = token.encodePayload(payload) + const decodedString = uint8arrays.toString( + uint8arrays.fromString(encoded, "base64url"), + "utf8" + ) + + const decoded = JSON.parse(decodedString) + + expect( + JSON.stringify(decoded.att) + ).toEqual( + JSON.stringify([ encodedCaps ]) + ) + }) + +}) + + + +// VALIDATION + + +describe("token.validate", () => { + async function makeUcan() { + return await token.build({ + audience: bob.did(), + issuer: alice, + capabilities: [ + { + "with": { scheme: "wnfs", hierPart: "//boris.fission.name/public/photos/" }, + "can": { namespace: "crud", segments: [ "DELETE" ] } + }, + { + "with": { scheme: "wnfs", hierPart: "//boris.fission.name/private/84MZ7aqwKn7sNiMGsSbaxsEa6EPnQLoKYbXByxNBrCEr" }, + "can": { namespace: "wnfs", segments: [ "APPEND" ] } + }, + { + "with": { scheme: "mailto", hierPart: "boris@fission.codes" }, + "can": { namespace: "msg", segments: [ "SEND" ] } + } + ] + }) + } + + it("round-trips with token.build", async () => { + const ucan = await makeUcan() + const parsedUcan = await token.validate(token.encode(ucan)) + expect(parsedUcan).toBeDefined() + }) + + it("throws with a bad audience", async () => { + const ucan = await makeUcan() + const badPayload = { + ...ucan.payload, + aud: "fakeaudience" + } + const badUcan = `${token.encodeHeader(ucan.header)}.${token.encodePayload(badPayload)}.${ucan.signature}` + await expect(() => token.validate(badUcan)).rejects.toBeDefined() + }) + + it("throws with a bad issuer", async () => { + const ucan = await makeUcan() + const badHeader = { + ...ucan.header, + alg: "RS256" + } + const badUcan = `${token.encodeHeader(badHeader)}.${token.encodePayload(ucan.payload)}.${ucan.signature}` + await expect(() => token.validate(badUcan)).rejects.toBeDefined() + }) + + it("identifies a ucan that is not active yet", async () => { + const ucan = await makeUcan() + const badUcan = { + ...ucan, + payload: { + ...ucan.payload, + nbf: 2637252774, + exp: 2637352774 + } + } + expect(token.isTooEarly(badUcan)).toBe(true) + }) + + it("identifies a ucan that has become active", async () => { + const ucan = await makeUcan() + const activeUcan = { + ...ucan, + payload: { + ...ucan.payload, + nbf: Math.floor(Date.now() / 1000), + lifetimeInSeonds: 30 + } + } + expect(token.isTooEarly(activeUcan)).toBe(false) + }) +}) + +describe("verifySignatureUtf8", () => { + + it("works with an example", async () => { + const [ header, payload, signature ] = token.encode(await token.build({ + issuer: alice, + audience: bob.did(), + })).split(".") + expect(await verifySignatureUtf8(`${header}.${payload}`, signature, alice.did())).toEqual(true) + }) + +}) diff --git a/packages/ucans/tests/verify.test.ts b/packages/ucans/tests/verify.test.ts new file mode 100644 index 0000000..ba91658 --- /dev/null +++ b/packages/ucans/tests/verify.test.ts @@ -0,0 +1,350 @@ +import * as token from "../src/token" +import * as capability from "../src/capability" +import { SUPERUSER } from "../src/capability/super-user" +import { verify } from "../src/verify" +import { emailCapability } from "./capability/email" +import { alice, bob, mallory } from "./fixtures" +import { REDELEGATE } from "../src/capability/ability" + + +describe("verify", () => { + + async function aliceEmailDelegationExample(expiration?: number) { + // alice -> bob, bob -> mallory + // alice delegates access to sending email as her to bob + // and bob delegates it further to mallory + const leafUcan = await token.build({ + issuer: alice, + audience: bob.did(), + expiration, + capabilities: [ emailCapability("alice@email.com") ] + }) + + const ucan = await token.build({ + issuer: bob, + audience: mallory.did(), + expiration, + capabilities: [ emailCapability("alice@email.com") ], + proofs: [ token.encode(leafUcan) ] + }) + + return token.encode(ucan) + } + + const alicesEmail = { + capability: emailCapability("alice@email.com"), + rootIssuer: alice.did(), + } + + it("verifies a delegation chain", async () => { + const ucan = await aliceEmailDelegationExample() + + const result = await verify(ucan, { + audience: mallory.did(), + requiredCapabilities: [ alicesEmail ] + }) + + if (result.ok === false) { + console.log(result.error) + } + + expect(result.ok).toEqual(true) + + if (!result.ok) return + + expect(result.value[ 0 ]?.rootIssuer).toEqual(alice.did()) + expect(result.value[ 0 ]?.capability).toEqual(emailCapability("alice@email.com")) + }) + + it("rejects an invalid escalation", async () => { + const ucan = await aliceEmailDelegationExample() + + const result = await verify(ucan, { + audience: mallory.did(), + requiredCapabilities: [ { + capability: { + ...emailCapability("alice@email.com"), + can: SUPERUSER, + }, + rootIssuer: alice.did() + } ] + }) + + expect(result.ok).toEqual(false) + }) + + it("rejects for an invalid audience", async () => { + const ucan = await aliceEmailDelegationExample() + + const result = await verify(ucan, { + audience: bob.did(), + requiredCapabilities: [ alicesEmail ] + }) + + expect(result.ok).toEqual(false) + }) + + it("rejects for an invalid rootIssuer", async () => { + const ucan = await aliceEmailDelegationExample() + + const result = await verify(ucan, { + audience: mallory.did(), + requiredCapabilities: [ { + capability: emailCapability("alice@email.com"), + // an invalid rootIssuer + rootIssuer: "did:someone-else", + } ] + }) + + expect(result.ok).toEqual(false) + }) + + it("rejects for an expired capability", async () => { + // unix timestamp in seconds. Will be after + const nowInSeconds = Math.floor(Date.now() / 1000) + // expiry is in the past + const ucan = await aliceEmailDelegationExample(nowInSeconds - 60 * 60 * 24) + + const result = await verify(ucan, { + audience: mallory.did(), + requiredCapabilities: [ alicesEmail ] + }) + + expect(result.ok).toEqual(false) + }) + + it("supports redelegation with a `prf:*` capability", async () => { + // alice -> bob + // alice delegates access to sending email as her to bob + // and bob delegates it further to mallory + const leafUcan = await token.build({ + issuer: alice, + audience: bob.did(), + capabilities: [ emailCapability("alice@email.com") ] + }) + + const ucan = await token.build({ + issuer: bob, + audience: mallory.did(), + capabilities: [ capability.prf(capability.superUser.SUPERUSER, REDELEGATE) ], + proofs: [ token.encode(leafUcan) ] + }) + + const result = await verify(token.encode(ucan), { + audience: mallory.did(), + requiredCapabilities: [ alicesEmail ] + }) + + expect(result.ok).toEqual(true) + + if (!result.ok) return + + expect(result.value[ 0 ]?.rootIssuer).toEqual(alice.did()) + expect(result.value[ 0 ]?.capability).toEqual(emailCapability("alice@email.com")) + }) + + it("supports redelegation with a `prf:1` capability", async () => { + // alice -> bob + // alice delegates access to sending email as her to bob + // and bob delegates it further to mallory + const leafUcanA = await token.build({ + issuer: alice, + audience: bob.did(), + capabilities: [ emailCapability("ignore-me@email.com") ] + }) + + const leafUcanB = await token.build({ + issuer: alice, + audience: bob.did(), + capabilities: [ emailCapability("alice@email.com") ] + }) + + const ucan = await token.build({ + issuer: bob, + audience: mallory.did(), + capabilities: [ capability.prf(1, REDELEGATE) ], + proofs: [ token.encode(leafUcanA), token.encode(leafUcanB) ] + }) + + const result = await verify(token.encode(ucan), { + audience: mallory.did(), + requiredCapabilities: [ alicesEmail ] + }) + + expect(result.ok).toEqual(true) + + if (!result.ok) return + + expect(result.value[ 0 ]?.rootIssuer).toEqual(alice.did()) + expect(result.value[ 0 ]?.capability).toEqual(emailCapability("alice@email.com")) + }) + + it("ignores other proofs not referred to by `prf:0`", async () => { + const leafUcanA = await token.build({ + issuer: alice, + audience: bob.did(), + capabilities: [ emailCapability("ignore-me@email.com") ] + }) + + const leafUcanB = await token.build({ + issuer: alice, + audience: bob.did(), + capabilities: [ emailCapability("alice@email.com") ] + }) + + const faultyUcan = await token.build({ + issuer: bob, + audience: mallory.did(), + capabilities: [ capability.prf(0, REDELEGATE) ], + proofs: [ token.encode(leafUcanA), token.encode(leafUcanB) ] + }) + + const result = await verify(token.encode(faultyUcan), { + audience: mallory.did(), + requiredCapabilities: [ alicesEmail ] + }) + + expect(result.ok).toEqual(false) + }) + + it("rejects an improper `prf` redelegation", async () => { + // alice -> bob + // alice delegates access to sending email as her to bob + // and bob delegates it further to mallory + const leafUcan = await token.build({ + issuer: alice, + audience: bob.did(), + capabilities: [ emailCapability("invalid@email.com") ] + }) + + const ucan = await token.build({ + issuer: bob, + audience: mallory.did(), + capabilities: [ capability.prf(capability.superUser.SUPERUSER, REDELEGATE) ], + proofs: [ token.encode(leafUcan) ] + }) + + const result = await verify(token.encode(ucan), { + audience: mallory.did(), + requiredCapabilities: [ alicesEmail ] + }) + + expect(result.ok).toEqual(false) + }) + + it("supports redelegation with a `my` capability", async () => { + // alice -> bob + // alice delegates access to sending email as her to bob + // and bob delegates it further to mallory + const leafUcan = await token.build({ + issuer: alice, + audience: bob.did(), + capabilities: [ capability.my(capability.superUser.SUPERUSER) ] + }) + + const ucan = await token.build({ + issuer: bob, + audience: mallory.did(), + capabilities: [ emailCapability("alice@email.com") ], + proofs: [ token.encode(leafUcan) ] + }) + + const result = await verify(token.encode(ucan), { + audience: mallory.did(), + requiredCapabilities: [ alicesEmail ], + }) + + expect(result.ok).toEqual(true) + + if (!result.ok) return + + expect(result.value[ 0 ]?.rootIssuer).toEqual(alice.did()) + }) + + it("supports redelegation with a `my` & `as` capability", async () => { + // alice -> bob, bob -> mallory, mallory -> "someone" + // alice delegates access to sending email as her to bob + // and bob delegates it further to mallory + const leafUcan = await token.build({ + issuer: alice, + audience: bob.did(), + capabilities: [ capability.my(capability.superUser.SUPERUSER) ] + }) + + const middleUcan = await token.build({ + issuer: bob, + audience: mallory.did(), + capabilities: [ capability.as(alice.did(), SUPERUSER) ], + proofs: [ token.encode(leafUcan) ] + }) + + const ucan = await token.build({ + issuer: mallory, + audience: "did:key:someone", + capabilities: [ emailCapability("alice@email.com") ], + proofs: [ token.encode(middleUcan) ] + }) + + const result = await verify(token.encode(ucan), { + audience: "did:key:someone", + requiredCapabilities: [ alicesEmail ] + }) + + expect(result.ok).toEqual(true) + + if (!result.ok) return + + expect(result.value[ 0 ]?.rootIssuer).toEqual(alice.did()) + }) + + it("rejects an improper `my` redelegation", async () => { + // alice -> bob + // alice delegates access to sending email as her to bob + // and bob delegates it further to mallory + const leafUcan = await token.build({ + issuer: alice, + audience: bob.did(), + capabilities: [] + }) + + const ucan = await token.build({ + issuer: bob, + audience: mallory.did(), + capabilities: [ emailCapability("alice@email.com") ], + proofs: [ token.encode(leafUcan) ] + }) + + const result = await verify(token.encode(ucan), { + audience: mallory.did(), + requiredCapabilities: [ alicesEmail ] + }) + + expect(result.ok).toEqual(false) + }) + + it("rejects an improper `as` redelegation - no `my`", async () => { + // alice -> bob + // alice delegates access to sending email as her to bob + // and bob delegates it further to mallory + const leafUcan = await token.build({ + issuer: alice, + audience: bob.did(), + capabilities: [ capability.as(bob.did(), SUPERUSER) ] + }) + + const ucan = await token.build({ + issuer: bob, + audience: mallory.did(), + capabilities: [ emailCapability("alice@email.com") ], + proofs: [ token.encode(leafUcan) ] + }) + + const result = await verify(token.encode(ucan), { + audience: mallory.did(), + requiredCapabilities: [ alicesEmail ] + }) + + expect(result.ok).toEqual(false) + }) + +}) diff --git a/tests-copy/attenuation.test.ts b/tests-copy/attenuation.test.ts new file mode 100644 index 0000000..a798d30 --- /dev/null +++ b/tests-copy/attenuation.test.ts @@ -0,0 +1,155 @@ +import * as token from "../src/token" +import { emailCapabilities, emailCapability } from "./capability/email" + +import { alice, bob, mallory } from "./fixtures" +import { all } from "../src/util" + + +describe("attenuation.emailCapabilities", () => { + + it("works with a simple example", async () => { + // alice -> bob, bob -> mallory + // alice delegates access to sending email as her to bob + // and bob delegates it further to mallory + const leafUcan = await token.build({ + issuer: alice, + audience: bob.did(), + capabilities: [ emailCapability("alice@email.com") ] + }) + + const ucan = await token.build({ + issuer: bob, + audience: mallory.did(), + capabilities: [ emailCapability("alice@email.com") ], + proofs: [ token.encode(leafUcan) ] + }) + + expect(await all(emailCapabilities(ucan))).toEqual([ + { + rootIssuer: bob.did(), + capability: emailCapability("alice@email.com") + }, + { + rootIssuer: alice.did(), + capability: emailCapability("alice@email.com") + } + ]) + }) + + it("reports the first issuer in the chain as originator", async () => { + // alice -> bob, bob -> mallory + // alice delegates nothing to bob + // and bob delegates his email to mallory + const leafUcan = await token.build({ + issuer: alice, + audience: bob.did(), + }) + + const ucan = await token.build({ + issuer: bob, + audience: mallory.did(), + capabilities: [ emailCapability("bob@email.com") ], + proofs: [ token.encode(leafUcan) ] + }) + + // we implicitly expect the originator to become bob + expect(await all(emailCapabilities(ucan))).toEqual([ { + rootIssuer: bob.did(), + capability: emailCapability("bob@email.com"), + } ]) + }) + + it("finds the right proof chain for the originator", async () => { + // alice -> mallory, bob -> mallory, mallory -> alice + // both alice and bob delegate their email access to mallory + // mallory then creates a UCAN with capability to send both + const leafUcanAlice = await token.build({ + issuer: alice, + audience: mallory.did(), + capabilities: [ emailCapability("alice@email.com") ] + }) + + const leafUcanBob = await token.build({ + issuer: bob, + audience: mallory.did(), + capabilities: [ emailCapability("bob@email.com") ] + }) + + const ucan = await token.build({ + issuer: mallory, + audience: alice.did(), + capabilities: [ + emailCapability("alice@email.com"), + emailCapability("bob@email.com") + ], + proofs: [ token.encode(leafUcanAlice), token.encode(leafUcanBob) ] + }) + + const chains = await all(emailCapabilities(ucan)) + + expect(chains).toEqual([ + // We expect two capabilities from parenthood: + { + rootIssuer: mallory.did(), + capability: emailCapability("alice@email.com") + }, + { + rootIssuer: mallory.did(), + capability: emailCapability("bob@email.com") + }, + // Then there's also the delegations + { + rootIssuer: alice.did(), + capability: emailCapability("alice@email.com") + }, + { + rootIssuer: bob.did(), + capability: emailCapability("bob@email.com") + } + ]) + }) + + it("reports all chain options", async () => { + // alice -> mallory, bob -> mallory, mallory -> alice + // both alice and bob claim to have access to alice@email.com + // and both grant that capability to mallory + // a verifier needs to know both to verify valid email access + + const aliceEmail = emailCapability("alice@email.com") + + const leafUcanAlice = await token.build({ + issuer: alice, + audience: mallory.did(), + capabilities: [ aliceEmail ] + }) + + const leafUcanBob = await token.build({ + issuer: bob, + audience: mallory.did(), + capabilities: [ aliceEmail ] + }) + + const ucan = await token.build({ + issuer: mallory, + audience: alice.did(), + capabilities: [ aliceEmail ], + proofs: [ token.encode(leafUcanAlice), token.encode(leafUcanBob) ] + }) + + expect(await all(emailCapabilities(ucan))).toEqual([ + { + rootIssuer: mallory.did(), + capability: aliceEmail + }, + { + rootIssuer: alice.did(), + capability: aliceEmail + }, + { + rootIssuer: bob.did(), + capability: aliceEmail + } + ]) + }) + +}) diff --git a/tests-copy/builder.test.ts b/tests-copy/builder.test.ts new file mode 100644 index 0000000..144ec0f --- /dev/null +++ b/tests-copy/builder.test.ts @@ -0,0 +1,135 @@ +import * as token from "../src/token" +import { Builder } from "../src/builder" +import { emailCapability } from "./capability/email" +import { wnfsCapability, wnfsPublicSemantics } from "./capability/wnfs" +import { EMAIL_SEMANTICS } from "./capability/email" +import { alice, bob, mallory } from "./fixtures" +import { delegationChains } from "../src/attenuation" +import { first } from "../src/util" + + +describe("Builder", () => { + + it("builds with a simple example", async () => { + const fact1 = { test: true } + const fact2 = { preimage: "abc", hash: "sth" } + const cap1 = emailCapability("alice@email.com") + const cap2 = wnfsCapability("alice.fission.name/public/", "SUPER_USER") + const expiration = Math.floor(Date.now() / 1000) + 30 + const notBefore = Math.floor(Date.now() / 1000) - 30 + + const ucan = await Builder.create() + .issuedBy(alice) + .toAudience(bob.did()) + .withExpiration(expiration) + .withNotBefore(notBefore) + .withFact(fact1, fact2) + .withNonce() + .claimCapability(cap1, cap2) + .build() + + expect(ucan.payload.iss).toEqual(alice.did()) + expect(ucan.payload.aud).toEqual(bob.did()) + expect(ucan.payload.exp).toEqual(expiration) + expect(ucan.payload.nbf).toEqual(notBefore) + expect(ucan.payload.fct).toEqual([ fact1, fact2 ]) + expect(ucan.payload.att).toEqual([ cap1, cap2 ]) + expect(ucan.payload.nnc).toBeDefined() + }) + + it("builds with lifetimeInSeconds", async () => { + const payload = Builder.create() + .issuedBy(alice) + .toAudience(bob.did()) + .withLifetimeInSeconds(300) + .buildPayload() + + expect(payload.exp).toBeGreaterThan(Date.now() / 1000 + 290) + }) + + it("prevents duplicate proofs", async () => { + const ucan = await Builder.create() + .issuedBy(alice) + .toAudience(bob.did()) + .withLifetimeInSeconds(30) + .claimCapability(wnfsCapability("alice.fission.name/public/", "SUPER_USER")) + .build() + + const publicCapability = await first(delegationChains(wnfsPublicSemantics, ucan)) + + if (publicCapability == null) { + throw "no capabilities" + } + + if (publicCapability instanceof Error) { + throw publicCapability + } + + const payload = Builder.create() + .issuedBy(bob) + .toAudience(mallory.did()) + .withLifetimeInSeconds(30) + .delegateCapability(wnfsCapability("alice.fission.name/public/Apps", "CREATE"), publicCapability, wnfsPublicSemantics) + .delegateCapability(wnfsCapability("alice.fission.name/public/Documents", "OVERWRITE"), publicCapability, wnfsPublicSemantics) + .buildPayload() + + expect(payload.prf).toEqual([ token.encode(ucan) ]) + }) + + it("throws when it's not ready to be built", () => { + expect(() => { + Builder.create() + .buildPayload() + }).toThrow() + // issuer missing + expect(() => { + Builder.create() + .toAudience(bob.did()) + .withLifetimeInSeconds(1) + .buildPayload() + }).toThrow() + // audience missing + expect(() => { + Builder.create() + .issuedBy(alice) + .withLifetimeInSeconds(1) + .buildPayload() + }).toThrow() + // expiration missing + expect(() => { + Builder.create() + .issuedBy(alice) + .toAudience(bob.did()) + .buildPayload() + }).toThrow() + }) + + it("throws when trying to delegate unproven capabilities", async () => { + const ucan = await Builder.create() + .issuedBy(alice) + .toAudience(bob.did()) + .withLifetimeInSeconds(30) + .claimCapability(emailCapability("alice@email.com")) + .build() + + const delegationChain = await first(delegationChains(EMAIL_SEMANTICS, ucan)) + + if (delegationChain == null) { + throw "no capabilities" + } + + if (delegationChain instanceof Error) { + throw delegationChain + } + + expect(() => { + Builder.create() + .issuedBy(bob) + .toAudience(mallory.did()) + .withLifetimeInSeconds(30) + .delegateCapability(emailCapability("bob@email.com"), delegationChain, EMAIL_SEMANTICS) + .buildPayload() + }).toThrow() + }) + +}) diff --git a/tests-copy/capability.test.ts b/tests-copy/capability.test.ts new file mode 100644 index 0000000..671469c --- /dev/null +++ b/tests-copy/capability.test.ts @@ -0,0 +1,22 @@ +import * as capability from "../src/capability" + + +describe("capability.isEqual", () => { + + it("is able to compare two equal capabilities", () => { + const a = { + with: { scheme: "scheme", hierPart: "hierPart" }, + can: { namespace: "namespace", segments: [ "a", "B" ] } + } + + const b = { + with: { scheme: "SCHEME", hierPart: "hierPart" }, + can: { namespace: "NAMESPACE", segments: [ "A", "b" ] } + } + + expect(capability.isEqual(a, b)).toBe(true) + expect(capability.resourcePointer.isEqual(a.with, b.with)).toBe(true) + expect(capability.ability.isEqual(a.can, b.can)).toBe(true) + }) + +}) \ No newline at end of file diff --git a/tests-copy/capability/email.ts b/tests-copy/capability/email.ts new file mode 100644 index 0000000..63ad868 --- /dev/null +++ b/tests-copy/capability/email.ts @@ -0,0 +1,82 @@ +import { Ucan } from "../../src/types" +import { DelegationSemantics, delegationChains, rootIssuer } from "../../src/attenuation" +import { Ability } from "../../src/capability/ability" +import { Capability } from "../../src/capability" +import { SUPERUSER } from "../../src/capability/super-user" +import { ResourcePointer } from "../../src/capability/resource-pointer" + + +// 🌸 + + +export interface EmailCapability { + with: ResourcePointer + can: Ability +} + + + +// 🏔 + + +export const SEND_ABILITY: Ability = { namespace: "msg", segments: [ "SEND" ] } + + +export const EMAIL_SEMANTICS: DelegationSemantics = { + + canDelegateResource(parentResource, resource) { + if (parentResource.scheme !== "email") { + return false + } + if (resource.scheme !== "email") { + return false + } + return parentResource.hierPart === resource.hierPart + }, + + canDelegateAbility(parentAbility, ability) { + if (parentAbility === SUPERUSER) { + return true + } + if (ability === SUPERUSER) { + return false + } + return parentAbility.namespace === "msg" + && parentAbility.segments.length === 1 + && parentAbility.segments[0] === "SEND" + && ability.namespace === "msg" + && ability.segments.length === 1 + && ability.segments[0] === "SEND" + } + +} + + + +// 🛠 + + +export function emailResourcePointer(emailAddress: string): ResourcePointer { + return { scheme: "email", hierPart: emailAddress } +} + + +export function emailCapability(emailAddress: string): Capability { + return { + with: emailResourcePointer(emailAddress), + can: SEND_ABILITY + } +} + + +export async function * emailCapabilities(ucan: Ucan): AsyncIterable<{ capability: EmailCapability; rootIssuer: string }> { + for await (const delegationChain of delegationChains(EMAIL_SEMANTICS, ucan)) { + if (delegationChain instanceof Error || "ownershipDID" in delegationChain) { + continue + } + yield { + rootIssuer: rootIssuer(delegationChain), + capability: delegationChain.capability + } + } +} diff --git a/tests-copy/capability/wnfs.test.ts b/tests-copy/capability/wnfs.test.ts new file mode 100644 index 0000000..58f9bdb --- /dev/null +++ b/tests-copy/capability/wnfs.test.ts @@ -0,0 +1,238 @@ +import * as token from "../../src/token" +import { Capability } from "../../src/capability" +import { wnfsCapability, wnfsPrivateCapabilities, wnfsPublicCapabilities } from "./wnfs" + +import { alice, bob, mallory } from "../fixtures" +import { all } from "../../src/util" + + + +describe("wnfs public capability", () => { + + it("works with a simple example", async () => { + const { ucan } = await makeSimpleDelegation( + [ wnfsCapability("//boris.fission.name/public/Apps/", "OVERWRITE") ], + [ wnfsCapability("//boris.fission.name/public/Apps/appinator/", "REVISE") ] + ) + + expect(await all(wnfsPublicCapabilities(ucan))).toEqual([ + { + rootIssuer: bob.did(), + capability: wnfsCapability("//boris.fission.name/public/Apps/appinator/", "REVISE") + }, + { + rootIssuer: alice.did(), + capability: wnfsCapability("//boris.fission.name/public/Apps/appinator/", "REVISE") + } + ]) + }) + + it("detects capability escalations", async () => { + const { ucan } = await makeSimpleDelegation( + [ wnfsCapability("//boris.fission.name/public/Apps/", "CREATE") ], + [ wnfsCapability("//boris.fission.name/public/Apps/appinator/", "OVERWRITE") ] + ) + + expect(await all(wnfsPublicCapabilities(ucan))).toEqual([ + { + rootIssuer: bob.did(), + capability: wnfsCapability("//boris.fission.name/public/Apps/appinator/", "OVERWRITE") + } + ]) + }) + + it("detects capability escalations, even if there's valid capabilities", async () => { + const { ucan } = await makeSimpleDelegation( + [ + wnfsCapability("//boris.fission.name/public/Apps/", "CREATE"), + wnfsCapability("//boris.fission.name/public/Apps/", "SUPER_USER") + ], + [ wnfsCapability("//boris.fission.name/public/Apps/appinator/", "OVERWRITE") + ] + ) + + expect(await all(wnfsPublicCapabilities(ucan))).toEqual([ + { + rootIssuer: bob.did(), + capability: wnfsCapability("//boris.fission.name/public/Apps/appinator/", "OVERWRITE") + }, + { + rootIssuer: alice.did(), + capability: wnfsCapability("//boris.fission.name/public/Apps/appinator/", "OVERWRITE") + } + ]) + }) + +}) + +describe("wnfs private capability", () => { + + it("works with a simple example", async () => { + const { ucan } = await makeSimpleDelegation( + [ wnfsCapability("//boris.fission.name/private/abc", "OVERWRITE") ], + [ wnfsCapability("//boris.fission.name/private/def", "REVISE") ] + ) + + expect(await all(wnfsPrivateCapabilities(ucan))).toEqual([ + { + rootIssuer: bob.did(), + capability: wnfsCapability("//boris.fission.name/private/def", "REVISE"), + requiredINumbers: new Set(["def"]) + }, + { + rootIssuer: alice.did(), + capability: wnfsCapability("//boris.fission.name/private/def", "REVISE"), + requiredINumbers: new Set(["abc", "def"]) + } + ]) + }) + + it("detects capability escalations", async () => { + const { ucan } = await makeSimpleDelegation( + [ wnfsCapability("//boris.fission.name/private/abc", "OVERWRITE") ], + [ wnfsCapability("//boris.fission.name/private/def", "SUPER_USER") ] + ) + + expect(await all(wnfsPrivateCapabilities(ucan))).toEqual([ + { + rootIssuer: bob.did(), + capability: wnfsCapability("//boris.fission.name/private/def", "SUPER_USER"), + requiredINumbers: new Set(["def"]) + }, + ]) + }) + + it("detects capability escalations, but still returns valid delegations", async () => { + const { ucan } = await makeSimpleDelegation( + [ wnfsCapability("//boris.fission.name/private/abc", "OVERWRITE") ], + [ + wnfsCapability("//boris.fission.name/private/def", "SUPER_USER"), + wnfsCapability("//boris.fission.name/private/ghi", "CREATE") + ] + ) + + expect(await all(wnfsPrivateCapabilities(ucan))).toEqual([ + { + rootIssuer: bob.did(), + capability: wnfsCapability("//boris.fission.name/private/def", "SUPER_USER"), + requiredINumbers: new Set(["def"]) + }, + { + rootIssuer: bob.did(), + capability: wnfsCapability("//boris.fission.name/private/ghi", "CREATE"), + requiredINumbers: new Set(["ghi"]) + }, + { + rootIssuer: alice.did(), + capability: wnfsCapability("//boris.fission.name/private/ghi", "CREATE"), + requiredINumbers: new Set(["ghi", "abc"]) + } + ]) + }) + + it("lists all possible inumber combinations", async () => { + const { ucan } = await makeComplexDelegation( + { + alice: [ wnfsCapability("//boris.fission.name/private/inumalice", "OVERWRITE") ], + bob: [ wnfsCapability("//boris.fission.name/private/inumbob", "OVERWRITE") ] + }, + [ wnfsCapability("//boris.fission.name/private/subinum", "OVERWRITE") ] + ) + + expect(await all(wnfsPrivateCapabilities(ucan))).toEqual([ + { + rootIssuer: mallory.did(), + capability: wnfsCapability("//boris.fission.name/private/subinum", "OVERWRITE"), + requiredINumbers: new Set(["subinum"]) + }, + { + rootIssuer: alice.did(), + capability: wnfsCapability("//boris.fission.name/private/subinum", "OVERWRITE"), + requiredINumbers: new Set(["subinum", "inumalice"]) + }, + { + rootIssuer: bob.did(), + capability: wnfsCapability("//boris.fission.name/private/subinum", "OVERWRITE"), + requiredINumbers: new Set(["subinum", "inumbob"]) + } + ]) + }) + + it("lists all possible inumber combinations except escalations", async () => { + const { ucan } = await makeComplexDelegation( + { + alice: [ wnfsCapability("//boris.fission.name/private/inumalice", "CREATE") ], + bob: [ wnfsCapability("//boris.fission.name/private/inumbob", "OVERWRITE") ] + }, + [ wnfsCapability("//boris.fission.name/private/subinum", "OVERWRITE") ] + ) + + expect(await all(wnfsPrivateCapabilities(ucan))).toEqual([ + { + rootIssuer: mallory.did(), + capability: wnfsCapability("//boris.fission.name/private/subinum", "OVERWRITE"), + requiredINumbers: new Set(["subinum"]) + }, + { + rootIssuer: bob.did(), + capability: wnfsCapability("//boris.fission.name/private/subinum", "OVERWRITE"), + requiredINumbers: new Set(["subinum", "inumbob"]) + } + ]) + }) + +}) + +/** + * A linear delegation chain: + * alice -> bob -> mallory + * + * The arguments are the capabilities delegated in the first and second arrow, respectively. + */ +async function makeSimpleDelegation(aliceCapabilities: Capability[], bobCapabilities: Capability[]) { + const leaf = await token.build({ + issuer: alice, + audience: bob.did(), + capabilities: aliceCapabilities + }) + + const ucan = await token.build({ + issuer: bob, + audience: mallory.did(), + capabilities: bobCapabilities, + proofs: [ token.encode(leaf) ] + }) + + return { leaf, ucan } +} + + +/** + * A tree-like delegation ucan: + * alice & bob => mallory -> alice + * + * The first argument are the capabilities delegated in the first two arrows, + * the second argument are the capabilities delegated in the last arrow. + */ +async function makeComplexDelegation(proofs: { alice: Capability[]; bob: Capability[] }, final: Capability[]) { + const leafAlice = await token.build({ + issuer: alice, + audience: mallory.did(), + capabilities: proofs.alice, + }) + + const leafBob = await token.build({ + issuer: bob, + audience: mallory.did(), + capabilities: proofs.bob, + }) + + const ucan = await token.build({ + issuer: mallory, + audience: alice.did(), + capabilities: final, + proofs: [ token.encode(leafAlice), token.encode(leafBob) ], + }) + + return { leafAlice, leafBob, ucan } +} diff --git a/tests-copy/capability/wnfs.ts b/tests-copy/capability/wnfs.ts new file mode 100644 index 0000000..68b189f --- /dev/null +++ b/tests-copy/capability/wnfs.ts @@ -0,0 +1,213 @@ +import { Ability, isAbility } from "../../src/capability/ability" +import { Capability } from "../../src/capability" +import { DelegationSemantics, DelegatedCapability, DelegatedOwnership, delegationChains, rootIssuer } from "../../src/attenuation" +import { SUPERUSER } from "../../src/capability/super-user" +import { Ucan } from "../../src/types" +import { ResourcePointer } from "../../src/capability/resource-pointer" + + +export const WNFS_ABILITY_LEVELS = { + "SUPER_USER": 0, + "OVERWRITE": -1, + "SOFT_DELETE": -2, + "REVISE": -3, + "CREATE": -4, +} + +export const WNFS_ABILITIES: string[] = Object.keys(WNFS_ABILITY_LEVELS) + +export type WnfsAbility = keyof typeof WNFS_ABILITY_LEVELS + +export function isWnfsCap(cap: Capability): boolean { + return cap.with.scheme === "wnfs" && isWnfsAbility(cap.can) +} + +export function isWnfsAbility(ability: unknown): ability is WnfsAbility { + if (!isAbility(ability)) return false + if (ability === SUPERUSER) return true + const abilitySegment = ability.segments[ 0 ] + const isWnfsAbilitySegment = !!abilitySegment && WNFS_ABILITIES.includes(abilitySegment) + return isWnfsAbilitySegment && ability.namespace.toLowerCase() === "wnfs" +} + +export function wnfsAbilityFromAbility(ability: Ability): WnfsAbility | null { + if (ability === SUPERUSER) return "SUPER_USER" + if (isWnfsAbility(ability)) return ability.segments[ 0 ] as WnfsAbility + return null +} + +export function wnfsCapability(path: string, ability: WnfsAbility): Capability { + return { + with: { scheme: "wnfs", hierPart: path }, + can: { namespace: "wnfs", segments: [ ability ] } + } +} + + + +////////////////////////////// +// Public WNFS Capabilities // +////////////////////////////// + + +interface WnfsPublicResourcePointer { + user: string // e.g. matheus23.fission.name + publicPath: string[] +} + +function tryParseWnfsPublicResource(pointer: ResourcePointer): WnfsPublicResourcePointer | null { + if (pointer.scheme !== "wnfs") return null + + // remove trailing slash + const path = pointer.hierPart.replace(/^\/\//, "") + const trimmed = path.endsWith("/") ? path.slice(0, -1) : path + const split = trimmed.split("/") + const user = split[ 0 ] + const publicPath = split.slice(2) // drop first two: matheus23.fission.name/public/keep/this + if (user == null || split[ 1 ] !== "public") return null + + return { + user, + publicPath, + } +} + +export const wnfsPublicSemantics: DelegationSemantics = { + + canDelegateResource(parentResource, childResource) { + const parent = tryParseWnfsPublicResource(parentResource) + const child = tryParseWnfsPublicResource(childResource) + + if (parent == null || child == null) { + return false + } + + if (parent.user !== child.user) { + return false + } + + // parentCap path must be a prefix of childCap path + if (child.publicPath.length < parent.publicPath.length) { + return false + } + + for (let i = 0; i < parent.publicPath.length; i++) { + if (child.publicPath[ i ] !== parent.publicPath[ i ]) { + return false + } + } + + return true + }, + + canDelegateAbility(parentAbility, childAbility) { + const parent = wnfsAbilityFromAbility(parentAbility) + const child = wnfsAbilityFromAbility(childAbility) + + if (parent == null || child == null) { + return false + } + + if (WNFS_ABILITY_LEVELS[ child ] > WNFS_ABILITY_LEVELS[ parent ]) { + return false + } + + return true + } + +} + +export async function * wnfsPublicCapabilities(ucan: Ucan) { + for await (const delegationChain of delegationChains(wnfsPublicSemantics, ucan)) { + if (delegationChain instanceof Error || "ownershipDID" in delegationChain) { + continue + } + yield { + capability: delegationChain.capability, + rootIssuer: rootIssuer(delegationChain), + } + } +} + + + +/////////////////////////////// +// Private WNFS Capabilities // +/////////////////////////////// + + +interface WnfsPrivateResourcePointer { + user: string + requiredINumbers: Set +} + +function tryParseWnfsPrivateResource(pointer: ResourcePointer): WnfsPrivateResourcePointer | null { + if (pointer.scheme !== "wnfs") return null + + // split up "boris.fission.name/private/fccXmZ8HYmpwxkvPSjwW9A" into "/private/" + const split = pointer.hierPart.replace(/^\/\//, "").split("/") + const user = split[ 0 ] + const inumberBase64url = split[ 2 ] + + if (user == null || split[ 1 ] !== "private" || inumberBase64url == null) return null + + return { + user, + requiredINumbers: new Set([ inumberBase64url ]), + } +} + +const wnfsPrivateSemantics: DelegationSemantics = { + + canDelegateResource(parentResource, childResource) { + const parent = tryParseWnfsPrivateResource(parentResource) + const child = tryParseWnfsPrivateResource(childResource) + + if (parent == null || child == null) { + return false + } + + // There's more tests that need to be run on the resulting delegation chain. + return true + }, + + canDelegateAbility(parentAbility, childAbility) { + const parent = wnfsAbilityFromAbility(parentAbility) + const child = wnfsAbilityFromAbility(childAbility) + + if (parent == null || child == null) { + return false + } + + if (WNFS_ABILITY_LEVELS[ child ] > WNFS_ABILITY_LEVELS[ parent ]) { + return false + } + + return true + } + +} + +export async function * wnfsPrivateCapabilities(ucan: Ucan) { + for await (const delegationChain of delegationChains(wnfsPrivateSemantics, ucan)) { + if (delegationChain instanceof Error || "ownershipDID" in delegationChain) { + continue + } + + const requiredINumbers = new Set() + let chainStep: DelegatedCapability | DelegatedOwnership | undefined = delegationChain + + while (chainStep != null && "capability" in chainStep) { + const hierSplit = chainStep.capability.with.hierPart.split("/") + const inumber = hierSplit[hierSplit.length - 1] + requiredINumbers.add(inumber) + chainStep = chainStep.chainStep + } + + yield { + capability: delegationChain.capability, + requiredINumbers, + rootIssuer: rootIssuer(delegationChain), + } + } +} diff --git a/tests-copy/compatibility.test.ts b/tests-copy/compatibility.test.ts new file mode 100644 index 0000000..fca9af9 --- /dev/null +++ b/tests-copy/compatibility.test.ts @@ -0,0 +1,39 @@ +import * as uint8arrays from "uint8arrays" +import * as token from "../src/token" + + +const oldUcan = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsInVhdiI6IjEuMC4wIn0.eyJhdWQiOiJkaWQ6a2V5OnoxM1YzU29nMllhVUtoZEdDbWd4OVVadVcxbzFTaEZKWWM2RHZHWWU3TlR0Njg5Tm9MMXRrZUd3NGMydGFQa2dBdWloUjh0cmg2azg2VHRVaTNIR2ZrNEh1NDg3czNiTWY4V1MzWjJoU3VwRktiNmhnV3VwajFIRzhheUxRdDFmeWJSdThjTGdBMkNKanFRYm16YzRFOEFKU0tKeDNndVFYa2F4c3R2Um5RRGN1eDFkZzhVR1BRS3haN2lLeUFKWkFuQlcyWXJUM2o0TVQxdTJNcWZQWG9RYU01WFZQMk04clBFN0FCSEREOXdMbWlKdjkzUUFDRFR5MllnZkVSS3JualNWaTdFb3RNOFR3NHg3M1pNUXJEQnZRRW01Zm9tTWZVaTZVSmJUTmVaaldDTUJQYllNbXRKUDZQZlRpaWZYZG0zdXprVFg5NnExUkVFOExodkU2Rzg2cUR0Wjg5MzdFYUdXdXFpNkRHVDFvc2FRMUVnR3NFN3Jac2JSdDFLNnRXeTZpYktlNTlKZWtnTWFlNW9XNER2IiwiZXhwIjozMjc0NDE5MTQyMywiZmN0IjpbXSwiaXNzIjoiZGlkOmtleTp6MTNWM1NvZzJZYVVLaGRHQ21neDlVWnVXMW8xU2hGSlljNkR2R1llN05UdDY4OU5vTDJWanZBR2JXdTFrdmZWUWFyVTVWMXBTUnNjOWFwR2h2dDdaODJmUWg1QWE1NW41Zm0zZGs2SnFuTXczZGU4WG91dWZUV2Z1eHpEVkhrSFNGV0sxOW1SWWI4d205d1VwZkxtUWl4QVdtMndFWVZqU2dENEd6YzhVUDlDSjFxMkY4ZXlpVXViMThGbld4Y2djUWhqdXB3OTNxUlMzWDlXUDViemlSYjE4TTZ0Vm8zaUJ4ZUozb2lrRTNaa3RScEtTZDlkcHU5WWNXZFhoeDZDQmY5NTZ1UXhkTDZoTkppNmVMbmZ1eFY2NEhpZU1rZFVoTTJSeThRd3lqZjQ4ZnZWMVhFVU1zeEM5YWFjNEtCcGJONDJHR3U4UmFkRDU3cjZuMWFOc2IyTjU3RkNOYnFIMXVLdHhNTmVHZHJ2QWlUUGRzVjJBRmppczJvN243ajhMNW41YmJ4TFl4VThNVHB3QVphdFpkSiIsIm5iZiI6MTY0MDE5MTQ1NywicHJmIjoiZXlKaGJHY2lPaUpTVXpJMU5pSXNJblI1Y0NJNklrcFhWQ0lzSW5WaGRpSTZJakV1TUM0d0luMC5leUpoZFdRaU9pSmthV1E2YTJWNU9ub3hNMVl6VTI5bk1sbGhWVXRvWkVkRGJXZDRPVlZhZFZjeGJ6RlRhRVpLV1dNMlJIWkhXV1UzVGxSME5qZzVUbTlNTWxacWRrRkhZbGQxTVd0MlpsWlJZWEpWTlZZeGNGTlNjMk01WVhCSGFIWjBOMW80TW1aUmFEVkJZVFUxYmpWbWJUTmthelpLY1c1TmR6TmtaVGhZYjNWMVpsUlhablY0ZWtSV1NHdElVMFpYU3pFNWJWSlpZamgzYlRsM1ZYQm1URzFSYVhoQlYyMHlkMFZaVm1wVFowUTBSM3BqT0ZWUU9VTktNWEV5UmpobGVXbFZkV0l4T0VadVYzaGpaMk5SYUdwMWNIYzVNM0ZTVXpOWU9WZFFOV0o2YVZKaU1UaE5OblJXYnpOcFFuaGxTak52YVd0Rk0xcHJkRkp3UzFOa09XUndkVGxaWTFka1dHaDROa05DWmprMU5uVlJlR1JNTm1oT1NtazJaVXh1Wm5WNFZqWTBTR2xsVFd0a1ZXaE5NbEo1T0ZGM2VXcG1ORGhtZGxZeFdFVlZUWE40UXpsaFlXTTBTMEp3WWs0ME1rZEhkVGhTWVdSRU5UZHlObTR4WVU1ellqSk9OVGRHUTA1aWNVZ3hkVXQwZUUxT1pVZGtjblpCYVZSUVpITldNa0ZHYW1sek1tODNiamRxT0V3MWJqVmlZbmhNV1hoVk9FMVVjSGRCV21GMFdtUktJaXdpWlhod0lqb3pNamMwTkRFNU1UUXlNeXdpWm1OMElqcGJYU3dpYVhOeklqb2laR2xrT210bGVUcDZNVE5XTTFOdlp6SlpZVlZMYUdSSFEyMW5lRGxWV25WWE1XOHhVMmhHU2xsak5rUjJSMWxsTjA1VWREWTRPVTV2VERKaE5VcE9hMlI0VmpabWJYVm9WbU5SWkRkSVIycHhkRXBRYVc1WlZWQTRRMUp4Y21veVkyVm5hVTFyT1RKUlNIazJRbWRXT1hveVVGQnJWMkZZU0RkUlRsQmlRekphZEUxNWFXbGFjWGRLUkVOd05sZG9VbkZVUzJodVFtaENUbWQ1WkRkTFJuUTNjRkkyTkhCa1ZIQjZUbXRNUlZKNGFHNTNUVUZqZURKcVJGZFlOelpDVG5SS04xUTFWVXQ0TTIxcWRHWTBaak0wWjJwVGRUaHJkME5UY0V0alFuQTRWV2RwU0hkdllVSkhkREUxVkZjNVUzQlNXVkoxYUZKdk1tdEljVFZ5Y0ROTmRFSnFSa2QyVUdZeVRsTlpZbUUzTmxoSGJYcFhlVEZyZUZOelEySTVUSGhqTW5welEwdG1lSEF5ZUd0VVFqWmtPVVJDUlVwVE5sUnhXbFo1WkhKU05GWmFNVkE1ZFhJeGRGcHBlbk5qYWtWd1kzVlViV1EzV0VRemRYSjZVelpqY0RSdU1sZHdSbFZNYjNsMk5tOW5ibWxaZEVOSGFUVlVlbWxEY2pKT1FWRjNWMEZYY25CMldVMWllbVEyVmt0a2RUVmpaekZZUWxoTVZFNWhUQ0lzSW01aVppSTZNVFkwTURFNU1UTTJNeXdpY0hSaklqb2lVMVZRUlZKZlZWTkZVaUlzSW5Kell5STZJaW9pZlEuQ0k5SjlOLVhUZUxQNEM5WTktUl9TcEE1aE80dHdpNUQxNFpTR2lwUzdjNS1jTlJWTVItc285Z0JZMlQzSFNaTHFmQ2xyMEtlQVJicFk2TFBwSm1NRGQ1ODdvck1TVVRnMndqN043eUNVeksxSWhOazhQMkQ3RGVlSHNxQ1lsTVotdXpjMHBSbnFJb3dPTWl6MVFkbHZXaTZ0UHNxZkZVYnl4bEx1bXRHdjV1a1hqc1FZcmYzdko3aU5DMkJibWotMGhTV25wNTNBN01TQTllLWFXVGpLUWEwSkpXVVVhWG5XS19CNjRaa3NyTWRXdW5mVFNuSE9lR2o3MFRuSXhieVcxbFhodk5pcnhIUV90ZVlKZ2xIZTRBbldEQXdUa2dnaVotdkp0WUhsYnVwQkt4S1YtNm9OMTlXS3dUT3U3QnpPX2QyUHAtWVVyY1RSSS1KZ0F2NUpnIiwicHRjIjoiU1VQRVJfVVNFUiIsInJzYyI6IioifQ.CRLB4gBBHhnsbfUhLALiCfo6mHnHSlEUczyZsWhh9TNjv9UxdgvsSWQsehGIT4XR0jQeYZo2OhasEVaF-Gtt_qqtUQIrducKngd0qzmpfjVbicsQPKVdJjlcTwm9dqhLSEtL195El0oucLzYdqMEZMf-txEmyhCd_Q8CaNExhAnwN32v1salnO6vrAw33ZJID7ZaFmBleoGUXBHQwnkv9_m_P6Fh-UGIKjaOuNmBkGXGn-4irm-eXrne2OPZCoPjhiaf0xTONu4ROrQQYykG8CppvsSXeiylOFY11Ot0sdAlHGSlyZk1_chJ3ud17K9S-CKWK9NtqiMNcUdQGFnNQQ" +const [ encodedHeader, encodedPayload, encodedSignature ] = oldUcan.split(".") +const signature = encodedSignature +const header = JSON.parse(uint8arrays.toString(uint8arrays.fromString(encodedHeader, "base64url"))) +const payload = JSON.parse(uint8arrays.toString(uint8arrays.fromString(encodedPayload, "base64url"))) + +describe("compatibility", () => { + + it("allows parsing UCANs with 'uav: 1.0.0' into 'ucv: 0.3.0'", async () => { + const ucan = await token.validate(oldUcan, { checkIsExpired: false, checkIsTooEarly: false, checkSignature: false }) + expect(ucan).toEqual({ + header: { + alg: header.alg, // "RS256", + typ: header.typ, // "JWT", + ucv: { major: 0, minor: 3, patch: 0 } // we translate uav: 1.0.0 to ucv: 0.3.0 + }, + payload: { + iss: payload.iss, // "did:key:z13V3Sog2YaUKhdGCmgx9UZuW1o1ShFJYc6DvGYe7NTt689NoL2VjvAGbWu1kvfVQarU5V1pSRsc9apGhvt7Z82fQh5Aa55n5fm3dk6JqnMw3de8XouufTWfuxzDVHkHSFWK19mRYb8wm9wUpfLmQixAWm2wEYVjSgD4Gzc8UP9CJ1q2F8eyiUub18FnWxcgcQhjupw93qRS3X9WP5bziRb18M6tVo3iBxeJ3oikE3ZktRpKSd9dpu9YcWdXhx6CBf956uQxdL6hNJi6eLnfuxV64HieMkdUhM2Ry8Qwyjf48fvV1XEUMsxC9aac4KBpbN42GGu8RadD57r6n1aNsb2N57FCNbqH1uKtxMNeGdrvAiTPdsV2AFjis2o7n7j8L5n5bbxLYxU8MTpwAZatZdJ", + aud: payload.aud, // "did:key:z13V3Sog2YaUKhdGCmgx9UZuW1o1ShFJYc6DvGYe7NTt689NoL1tkeGw4c2taPkgAuihR8trh6k86TtUi3HGfk4Hu487s3bMf8WS3Z2hSupFKb6hgWupj1HG8ayLQt1fybRu8cLgA2CJjqQbmzc4E8AJSKJx3guQXkaxstvRnQDcux1dg8UGPQKxZ7iKyAJZAnBW2YrT3j4MT1u2MqfPXoQaM5XVP2M8rPE7ABHDD9wLmiJv93QACDTy2YgfERKrnjSVi7EotM8Tw4x73ZMQrDBvQEm5fomMfUi6UJbTNeZjWCMBPbYMmtJP6PfTiifXdm3uzkTX96q1REE8LhvE6G86qDtZ8937EaGWuqi6DGT1osaQ1EgGsE7rZsbRt1K6tWy6ibKe59JekgMae5oW4Dv", + nbf: payload.nbf, // 1640191457, + exp: payload.exp, // 32744191423, + att: [ { + can: payload.rsc, // "*", + with: { scheme: "my", hierPart: payload.rsc } + } ], + prf: [ + payload.prf, // "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsInVhdiI6IjEuMC4wIn0.eyJhdWQiOiJkaWQ6a2V5OnoxM1YzU29nMllhVUtoZEdDbWd4OVVadVcxbzFTaEZKWWM2RHZHWWU3TlR0Njg5Tm9MMlZqdkFHYld1MWt2ZlZRYXJVNVYxcFNSc2M5YXBHaHZ0N1o4MmZRaDVBYTU1bjVmbTNkazZKcW5NdzNkZThYb3V1ZlRXZnV4ekRWSGtIU0ZXSzE5bVJZYjh3bTl3VXBmTG1RaXhBV20yd0VZVmpTZ0Q0R3pjOFVQOUNKMXEyRjhleWlVdWIxOEZuV3hjZ2NRaGp1cHc5M3FSUzNYOVdQNWJ6aVJiMThNNnRWbzNpQnhlSjNvaWtFM1prdFJwS1NkOWRwdTlZY1dkWGh4NkNCZjk1NnVReGRMNmhOSmk2ZUxuZnV4VjY0SGllTWtkVWhNMlJ5OFF3eWpmNDhmdlYxWEVVTXN4QzlhYWM0S0JwYk40MkdHdThSYWRENTdyNm4xYU5zYjJONTdGQ05icUgxdUt0eE1OZUdkcnZBaVRQZHNWMkFGamlzMm83bjdqOEw1bjViYnhMWXhVOE1UcHdBWmF0WmRKIiwiZXhwIjozMjc0NDE5MTQyMywiZmN0IjpbXSwiaXNzIjoiZGlkOmtleTp6MTNWM1NvZzJZYVVLaGRHQ21neDlVWnVXMW8xU2hGSlljNkR2R1llN05UdDY4OU5vTDJhNUpOa2R4VjZmbXVoVmNRZDdIR2pxdEpQaW5ZVVA4Q1JxcmoyY2VnaU1rOTJRSHk2QmdWOXoyUFBrV2FYSDdRTlBiQzJadE15aWlacXdKRENwNldoUnFUS2huQmhCTmd5ZDdLRnQ3cFI2NHBkVHB6TmtMRVJ4aG53TUFjeDJqRFdYNzZCTnRKN1Q1VUt4M21qdGY0ZjM0Z2pTdThrd0NTcEtjQnA4VWdpSHdvYUJHdDE1VFc5U3BSWVJ1aFJvMmtIcTVycDNNdEJqRkd2UGYyTlNZYmE3NlhHbXpXeTFreFNzQ2I5THhjMnpzQ0tmeHAyeGtUQjZkOURCRUpTNlRxWlZ5ZHJSNFZaMVA5dXIxdFppenNjakVwY3VUbWQ3WEQzdXJ6UzZjcDRuMldwRlVMb3l2Nm9nbmlZdENHaTVUemlDcjJOQVF3V0FXcnB2WU1iemQ2VktkdTVjZzFYQlhMVE5hTCIsIm5iZiI6MTY0MDE5MTM2MywicHRjIjoiU1VQRVJfVVNFUiIsInJzYyI6IioifQ.CI9J9N-XTeLP4C9Y9-R_SpA5hO4twi5D14ZSGipS7c5-cNRVMR-so9gBY2T3HSZLqfClr0KeARbpY6LPpJmMDd587orMSUTg2wj7N7yCUzK1IhNk8P2D7DeeHsqCYlMZ-uzc0pRnqIowOMiz1QdlvWi6tPsqfFUbyxlLumtGv5ukXjsQYrf3vJ7iNC2Bbmj-0hSWnp53A7MSA9e-aWTjKQa0JJWUUaXnWK_B64ZksrMdWunfTSnHOeGj70TnIxbyW1lXhvNirxHQ_teYJglHe4AnWDAwTkggiZ-vJtYHlbupBKxKV-6oN19WKwTOu7BzO_d2Pp-YUrcTRI-JgAv5Jg", + ], + }, + signedData: `${encodedHeader}.${encodedPayload}`, + signature // "CRLB4gBBHhnsbfUhLALiCfo6mHnHSlEUczyZsWhh9TNjv9UxdgvsSWQsehGIT4XR0jQeYZo2OhasEVaF-Gtt_qqtUQIrducKngd0qzmpfjVbicsQPKVdJjlcTwm9dqhLSEtL195El0oucLzYdqMEZMf-txEmyhCd_Q8CaNExhAnwN32v1salnO6vrAw33ZJID7ZaFmBleoGUXBHQwnkv9_m_P6Fh-UGIKjaOuNmBkGXGn-4irm-eXrne2OPZCoPjhiaf0xTONu4ROrQQYykG8CppvsSXeiylOFY11Ot0sdAlHGSlyZk1_chJ3ud17K9S-CKWK9NtqiMNcUdQGFnNQQ" + }) + }) + +}) diff --git a/tests-copy/did-key.ts b/tests-copy/did-key.ts new file mode 100644 index 0000000..9ad2824 --- /dev/null +++ b/tests-copy/did-key.ts @@ -0,0 +1,36 @@ +import EcdsaKeypair from "../src/keypair/ecdsa.js" + +// did:key test vectors from W3C +// https://github.com/w3c-ccg/did-method-key/blob/main/test-vectors/nist-curves.json +const testVectors = [ + { + id: "did:key:zDnaerx9CtbPJ1q36T5Ln5wYt3MQYeGRG5ehnPAmxcf5mDZpv", + jwk: { + kty: "EC", + crv: "P-256", + x: "igrFmi0whuihKnj9R3Om1SoMph72wUGeFaBbzG2vzns", + y: "efsX5b10x8yjyrj4ny3pGfLcY7Xby1KzgqOdqnsrJIM", + d: "gPh-VvVS8MbvKQ9LSVVmfnxnKjHn4Tqj0bmbpehRlpc" + } + }, + { + id: "did:key:zDnaerDaTF5BXEavCrfRZEk316dpbLsfPDZ3WJ5hRTPFU2169", + jwk: { + kty: "EC", + crv: "P-256", + x: "fyNYMN0976ci7xqiSdag3buk-ZCwgXU4kz9XNkBlNUI", + y: "hW2ojTNfH7Jbi8--CJUo3OCbH3y5n91g-IMA9MLMbTU", + d: "YjRs6vNvw4sYrzVVY8ipkEpDAD9PFqw1sUnvPRMA-WI" + } + } +] + +describe("did:key ECDSA P-256", () => { + it("derives the correct DID from the JWK", async () => { + for(const vector of testVectors) { + const keypair = await EcdsaKeypair.importFromJwk(vector.jwk) + const did = keypair.did() + expect(did).toEqual(vector.id) + } + }) +}) \ No newline at end of file diff --git a/tests-copy/did.test.ts b/tests-copy/did.test.ts new file mode 100644 index 0000000..02abca7 --- /dev/null +++ b/tests-copy/did.test.ts @@ -0,0 +1,78 @@ +import * as did from "../src/did" + +describe("publicKeyToDid", () => { + + it("handles RSA Keys", async () => { + const pubkey = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAnzyis1ZjfNB0bBgKFMSvvkTtwlvBsaJq7S5wA+kzeVOVpVWwkWdVha4s38XM/pa/yr47av7+z3VTmvDRyAHcaT92whREFpLv9cj5lTeJSibyr/Mrm/YtjCZVWgaOYIhwrXwKLqPr/11inWsAkfIytvHWTxZYEcXLgAXFuUuaS3uF9gEiNQwzGTU1v0FqkqTBr4B8nW3HCN47XUu0t8Y0e+lf4s4OxQawWD79J9/5d3Ry0vbV3Am1FtGJiJvOwRsIfVChDpYStTcHTCMqtvWbV6L11BWkpzGXSW4Hv43qa+GSYOD2QU68Mb59oSk2OB+BtOLpJofmbGEGgvmwyCI9MwIDAQAB" + // old: const expectedDid = "did:key:z13V3Sog2YaUKhdGCmgx9UZuW1o1ShFJYc6DvGYe7NTt689NoL2RtpVs65Zw899YrTN9WuxdEEDm54YxWuQHQvcKfkZwa8HTgokHxGDPEmNLhvh69zUMEP4zjuARQ3T8bMUumkSLGpxNe1bfQX624ef45GhWb3S9HM3gvAJ7Qftm8iqnDQVcxwKHjmkV4hveKMTix4bTRhieVHi1oqU4QCVy4QPWpAAympuCP9dAoJFxSP6TNBLY9vPKLazsg7XcFov6UuLWsEaxJ5SomCpDx181mEgW2qTug5oQbrJwExbD9CMgXHLVDE2QgLoQMmgsrPevX57dH715NXC2uY6vo2mYCzRY4KuDRUsrkuYCkewL8q2oK1BEDVvi3Sg8pbC9QYQ5mMiHf8uxiHxTAmPedv8" + const expectedDid = "did:key:z4MXj1wBzi9jUstyNvmiK5WLRRL4rr9UvzPxhry1CudCLKWLyMbP1WoTwDfttBTpxDKf5hAJEjqNbeYx2EEvrJmSWHAu7TJRPTrE3QodbMfRvRNRDyYvaN1FSQus2ziS1rWXwAi5Gpc16bY3JwjyLCPJLfdRWHZhRXiay5FWEkfoSKy6aftnzAvqNkKBg2AxgzGMinR6d1WiH4w5mEXFtUeZkeo4uwtRTd8rD9BoVaHVkGwJkksDybE23CsBNXiNfbweFVRcwfTMhcQsTsYhUWDcSC6QE3zt9h4Rsrj7XRYdwYSK5bc1qFRsg5HULKBp2uZ1gcayiW2FqHFcMRjBieC4LnSMSD1AZB1WUncVRbPpVkn1UGhCU" + const result = did.publicKeyToDid(pubkey, "rsa") + expect(result).toEqual(expectedDid) + }) + + it("handles Ed25519 Keys", async () => { + const pubkey = "Hv+AVRD2WUjUFOsSNbsmrp9fokuwrUnjBcr92f0kxw4=" + const expectedDid = "did:key:z6MkgYGF3thn8k1Fv4p4dWXKtsXCnLH7q9yw4QgNPULDmDKB" + const result = did.publicKeyToDid(pubkey, "ed25519") + expect(result).toEqual(expectedDid) + }) + + it("handles BLS12-381 Keys", async () => { + const pubkey = "Hv+AVRD2WUjUFOsSNbsmrp9fokuwrUnjBcr92f0kxw4=" + const expectedDid = "did:key:z6HpYD1br5P4QVh5rjRGAkBfKMWes44uhKmKdJ6dN2Nm9gHK" + const result = did.publicKeyToDid(pubkey, "bls12-381") + expect(result).toEqual(expectedDid) + }) + + it("handles NIST P-256 Keys", async () => { + const pubkey = "BEgg6smRlRQXKqzJHu0w5/nHcy7zYNxFXwTKUyyCOoDMztUvQn5VZg668v4MecHGUZTeHkaZhSraK7RtJuXfWp4=" + const expectedDid = "did:key:zDnaeVHXWgKUpJFc8AUfRDuRWfsAQejmZu5HrERh41iGumZVu" + const result = did.publicKeyToDid(pubkey, "p256") + expect(result).toEqual(expectedDid) + }) + +}) + +describe("didToPublicKey", () => { + + it("handles old RSA Keys", async () => { + const toDecode = "did:key:z13V3Sog2YaUKhdGCmgx9UZuW1o1ShFJYc6DvGYe7NTt689NoL2RtpVs65Zw899YrTN9WuxdEEDm54YxWuQHQvcKfkZwa8HTgokHxGDPEmNLhvh69zUMEP4zjuARQ3T8bMUumkSLGpxNe1bfQX624ef45GhWb3S9HM3gvAJ7Qftm8iqnDQVcxwKHjmkV4hveKMTix4bTRhieVHi1oqU4QCVy4QPWpAAympuCP9dAoJFxSP6TNBLY9vPKLazsg7XcFov6UuLWsEaxJ5SomCpDx181mEgW2qTug5oQbrJwExbD9CMgXHLVDE2QgLoQMmgsrPevX57dH715NXC2uY6vo2mYCzRY4KuDRUsrkuYCkewL8q2oK1BEDVvi3Sg8pbC9QYQ5mMiHf8uxiHxTAmPedv8" + const expectedKey = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAnzyis1ZjfNB0bBgKFMSvvkTtwlvBsaJq7S5wA+kzeVOVpVWwkWdVha4s38XM/pa/yr47av7+z3VTmvDRyAHcaT92whREFpLv9cj5lTeJSibyr/Mrm/YtjCZVWgaOYIhwrXwKLqPr/11inWsAkfIytvHWTxZYEcXLgAXFuUuaS3uF9gEiNQwzGTU1v0FqkqTBr4B8nW3HCN47XUu0t8Y0e+lf4s4OxQawWD79J9/5d3Ry0vbV3Am1FtGJiJvOwRsIfVChDpYStTcHTCMqtvWbV6L11BWkpzGXSW4Hv43qa+GSYOD2QU68Mb59oSk2OB+BtOLpJofmbGEGgvmwyCI9MwIDAQAB" + const { publicKey, type } = did.didToPublicKey(toDecode) + expect(publicKey).toEqual(expectedKey) + expect(type).toEqual("rsa") + }) + + it("handles standardized RSA Keys", async () => { + const toDecode = "did:key:z4MXj1wBzi9jUstyNvmiK5WLRRL4rr9UvzPxhry1CudCLKWLyMbP1WoTwDfttBTpxDKf5hAJEjqNbeYx2EEvrJmSWHAu7TJRPTrE3QodbMfRvRNRDyYvaN1FSQus2ziS1rWXwAi5Gpc16bY3JwjyLCPJLfdRWHZhRXiay5FWEkfoSKy6aftnzAvqNkKBg2AxgzGMinR6d1WiH4w5mEXFtUeZkeo4uwtRTd8rD9BoVaHVkGwJkksDybE23CsBNXiNfbweFVRcwfTMhcQsTsYhUWDcSC6QE3zt9h4Rsrj7XRYdwYSK5bc1qFRsg5HULKBp2uZ1gcayiW2FqHFcMRjBieC4LnSMSD1AZB1WUncVRbPpVkn1UGhCU" + const expectedKey = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAnzyis1ZjfNB0bBgKFMSvvkTtwlvBsaJq7S5wA+kzeVOVpVWwkWdVha4s38XM/pa/yr47av7+z3VTmvDRyAHcaT92whREFpLv9cj5lTeJSibyr/Mrm/YtjCZVWgaOYIhwrXwKLqPr/11inWsAkfIytvHWTxZYEcXLgAXFuUuaS3uF9gEiNQwzGTU1v0FqkqTBr4B8nW3HCN47XUu0t8Y0e+lf4s4OxQawWD79J9/5d3Ry0vbV3Am1FtGJiJvOwRsIfVChDpYStTcHTCMqtvWbV6L11BWkpzGXSW4Hv43qa+GSYOD2QU68Mb59oSk2OB+BtOLpJofmbGEGgvmwyCI9MwIDAQAB" + const { publicKey, type } = did.didToPublicKey(toDecode) + expect(publicKey).toEqual(expectedKey) + expect(type).toEqual("rsa") + }) + + it("handles Ed25519 Keys", async () => { + const toDecode = "did:key:z6MkgYGF3thn8k1Fv4p4dWXKtsXCnLH7q9yw4QgNPULDmDKB" + const expectedKey = "Hv+AVRD2WUjUFOsSNbsmrp9fokuwrUnjBcr92f0kxw4=" + const { publicKey, type } = did.didToPublicKey(toDecode) + expect(publicKey).toEqual(expectedKey) + expect(type).toEqual("ed25519") + }) + + it("handles BLS12-381 Keys", async () => { + const toDecode = "did:key:z6HpYD1br5P4QVh5rjRGAkBfKMWes44uhKmKdJ6dN2Nm9gHK" + const expectedKey = "Hv+AVRD2WUjUFOsSNbsmrp9fokuwrUnjBcr92f0kxw4=" + const { publicKey, type } = did.didToPublicKey(toDecode) + expect(publicKey).toEqual(expectedKey) + expect(type).toEqual("bls12-381") + }) + + it("handles NIST P-256 Keys", async () => { + const toDecode = "did:key:zDnaeVHXWgKUpJFc8AUfRDuRWfsAQejmZu5HrERh41iGumZVu" + const expectedKey = "BEgg6smRlRQXKqzJHu0w5/nHcy7zYNxFXwTKUyyCOoDMztUvQn5VZg668v4MecHGUZTeHkaZhSraK7RtJuXfWp4=" + const { publicKey, type } = did.didToPublicKey(toDecode) + expect(publicKey).toEqual(expectedKey) + expect(type).toEqual("p256") + }) + +}) diff --git a/tests-copy/ecdsa.test.ts b/tests-copy/ecdsa.test.ts new file mode 100644 index 0000000..3d74917 --- /dev/null +++ b/tests-copy/ecdsa.test.ts @@ -0,0 +1,34 @@ +import * as did from "../src/did" +import ECDSAKeyPair from "../src/keypair/ecdsa" + + +describe("ecdsa", () => { + + let p256Keypair: ECDSAKeyPair + + let p256Signature: Uint8Array + + const data = new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8, 9]) + + it("creates an ecdsa keypairs with different curves", async () => { + p256Keypair = await ECDSAKeyPair.create() + }) + + it("returns a publicKeyStr and did: curve P-256", () => { + const keyDid = p256Keypair.did() + const publicKey = p256Keypair.publicKeyStr() + const transformed = did.didToPublicKey(keyDid) + expect(transformed.publicKey).toEqual(publicKey) + expect(transformed.type).toEqual("p256") + }) + + it("signs data: curve P-256", async () => { + p256Signature = await p256Keypair.sign(data) + }) + + it("can verify signature: P-256", async () => { + const isValid = await did.verifySignature(data, p256Signature, p256Keypair.did()) + expect(isValid).toBeTruthy() + }) + +}) diff --git a/tests-copy/ed25519.test.ts b/tests-copy/ed25519.test.ts new file mode 100644 index 0000000..65769d9 --- /dev/null +++ b/tests-copy/ed25519.test.ts @@ -0,0 +1,31 @@ +import * as did from "../src/did" +import EdwardsKey from "../src/keypair/ed25519" + +describe("ed25519", () => { + + let keypair: EdwardsKey + let signature: Uint8Array + const data = new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8, 9]) + + it("creates an edwards curve keypair", async () => { + keypair = await EdwardsKey.create() + }) + + it("returns a publicKeyStr and did", () => { + const publicKey = keypair.publicKeyStr() + const keyDid = keypair.did() + const transformed = did.didToPublicKey(keyDid) + expect(transformed.publicKey).toEqual(publicKey) + expect(transformed.type).toEqual("ed25519") + }) + + it("signs data", async () => { + signature = await keypair.sign(data) + }) + + it("can verify signature", async () => { + const isValid = await did.verifySignature(data, signature, keypair.did()) + expect(isValid).toEqual(true) + }) + +}) diff --git a/tests-copy/fixtures.ts b/tests-copy/fixtures.ts new file mode 100644 index 0000000..fbdeeb1 --- /dev/null +++ b/tests-copy/fixtures.ts @@ -0,0 +1,19 @@ +import { EdKeypair } from "../src/keypair/ed25519" + + +/** did:key:z6Mkk89bC3JrVqKie71YEcc5M1SMVxuCgNx6zLZ8SYJsxALi */ +export const alice = EdKeypair.fromSecretKey("U+bzp2GaFQHso587iSFWPSeCzbSfn/CbNHEz7ilKRZ1UQMmMS7qq4UhTzKn3X9Nj/4xgrwa+UqhMOeo4Ki8JUw==") + +/** did:key:z6MkffDZCkCTWreg8868fG1FGFogcJj5X6PY93pPcWDn9bob */ +export const bob = EdKeypair.fromSecretKey("G4+QCX1b3a45IzQsQd4gFMMe0UB1UOx9bCsh8uOiKLER69eAvVXvc8P2yc4Iig42Bv7JD2zJxhyFALyTKBHipg==") + +/** did:key:z6MktafZTREjJkvV5mfJxcLpNBoVPwDLhTuMg9ng7dY4zMAL */ +export const mallory = EdKeypair.fromSecretKey("LR9AL2MYkMARuvmV3MJV8sKvbSOdBtpggFCW8K62oZDR6UViSXdSV/dDcD8S9xVjS61vh62JITx7qmLgfQUSZQ==") + + +export function didToName(did: string) { + if (did === alice.did()) return "alice" + if (did === bob.did()) return "bob" + if (did === mallory.did()) return "mallory" + return did +} diff --git a/tests-copy/rsa.test.ts b/tests-copy/rsa.test.ts new file mode 100644 index 0000000..70950ce --- /dev/null +++ b/tests-copy/rsa.test.ts @@ -0,0 +1,96 @@ +import * as fc from "fast-check" +import * as uint8arrays from "uint8arrays" +import * as did from "../src/did" +import * as rsaCrypto from "../src/crypto/rsa" +import RSAKeypair from "../src/keypair/rsa" + + +describe("rsa", () => { + + let keypair: RSAKeypair + let signature: Uint8Array + const data = new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8, 9]) + + it("creates an rsa keypair", async () => { + keypair = await RSAKeypair.create() + }) + + it("returns a publicKeyStr and did", () => { + const publicKey = keypair.publicKeyStr() + const keyDid = keypair.did() + const transformed = did.didToPublicKey(keyDid) + expect(transformed.publicKey).toEqual(publicKey) + expect(transformed.type).toEqual("rsa") + }) + + it("signs data", async () => { + signature = await keypair.sign(data) + }) + + it("can verify signature", async () => { + const isValid = await did.verifySignature(data, signature, keypair.did()) + expect(isValid).toEqual(true) + }) + +}) + +describe("ASN", () => { + + describe("asn1DERLengthEncode/Decode", () => { + + it("works with simple examples", () => { + // 82 - bigger than 127 & 2 length octets + // 01 - 1 * 256^1 + + // b3 - 179 * 256^0 + // = 435 + // Example from https://en.wikipedia.org/wiki/X.690#Length_octets + expect(uint8arrays.toString(rsaCrypto.asn1DERLengthEncode(435), "hex")).toEqual("8201b3") + }) + + it("round-trips", () => { + fc.assert(fc.property(fc.nat(), n => { + expect(rsaCrypto.asn1DERLengthDecode(rsaCrypto.asn1DERLengthEncode(n))).toEqual(n) + })) + }) + + it("encodes in a simple way until 127", () => { + for (let i = 0; i < 128; i++) { + expect(`Encoded ${i}: ${uint8arrays.toString(rsaCrypto.asn1DERLengthEncode(i), "hex")}`) + .toEqual(`Encoded ${i}: ${uint8arrays.toString(new Uint8Array([i]), "hex")}`) + } + }) + }) + + describe("SPKI/PKCS1 conversion", () => { + + it("round trips with webcrypto-generated spki keys", async () => { + await fc.assert( + fc.asyncProperty( + fc.constantFrom(1024, 2048, 3072, 4096), + async size => { + const key = await rsaCrypto.generateKeypair(size) + if (key.publicKey == null) { + expect(key.publicKey).toBeDefined() + throw "public key is undefined" + } + const spki = await rsaCrypto.exportKey(key.publicKey) + const converted = + rsaCrypto.convertRSAPublicKeyToSubjectPublicKeyInfo( + rsaCrypto.convertSubjectPublicKeyInfoToRSAPublicKey( + spki + ) + ) + + // I find hex dumps the most readable when it comes to ASN1 + expect(uint8arrays.toString(converted, "hex")).toEqual(uint8arrays.toString(spki, "hex")) + } + ), + { + numRuns: 5, // unfortunately, generating rsa keys is quite slow. Let's try to reliably keep below the 5s timeout + examples: [[1024], [2048], [3072], [4096]], // ensure we're testing each variant at least once + } + ) + }) + + }) +}) diff --git a/tests-copy/semver.test.ts b/tests-copy/semver.test.ts new file mode 100644 index 0000000..70667a8 --- /dev/null +++ b/tests-copy/semver.test.ts @@ -0,0 +1,89 @@ +import * as fc from "fast-check" + +import { SemVer, parse, format, compare, GT, EQ, LT } from "../src/semver" + +const arbitrarySemVer = fc.record({ + major: fc.nat(100), + minor: fc.nat(100), + patch: fc.nat(100) +}) + +const toNum = (semVer: SemVer) => + 100 * 100 * semVer.major + + 100 * semVer.minor + + semVer.patch + +describe("SemVer", () => { + + it("has the property parse(format(x)) == x", () => { + fc.property(arbitrarySemVer, semVer => { + expect(parse(format(semVer))).toEqual(semVer) + }) + }) + + it("has the property compare(x, y) == flip(compare(y, x))", () => { + fc.property(arbitrarySemVer, arbitrarySemVer, (l, r) => { + expect(compare(l, r)).toEqual(flip(compare(r, l))) + }) + }) + + it("has the property compare(x, x) == EQ", () => { + fc.property(arbitrarySemVer, semVer => { + expect(compare(semVer, semVer)).toEqual(EQ) + }) + }) + + it("has the property compare(x, y) == compareNum(toNum(x), toNum(y))", () => { + fc.property(arbitrarySemVer, arbitrarySemVer, (x, y) => { + expect(compare(x, y)).toEqual(compareNum(toNum(x), toNum(y))) + }) + }) + + describe("parse", () => { + + it("parses 0.1.2", () => { + expect(parse("0.1.2")).toEqual({ + major: 0, + minor: 1, + patch: 2, + }) + }) + + it("parses 11.22.33", () => { + expect(parse("11.22.33")).toEqual({ + major: 11, + minor: 22, + patch: 33, + }) + }) + + it("doesn't parse negative integers", () => { + expect(parse("0.-1.1")).toEqual(null) + + }) + + it("doesn't parse octal integers", () => { + expect(parse("0.010.0")).toEqual(null) + }) + + it("doesn't parse scientific integers", () => { + expect(parse("1e5.0.0")).toEqual(null) + }) + + }) + +}) + +function flip(sign: GT | EQ | LT): GT | EQ | LT { + switch (sign) { + case GT: return LT + case LT: return GT + default: return EQ + } +} + +function compareNum(l: number, r: number): LT | EQ | GT { + if (l < r) return LT + if (l > r) return GT + return EQ +} diff --git a/tests-copy/store.test.ts b/tests-copy/store.test.ts new file mode 100644 index 0000000..da7864f --- /dev/null +++ b/tests-copy/store.test.ts @@ -0,0 +1,142 @@ +import * as token from "../src/token" +import { Store } from "../src/store" +import { Builder } from "../src/builder" +import { alice, bob, mallory } from "./fixtures" +import { wnfsCapability, wnfsPublicSemantics } from "./capability/wnfs" +import { Ucan } from "../src/types" +import { equalCanDelegate } from "../src/attenuation" +import { all } from "../src/util" + + +describe("Store.add", () => { + + it("makes added items retrievable with findByAudience", async () => { + const ucan = await Builder.create() + .issuedBy(alice) + .toAudience(bob.did()) + .withLifetimeInSeconds(30) + .build() + + const encoded = token.encode(ucan) + + const store = await Store.fromTokens(equalCanDelegate, []) + await store.add(ucan) + expect(encodeOrNull(store.findByAudience(ucan.payload.aud, find => token.encode(find) === encoded))).toEqual(encoded) + }) + + it("makes added items retrievable with findByAudience among multiple others", async () => { + const ucan = await Builder.create() + .issuedBy(alice) + .toAudience(bob.did()) + .withLifetimeInSeconds(30) + .build() + + const ucan2 = await Builder.create() + .issuedBy(alice) + .toAudience(mallory.did()) + .withLifetimeInSeconds(30) + .build() + + const encoded = token.encode(ucan) + const store = await Store.fromTokens(equalCanDelegate, []) + await store.add(ucan2) + await store.add(ucan) + expect(encodeOrNull(store.findByAudience(ucan.payload.aud, find => token.encode(find) === encoded))).toEqual(encoded) + }) + + it("doesn't add items twice", async () => { + const ucan = await Builder.create() + .issuedBy(alice) + .toAudience(bob.did()) + .withLifetimeInSeconds(30) + .build() + + const store = await Store.fromTokens(equalCanDelegate, []) + await store.add(ucan) + await store.add(ucan) + expect(store.getByAudience(ucan.payload.aud)).toEqual([ ucan ]) + }) + +}) + +describe("Store.findByAudience", () => { + + it("only returns ucans with given audience", async () => { + const ucanBob = await Builder.create() + .issuedBy(alice) + .toAudience(bob.did()) + .withLifetimeInSeconds(30) + .build() + + const ucanAlice = await Builder.create() + .issuedBy(bob) + .toAudience(alice.did()) + .withLifetimeInSeconds(30) + .build() + + const store = await Store.fromTokens(equalCanDelegate, [ ucanBob, ucanAlice ].map(ucan => token.encode(ucan))) + expect(store.findByAudience(mallory.did(), () => true)).toEqual(null) + expect(encodeOrNull(store.findByAudience(bob.did(), () => true))).toEqual(token.encode(ucanBob)) + expect(encodeOrNull(store.findByAudience(alice.did(), () => true))).toEqual(token.encode(ucanAlice)) + }) + +}) + +describe("Store.findWithCapability", () => { + + it("finds ucans with more capabilities than the given", async () => { + const ucan = await Builder.create() + .issuedBy(alice) + .toAudience(bob.did()) + .withLifetimeInSeconds(30) + .claimCapability(wnfsCapability("alice.fission.name/public/", "SUPER_USER")) + .build() + + const store = await Store.fromTokens(wnfsPublicSemantics, [ token.encode(ucan) ]) + + const results = all(store.findWithCapability( + bob.did(), + wnfsCapability("alice.fission.name/public/Apps", "OVERWRITE"), + alice.did() + )) + + if (!("capability" in results[0])) { + throw "no capability" + } + + expect(encodeOrNull(results[0]?.ucan)).toEqual(token.encode(ucan)) + }) + + it("reports an error if the capability can't be found with given audience", async () => { + const ucanBob = await Builder.create() + .issuedBy(alice) + .toAudience(bob.did()) + .withLifetimeInSeconds(30) + .claimCapability(wnfsCapability("alice.fission.name/public/", "SUPER_USER")) + .build() + + const ucanAlice = await Builder.create() + .issuedBy(alice) + .toAudience(bob.did()) + .withLifetimeInSeconds(30) + .build() + + const store = await Store.fromTokens(wnfsPublicSemantics, [ token.encode(ucanAlice), token.encode(ucanBob) ]) + + const results = all(store.findWithCapability( + alice.did(), + wnfsCapability("alice.fission.name/public/Apps", "OVERWRITE"), + alice.did() + )) + + expect(results).toEqual([]) + }) + +}) + +function encodeOrNull(ucan: Ucan | null): string { + if (ucan == null) { + return "null" + } + return token.encode(ucan) +} diff --git a/tests-copy/token.test.ts b/tests-copy/token.test.ts new file mode 100644 index 0000000..b625a78 --- /dev/null +++ b/tests-copy/token.test.ts @@ -0,0 +1,175 @@ +import * as uint8arrays from "uint8arrays" + +import * as capability from "../src/capability" +import * as token from "../src/token" +import { verifySignatureUtf8 } from "../src/did" +import { alice, bob } from "./fixtures" + + +// COMPOSING + + +describe("token.build", () => { + + it("can build payloads without nbf", () => { + const payload = token.buildPayload({ + issuer: alice.did(), + audience: bob.did(), + }) + expect(payload.nbf).not.toBeDefined() + }) + + it("builds payloads that expire in the future", () => { + const payload = token.buildPayload({ + issuer: alice.did(), + audience: bob.did(), + + lifetimeInSeconds: 30, + }) + expect(payload.exp).toBeGreaterThan(Date.now() / 1000) + }) + + it("throws when enclosing tokens with an invalid key type", async () => { + await expect(() => { + const payload = token.buildPayload({ + issuer: alice.did(), + audience: bob.did(), + }) + + return token.sign( + payload, + "rsa", + data => alice.sign(data) + ) + }).rejects.toBeDefined() + }) + +}) + + + +// ENCODING + + +describe("token.encodePayload", () => { + + it("encodes capabilities", () => { + const encodedCaps = { + with: "wnfs://boris.fission.name/public/photos/", + can: "crud/DELETE" + } + + const payload = token.buildPayload({ + issuer: alice.did(), + audience: bob.did(), + capabilities: [ capability.parse(encodedCaps) ] + }) + + const encoded = token.encodePayload(payload) + const decodedString = uint8arrays.toString( + uint8arrays.fromString(encoded, "base64url"), + "utf8" + ) + + const decoded = JSON.parse(decodedString) + + expect( + JSON.stringify(decoded.att) + ).toEqual( + JSON.stringify([ encodedCaps ]) + ) + }) + +}) + + + +// VALIDATION + + +describe("token.validate", () => { + async function makeUcan() { + return await token.build({ + audience: bob.did(), + issuer: alice, + capabilities: [ + { + "with": { scheme: "wnfs", hierPart: "//boris.fission.name/public/photos/" }, + "can": { namespace: "crud", segments: [ "DELETE" ] } + }, + { + "with": { scheme: "wnfs", hierPart: "//boris.fission.name/private/84MZ7aqwKn7sNiMGsSbaxsEa6EPnQLoKYbXByxNBrCEr" }, + "can": { namespace: "wnfs", segments: [ "APPEND" ] } + }, + { + "with": { scheme: "mailto", hierPart: "boris@fission.codes" }, + "can": { namespace: "msg", segments: [ "SEND" ] } + } + ] + }) + } + + it("round-trips with token.build", async () => { + const ucan = await makeUcan() + const parsedUcan = await token.validate(token.encode(ucan)) + expect(parsedUcan).toBeDefined() + }) + + it("throws with a bad audience", async () => { + const ucan = await makeUcan() + const badPayload = { + ...ucan.payload, + aud: "fakeaudience" + } + const badUcan = `${token.encodeHeader(ucan.header)}.${token.encodePayload(badPayload)}.${ucan.signature}` + await expect(() => token.validate(badUcan)).rejects.toBeDefined() + }) + + it("throws with a bad issuer", async () => { + const ucan = await makeUcan() + const badHeader = { + ...ucan.header, + alg: "RS256" + } + const badUcan = `${token.encodeHeader(badHeader)}.${token.encodePayload(ucan.payload)}.${ucan.signature}` + await expect(() => token.validate(badUcan)).rejects.toBeDefined() + }) + + it("identifies a ucan that is not active yet", async () => { + const ucan = await makeUcan() + const badUcan = { + ...ucan, + payload: { + ...ucan.payload, + nbf: 2637252774, + exp: 2637352774 + } + } + expect(token.isTooEarly(badUcan)).toBe(true) + }) + + it("identifies a ucan that has become active", async () => { + const ucan = await makeUcan() + const activeUcan = { + ...ucan, + payload: { + ...ucan.payload, + nbf: Math.floor(Date.now() / 1000), + lifetimeInSeonds: 30 + } + } + expect(token.isTooEarly(activeUcan)).toBe(false) + }) +}) + +describe("verifySignatureUtf8", () => { + + it("works with an example", async () => { + const [ header, payload, signature ] = token.encode(await token.build({ + issuer: alice, + audience: bob.did(), + })).split(".") + expect(await verifySignatureUtf8(`${header}.${payload}`, signature, alice.did())).toEqual(true) + }) + +}) diff --git a/tests-copy/verify.test.ts b/tests-copy/verify.test.ts new file mode 100644 index 0000000..ba91658 --- /dev/null +++ b/tests-copy/verify.test.ts @@ -0,0 +1,350 @@ +import * as token from "../src/token" +import * as capability from "../src/capability" +import { SUPERUSER } from "../src/capability/super-user" +import { verify } from "../src/verify" +import { emailCapability } from "./capability/email" +import { alice, bob, mallory } from "./fixtures" +import { REDELEGATE } from "../src/capability/ability" + + +describe("verify", () => { + + async function aliceEmailDelegationExample(expiration?: number) { + // alice -> bob, bob -> mallory + // alice delegates access to sending email as her to bob + // and bob delegates it further to mallory + const leafUcan = await token.build({ + issuer: alice, + audience: bob.did(), + expiration, + capabilities: [ emailCapability("alice@email.com") ] + }) + + const ucan = await token.build({ + issuer: bob, + audience: mallory.did(), + expiration, + capabilities: [ emailCapability("alice@email.com") ], + proofs: [ token.encode(leafUcan) ] + }) + + return token.encode(ucan) + } + + const alicesEmail = { + capability: emailCapability("alice@email.com"), + rootIssuer: alice.did(), + } + + it("verifies a delegation chain", async () => { + const ucan = await aliceEmailDelegationExample() + + const result = await verify(ucan, { + audience: mallory.did(), + requiredCapabilities: [ alicesEmail ] + }) + + if (result.ok === false) { + console.log(result.error) + } + + expect(result.ok).toEqual(true) + + if (!result.ok) return + + expect(result.value[ 0 ]?.rootIssuer).toEqual(alice.did()) + expect(result.value[ 0 ]?.capability).toEqual(emailCapability("alice@email.com")) + }) + + it("rejects an invalid escalation", async () => { + const ucan = await aliceEmailDelegationExample() + + const result = await verify(ucan, { + audience: mallory.did(), + requiredCapabilities: [ { + capability: { + ...emailCapability("alice@email.com"), + can: SUPERUSER, + }, + rootIssuer: alice.did() + } ] + }) + + expect(result.ok).toEqual(false) + }) + + it("rejects for an invalid audience", async () => { + const ucan = await aliceEmailDelegationExample() + + const result = await verify(ucan, { + audience: bob.did(), + requiredCapabilities: [ alicesEmail ] + }) + + expect(result.ok).toEqual(false) + }) + + it("rejects for an invalid rootIssuer", async () => { + const ucan = await aliceEmailDelegationExample() + + const result = await verify(ucan, { + audience: mallory.did(), + requiredCapabilities: [ { + capability: emailCapability("alice@email.com"), + // an invalid rootIssuer + rootIssuer: "did:someone-else", + } ] + }) + + expect(result.ok).toEqual(false) + }) + + it("rejects for an expired capability", async () => { + // unix timestamp in seconds. Will be after + const nowInSeconds = Math.floor(Date.now() / 1000) + // expiry is in the past + const ucan = await aliceEmailDelegationExample(nowInSeconds - 60 * 60 * 24) + + const result = await verify(ucan, { + audience: mallory.did(), + requiredCapabilities: [ alicesEmail ] + }) + + expect(result.ok).toEqual(false) + }) + + it("supports redelegation with a `prf:*` capability", async () => { + // alice -> bob + // alice delegates access to sending email as her to bob + // and bob delegates it further to mallory + const leafUcan = await token.build({ + issuer: alice, + audience: bob.did(), + capabilities: [ emailCapability("alice@email.com") ] + }) + + const ucan = await token.build({ + issuer: bob, + audience: mallory.did(), + capabilities: [ capability.prf(capability.superUser.SUPERUSER, REDELEGATE) ], + proofs: [ token.encode(leafUcan) ] + }) + + const result = await verify(token.encode(ucan), { + audience: mallory.did(), + requiredCapabilities: [ alicesEmail ] + }) + + expect(result.ok).toEqual(true) + + if (!result.ok) return + + expect(result.value[ 0 ]?.rootIssuer).toEqual(alice.did()) + expect(result.value[ 0 ]?.capability).toEqual(emailCapability("alice@email.com")) + }) + + it("supports redelegation with a `prf:1` capability", async () => { + // alice -> bob + // alice delegates access to sending email as her to bob + // and bob delegates it further to mallory + const leafUcanA = await token.build({ + issuer: alice, + audience: bob.did(), + capabilities: [ emailCapability("ignore-me@email.com") ] + }) + + const leafUcanB = await token.build({ + issuer: alice, + audience: bob.did(), + capabilities: [ emailCapability("alice@email.com") ] + }) + + const ucan = await token.build({ + issuer: bob, + audience: mallory.did(), + capabilities: [ capability.prf(1, REDELEGATE) ], + proofs: [ token.encode(leafUcanA), token.encode(leafUcanB) ] + }) + + const result = await verify(token.encode(ucan), { + audience: mallory.did(), + requiredCapabilities: [ alicesEmail ] + }) + + expect(result.ok).toEqual(true) + + if (!result.ok) return + + expect(result.value[ 0 ]?.rootIssuer).toEqual(alice.did()) + expect(result.value[ 0 ]?.capability).toEqual(emailCapability("alice@email.com")) + }) + + it("ignores other proofs not referred to by `prf:0`", async () => { + const leafUcanA = await token.build({ + issuer: alice, + audience: bob.did(), + capabilities: [ emailCapability("ignore-me@email.com") ] + }) + + const leafUcanB = await token.build({ + issuer: alice, + audience: bob.did(), + capabilities: [ emailCapability("alice@email.com") ] + }) + + const faultyUcan = await token.build({ + issuer: bob, + audience: mallory.did(), + capabilities: [ capability.prf(0, REDELEGATE) ], + proofs: [ token.encode(leafUcanA), token.encode(leafUcanB) ] + }) + + const result = await verify(token.encode(faultyUcan), { + audience: mallory.did(), + requiredCapabilities: [ alicesEmail ] + }) + + expect(result.ok).toEqual(false) + }) + + it("rejects an improper `prf` redelegation", async () => { + // alice -> bob + // alice delegates access to sending email as her to bob + // and bob delegates it further to mallory + const leafUcan = await token.build({ + issuer: alice, + audience: bob.did(), + capabilities: [ emailCapability("invalid@email.com") ] + }) + + const ucan = await token.build({ + issuer: bob, + audience: mallory.did(), + capabilities: [ capability.prf(capability.superUser.SUPERUSER, REDELEGATE) ], + proofs: [ token.encode(leafUcan) ] + }) + + const result = await verify(token.encode(ucan), { + audience: mallory.did(), + requiredCapabilities: [ alicesEmail ] + }) + + expect(result.ok).toEqual(false) + }) + + it("supports redelegation with a `my` capability", async () => { + // alice -> bob + // alice delegates access to sending email as her to bob + // and bob delegates it further to mallory + const leafUcan = await token.build({ + issuer: alice, + audience: bob.did(), + capabilities: [ capability.my(capability.superUser.SUPERUSER) ] + }) + + const ucan = await token.build({ + issuer: bob, + audience: mallory.did(), + capabilities: [ emailCapability("alice@email.com") ], + proofs: [ token.encode(leafUcan) ] + }) + + const result = await verify(token.encode(ucan), { + audience: mallory.did(), + requiredCapabilities: [ alicesEmail ], + }) + + expect(result.ok).toEqual(true) + + if (!result.ok) return + + expect(result.value[ 0 ]?.rootIssuer).toEqual(alice.did()) + }) + + it("supports redelegation with a `my` & `as` capability", async () => { + // alice -> bob, bob -> mallory, mallory -> "someone" + // alice delegates access to sending email as her to bob + // and bob delegates it further to mallory + const leafUcan = await token.build({ + issuer: alice, + audience: bob.did(), + capabilities: [ capability.my(capability.superUser.SUPERUSER) ] + }) + + const middleUcan = await token.build({ + issuer: bob, + audience: mallory.did(), + capabilities: [ capability.as(alice.did(), SUPERUSER) ], + proofs: [ token.encode(leafUcan) ] + }) + + const ucan = await token.build({ + issuer: mallory, + audience: "did:key:someone", + capabilities: [ emailCapability("alice@email.com") ], + proofs: [ token.encode(middleUcan) ] + }) + + const result = await verify(token.encode(ucan), { + audience: "did:key:someone", + requiredCapabilities: [ alicesEmail ] + }) + + expect(result.ok).toEqual(true) + + if (!result.ok) return + + expect(result.value[ 0 ]?.rootIssuer).toEqual(alice.did()) + }) + + it("rejects an improper `my` redelegation", async () => { + // alice -> bob + // alice delegates access to sending email as her to bob + // and bob delegates it further to mallory + const leafUcan = await token.build({ + issuer: alice, + audience: bob.did(), + capabilities: [] + }) + + const ucan = await token.build({ + issuer: bob, + audience: mallory.did(), + capabilities: [ emailCapability("alice@email.com") ], + proofs: [ token.encode(leafUcan) ] + }) + + const result = await verify(token.encode(ucan), { + audience: mallory.did(), + requiredCapabilities: [ alicesEmail ] + }) + + expect(result.ok).toEqual(false) + }) + + it("rejects an improper `as` redelegation - no `my`", async () => { + // alice -> bob + // alice delegates access to sending email as her to bob + // and bob delegates it further to mallory + const leafUcan = await token.build({ + issuer: alice, + audience: bob.did(), + capabilities: [ capability.as(bob.did(), SUPERUSER) ] + }) + + const ucan = await token.build({ + issuer: bob, + audience: mallory.did(), + capabilities: [ emailCapability("alice@email.com") ], + proofs: [ token.encode(leafUcan) ] + }) + + const result = await verify(token.encode(ucan), { + audience: mallory.did(), + requiredCapabilities: [ alicesEmail ] + }) + + expect(result.ok).toEqual(false) + }) + +}) diff --git a/tsconfig.eslint.json b/tsconfig.eslint.json new file mode 100644 index 0000000..e662081 --- /dev/null +++ b/tsconfig.eslint.json @@ -0,0 +1,5 @@ +{ + "extends": "./tsconfig.json", + "include": ["**/*.ts"], + "exclude": [] +} From 0060b8cd1494d30ccc7b3748478593d2dc7a612f Mon Sep 17 00:00:00 2001 From: dholms Date: Tue, 28 Jun 2022 14:49:04 -0500 Subject: [PATCH 03/25] fleshed out plugin api & got all tests running again --- packages/core/src/index.ts | 1 + packages/core/src/plugins.ts | 4 +-- packages/core/src/token.ts | 10 +++---- packages/core/tests/attenuation.test.ts | 3 +++ packages/core/tests/builder.test.ts | 3 +++ packages/core/tests/capability/wnfs.test.ts | 5 ++++ packages/core/tests/compatibility.test.ts | 3 +++ packages/core/tests/fixtures.ts | 2 -- packages/core/tests/setup.ts | 9 +++++++ packages/core/tests/store.test.ts | 3 +++ packages/core/tests/token.test.ts | 8 ++++++ packages/defaults/src/index.ts | 3 ++- packages/defaults/src/plugins.ts | 29 +++++++++++++++++++++ tests-copy/fixtures.ts | 1 - 14 files changed, 73 insertions(+), 11 deletions(-) create mode 100644 packages/core/tests/setup.ts create mode 100644 packages/defaults/src/plugins.ts diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index ffd4721..db0ee1e 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -4,6 +4,7 @@ export * from "./store.js" export * from "./token.js" export * from "./types.js" export * from "./verify.js" +export * from "./plugins.js" export * as capability from "./capability/index.js" diff --git a/packages/core/src/plugins.ts b/packages/core/src/plugins.ts index e87931d..5570d33 100644 --- a/packages/core/src/plugins.ts +++ b/packages/core/src/plugins.ts @@ -63,8 +63,8 @@ export const checkSignature = async (did: string, data: Uint8Array, sig: Uint8Ar throw new Error(`DID method not supported by plugins: ${did}`) } -export const loadPlugins = (plugins: Plugins): void => { - plugins = plugins +export const loadPlugins = (toLoad: Plugins): void => { + plugins = toLoad } export const hasPrefix = ( diff --git a/packages/core/src/token.ts b/packages/core/src/token.ts index baf6d68..c085bfc 100644 --- a/packages/core/src/token.ts +++ b/packages/core/src/token.ts @@ -121,6 +121,7 @@ export function buildPayload(params: { } } +// TODO remove keyType here? /** * Encloses a UCAN payload as to form a finalised UCAN. */ @@ -135,11 +136,10 @@ export async function sign( ucv: VERSION, } - // @TODO do we need to perform this?? // Issuer key type must match UCAN algorithm - // if (signPlugins.checkJwtAlg(payload.iss, jwtAlgorithm(keyType))) { - // throw new Error("The issuer's key type must match the given key type.") - // } + if (!plugins.checkIssuer(payload.iss, jwtAlgorithm(keyType))) { + throw new Error("The issuer's key type must match the given key type.") + } // Encode parts const encodedHeader = encodeHeader(header) @@ -333,7 +333,7 @@ export async function validate(encodedUcan: string, opts?: Partial { + beforeAll(loadTestPlugins) + it("works with a simple example", async () => { // alice -> bob, bob -> mallory // alice delegates access to sending email as her to bob diff --git a/packages/core/tests/builder.test.ts b/packages/core/tests/builder.test.ts index 144ec0f..b4bec59 100644 --- a/packages/core/tests/builder.test.ts +++ b/packages/core/tests/builder.test.ts @@ -6,9 +6,12 @@ import { EMAIL_SEMANTICS } from "./capability/email" import { alice, bob, mallory } from "./fixtures" import { delegationChains } from "../src/attenuation" import { first } from "../src/util" +import { loadTestPlugins } from "./setup.js" describe("Builder", () => { + + beforeAll(loadTestPlugins) it("builds with a simple example", async () => { const fact1 = { test: true } diff --git a/packages/core/tests/capability/wnfs.test.ts b/packages/core/tests/capability/wnfs.test.ts index 62ff800..390a0b0 100644 --- a/packages/core/tests/capability/wnfs.test.ts +++ b/packages/core/tests/capability/wnfs.test.ts @@ -3,12 +3,15 @@ import { Capability } from "../../src/capability" import { wnfsCapability, wnfsPrivateCapabilities, wnfsPublicCapabilities } from "./wnfs" import { alice, bob, mallory } from "../fixtures" +import { loadTestPlugins } from "../setup.js" import { all } from "../../src/util" describe("wnfs public capability", () => { + beforeAll(loadTestPlugins) + // @TODO undo it('passes', () => { expect(true) @@ -72,6 +75,8 @@ describe("wnfs public capability", () => { describe("wnfs private capability", () => { + beforeAll(loadTestPlugins) + it("works with a simple example", async () => { const { ucan } = await makeSimpleDelegation( [ wnfsCapability("//boris.fission.name/private/abc", "OVERWRITE") ], diff --git a/packages/core/tests/compatibility.test.ts b/packages/core/tests/compatibility.test.ts index fca9af9..7030df4 100644 --- a/packages/core/tests/compatibility.test.ts +++ b/packages/core/tests/compatibility.test.ts @@ -1,5 +1,6 @@ import * as uint8arrays from "uint8arrays" import * as token from "../src/token" +import { loadTestPlugins } from "./setup.js" const oldUcan = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsInVhdiI6IjEuMC4wIn0.eyJhdWQiOiJkaWQ6a2V5OnoxM1YzU29nMllhVUtoZEdDbWd4OVVadVcxbzFTaEZKWWM2RHZHWWU3TlR0Njg5Tm9MMXRrZUd3NGMydGFQa2dBdWloUjh0cmg2azg2VHRVaTNIR2ZrNEh1NDg3czNiTWY4V1MzWjJoU3VwRktiNmhnV3VwajFIRzhheUxRdDFmeWJSdThjTGdBMkNKanFRYm16YzRFOEFKU0tKeDNndVFYa2F4c3R2Um5RRGN1eDFkZzhVR1BRS3haN2lLeUFKWkFuQlcyWXJUM2o0TVQxdTJNcWZQWG9RYU01WFZQMk04clBFN0FCSEREOXdMbWlKdjkzUUFDRFR5MllnZkVSS3JualNWaTdFb3RNOFR3NHg3M1pNUXJEQnZRRW01Zm9tTWZVaTZVSmJUTmVaaldDTUJQYllNbXRKUDZQZlRpaWZYZG0zdXprVFg5NnExUkVFOExodkU2Rzg2cUR0Wjg5MzdFYUdXdXFpNkRHVDFvc2FRMUVnR3NFN3Jac2JSdDFLNnRXeTZpYktlNTlKZWtnTWFlNW9XNER2IiwiZXhwIjozMjc0NDE5MTQyMywiZmN0IjpbXSwiaXNzIjoiZGlkOmtleTp6MTNWM1NvZzJZYVVLaGRHQ21neDlVWnVXMW8xU2hGSlljNkR2R1llN05UdDY4OU5vTDJWanZBR2JXdTFrdmZWUWFyVTVWMXBTUnNjOWFwR2h2dDdaODJmUWg1QWE1NW41Zm0zZGs2SnFuTXczZGU4WG91dWZUV2Z1eHpEVkhrSFNGV0sxOW1SWWI4d205d1VwZkxtUWl4QVdtMndFWVZqU2dENEd6YzhVUDlDSjFxMkY4ZXlpVXViMThGbld4Y2djUWhqdXB3OTNxUlMzWDlXUDViemlSYjE4TTZ0Vm8zaUJ4ZUozb2lrRTNaa3RScEtTZDlkcHU5WWNXZFhoeDZDQmY5NTZ1UXhkTDZoTkppNmVMbmZ1eFY2NEhpZU1rZFVoTTJSeThRd3lqZjQ4ZnZWMVhFVU1zeEM5YWFjNEtCcGJONDJHR3U4UmFkRDU3cjZuMWFOc2IyTjU3RkNOYnFIMXVLdHhNTmVHZHJ2QWlUUGRzVjJBRmppczJvN243ajhMNW41YmJ4TFl4VThNVHB3QVphdFpkSiIsIm5iZiI6MTY0MDE5MTQ1NywicHJmIjoiZXlKaGJHY2lPaUpTVXpJMU5pSXNJblI1Y0NJNklrcFhWQ0lzSW5WaGRpSTZJakV1TUM0d0luMC5leUpoZFdRaU9pSmthV1E2YTJWNU9ub3hNMVl6VTI5bk1sbGhWVXRvWkVkRGJXZDRPVlZhZFZjeGJ6RlRhRVpLV1dNMlJIWkhXV1UzVGxSME5qZzVUbTlNTWxacWRrRkhZbGQxTVd0MlpsWlJZWEpWTlZZeGNGTlNjMk01WVhCSGFIWjBOMW80TW1aUmFEVkJZVFUxYmpWbWJUTmthelpLY1c1TmR6TmtaVGhZYjNWMVpsUlhablY0ZWtSV1NHdElVMFpYU3pFNWJWSlpZamgzYlRsM1ZYQm1URzFSYVhoQlYyMHlkMFZaVm1wVFowUTBSM3BqT0ZWUU9VTktNWEV5UmpobGVXbFZkV0l4T0VadVYzaGpaMk5SYUdwMWNIYzVNM0ZTVXpOWU9WZFFOV0o2YVZKaU1UaE5OblJXYnpOcFFuaGxTak52YVd0Rk0xcHJkRkp3UzFOa09XUndkVGxaWTFka1dHaDROa05DWmprMU5uVlJlR1JNTm1oT1NtazJaVXh1Wm5WNFZqWTBTR2xsVFd0a1ZXaE5NbEo1T0ZGM2VXcG1ORGhtZGxZeFdFVlZUWE40UXpsaFlXTTBTMEp3WWs0ME1rZEhkVGhTWVdSRU5UZHlObTR4WVU1ellqSk9OVGRHUTA1aWNVZ3hkVXQwZUUxT1pVZGtjblpCYVZSUVpITldNa0ZHYW1sek1tODNiamRxT0V3MWJqVmlZbmhNV1hoVk9FMVVjSGRCV21GMFdtUktJaXdpWlhod0lqb3pNamMwTkRFNU1UUXlNeXdpWm1OMElqcGJYU3dpYVhOeklqb2laR2xrT210bGVUcDZNVE5XTTFOdlp6SlpZVlZMYUdSSFEyMW5lRGxWV25WWE1XOHhVMmhHU2xsak5rUjJSMWxsTjA1VWREWTRPVTV2VERKaE5VcE9hMlI0VmpabWJYVm9WbU5SWkRkSVIycHhkRXBRYVc1WlZWQTRRMUp4Y21veVkyVm5hVTFyT1RKUlNIazJRbWRXT1hveVVGQnJWMkZZU0RkUlRsQmlRekphZEUxNWFXbGFjWGRLUkVOd05sZG9VbkZVUzJodVFtaENUbWQ1WkRkTFJuUTNjRkkyTkhCa1ZIQjZUbXRNUlZKNGFHNTNUVUZqZURKcVJGZFlOelpDVG5SS04xUTFWVXQ0TTIxcWRHWTBaak0wWjJwVGRUaHJkME5UY0V0alFuQTRWV2RwU0hkdllVSkhkREUxVkZjNVUzQlNXVkoxYUZKdk1tdEljVFZ5Y0ROTmRFSnFSa2QyVUdZeVRsTlpZbUUzTmxoSGJYcFhlVEZyZUZOelEySTVUSGhqTW5welEwdG1lSEF5ZUd0VVFqWmtPVVJDUlVwVE5sUnhXbFo1WkhKU05GWmFNVkE1ZFhJeGRGcHBlbk5qYWtWd1kzVlViV1EzV0VRemRYSjZVelpqY0RSdU1sZHdSbFZNYjNsMk5tOW5ibWxaZEVOSGFUVlVlbWxEY2pKT1FWRjNWMEZYY25CMldVMWllbVEyVmt0a2RUVmpaekZZUWxoTVZFNWhUQ0lzSW01aVppSTZNVFkwTURFNU1UTTJNeXdpY0hSaklqb2lVMVZRUlZKZlZWTkZVaUlzSW5Kell5STZJaW9pZlEuQ0k5SjlOLVhUZUxQNEM5WTktUl9TcEE1aE80dHdpNUQxNFpTR2lwUzdjNS1jTlJWTVItc285Z0JZMlQzSFNaTHFmQ2xyMEtlQVJicFk2TFBwSm1NRGQ1ODdvck1TVVRnMndqN043eUNVeksxSWhOazhQMkQ3RGVlSHNxQ1lsTVotdXpjMHBSbnFJb3dPTWl6MVFkbHZXaTZ0UHNxZkZVYnl4bEx1bXRHdjV1a1hqc1FZcmYzdko3aU5DMkJibWotMGhTV25wNTNBN01TQTllLWFXVGpLUWEwSkpXVVVhWG5XS19CNjRaa3NyTWRXdW5mVFNuSE9lR2o3MFRuSXhieVcxbFhodk5pcnhIUV90ZVlKZ2xIZTRBbldEQXdUa2dnaVotdkp0WUhsYnVwQkt4S1YtNm9OMTlXS3dUT3U3QnpPX2QyUHAtWVVyY1RSSS1KZ0F2NUpnIiwicHRjIjoiU1VQRVJfVVNFUiIsInJzYyI6IioifQ.CRLB4gBBHhnsbfUhLALiCfo6mHnHSlEUczyZsWhh9TNjv9UxdgvsSWQsehGIT4XR0jQeYZo2OhasEVaF-Gtt_qqtUQIrducKngd0qzmpfjVbicsQPKVdJjlcTwm9dqhLSEtL195El0oucLzYdqMEZMf-txEmyhCd_Q8CaNExhAnwN32v1salnO6vrAw33ZJID7ZaFmBleoGUXBHQwnkv9_m_P6Fh-UGIKjaOuNmBkGXGn-4irm-eXrne2OPZCoPjhiaf0xTONu4ROrQQYykG8CppvsSXeiylOFY11Ot0sdAlHGSlyZk1_chJ3ud17K9S-CKWK9NtqiMNcUdQGFnNQQ" @@ -10,6 +11,8 @@ const payload = JSON.parse(uint8arrays.toString(uint8arrays.fromString(encodedPa describe("compatibility", () => { + beforeAll(loadTestPlugins) + it("allows parsing UCANs with 'uav: 1.0.0' into 'ucv: 0.3.0'", async () => { const ucan = await token.validate(oldUcan, { checkIsExpired: false, checkIsTooEarly: false, checkSignature: false }) expect(ucan).toEqual({ diff --git a/packages/core/tests/fixtures.ts b/packages/core/tests/fixtures.ts index 0402546..85faf7a 100644 --- a/packages/core/tests/fixtures.ts +++ b/packages/core/tests/fixtures.ts @@ -1,6 +1,4 @@ import { EdKeypair } from '@ucans/defaults' -// import { EdKeypair } from "../src/keypair/ed25519" - /** did:key:z6Mkk89bC3JrVqKie71YEcc5M1SMVxuCgNx6zLZ8SYJsxALi */ export const alice = EdKeypair.fromSecretKey("U+bzp2GaFQHso587iSFWPSeCzbSfn/CbNHEz7ilKRZ1UQMmMS7qq4UhTzKn3X9Nj/4xgrwa+UqhMOeo4Ki8JUw==") diff --git a/packages/core/tests/setup.ts b/packages/core/tests/setup.ts new file mode 100644 index 0000000..e2cbeda --- /dev/null +++ b/packages/core/tests/setup.ts @@ -0,0 +1,9 @@ +import { plugins } from '@ucans/defaults' +import { loadPlugins } from '../src/plugins' + +export const loadTestPlugins = () => { + loadPlugins({ + keys: [plugins.edwards, plugins.rsa, plugins.rsaOld], + methods: [], + }) +} \ No newline at end of file diff --git a/packages/core/tests/store.test.ts b/packages/core/tests/store.test.ts index da7864f..0e387a8 100644 --- a/packages/core/tests/store.test.ts +++ b/packages/core/tests/store.test.ts @@ -6,10 +6,13 @@ import { wnfsCapability, wnfsPublicSemantics } from "./capability/wnfs" import { Ucan } from "../src/types" import { equalCanDelegate } from "../src/attenuation" import { all } from "../src/util" +import { loadTestPlugins } from "./setup.js" describe("Store.add", () => { + beforeAll(loadTestPlugins) + it("makes added items retrievable with findByAudience", async () => { const ucan = await Builder.create() .issuedBy(alice) diff --git a/packages/core/tests/token.test.ts b/packages/core/tests/token.test.ts index fa6840d..4945215 100644 --- a/packages/core/tests/token.test.ts +++ b/packages/core/tests/token.test.ts @@ -3,6 +3,7 @@ import * as uint8arrays from "uint8arrays" import * as capability from "../src/capability" import * as token from "../src/token" // import { verifySignatureUtf8 } from "../src/did" +import { loadTestPlugins } from "./setup.js" import { alice, bob } from "./fixtures" @@ -11,6 +12,8 @@ import { alice, bob } from "./fixtures" describe("token.build", () => { + beforeAll(loadTestPlugins) + it("can build payloads without nbf", () => { const payload = token.buildPayload({ issuer: alice.did(), @@ -53,6 +56,8 @@ describe("token.build", () => { describe("token.encodePayload", () => { + beforeAll(loadTestPlugins) + it("encodes capabilities", () => { const encodedCaps = { with: "wnfs://boris.fission.name/public/photos/", @@ -88,6 +93,9 @@ describe("token.encodePayload", () => { describe("token.validate", () => { + + beforeAll(loadTestPlugins) + async function makeUcan() { return await token.build({ audience: bob.did(), diff --git a/packages/defaults/src/index.ts b/packages/defaults/src/index.ts index 77b233c..91b5622 100644 --- a/packages/defaults/src/index.ts +++ b/packages/defaults/src/index.ts @@ -1,4 +1,5 @@ export * from "./keypair/ed25519.js" export * from "./keypair/rsa.js" export * from "./did/index.js" -export * as keypair from "./keypair/index.js" \ No newline at end of file +export * as keypair from "./keypair/index.js" +export * as plugins from "./plugins.js" \ No newline at end of file diff --git a/packages/defaults/src/plugins.ts b/packages/defaults/src/plugins.ts new file mode 100644 index 0000000..8a7b6fa --- /dev/null +++ b/packages/defaults/src/plugins.ts @@ -0,0 +1,29 @@ +import { DidKeyPlugin } from '@ucans/core' +import { verifySignature } from './did' + +export const edwards: DidKeyPlugin = { + prefix: new Uint8Array([ 0xed, 0x01 ]), + jwtAlg: 'EdDSA', + checkSignature: async (did, data, sig): Promise => { + const isValid = await verifySignature(data, sig, did) + return isValid + } +} + +export const rsa: DidKeyPlugin = { + prefix: new Uint8Array([ 0x85, 0x24 ]), + jwtAlg: 'RS256', + checkSignature: async (did, data, sig): Promise => { + const isValid = await verifySignature(data, sig, did) + return isValid + } +} + +export const rsaOld: DidKeyPlugin = { + prefix: new Uint8Array([ 0x00, 0xf5, 0x02 ]), + jwtAlg: 'RS256', + checkSignature: async (did, data, sig): Promise => { + const isValid = await verifySignature(data, sig, did) + return isValid + } +} \ No newline at end of file diff --git a/tests-copy/fixtures.ts b/tests-copy/fixtures.ts index fbdeeb1..8d0c9fc 100644 --- a/tests-copy/fixtures.ts +++ b/tests-copy/fixtures.ts @@ -1,6 +1,5 @@ import { EdKeypair } from "../src/keypair/ed25519" - /** did:key:z6Mkk89bC3JrVqKie71YEcc5M1SMVxuCgNx6zLZ8SYJsxALi */ export const alice = EdKeypair.fromSecretKey("U+bzp2GaFQHso587iSFWPSeCzbSfn/CbNHEz7ilKRZ1UQMmMS7qq4UhTzKn3X9Nj/4xgrwa+UqhMOeo4Ki8JUw==") From 97bae0f6f9ee872f372dcb626d71a9e896f3e4a0 Mon Sep 17 00:00:00 2001 From: dholms Date: Tue, 28 Jun 2022 15:34:15 -0500 Subject: [PATCH 04/25] cleaned up plugins --- packages/core/package.json | 2 +- packages/core/src/plugins.ts | 6 +-- packages/core/tests/fixtures.ts | 2 +- packages/core/tests/setup.ts | 7 +-- packages/core/tsconfig.json | 3 +- packages/defaults/src/plugins.ts | 29 ----------- packages/{defaults => plugins}/.eslintrc.js | 0 packages/{defaults => plugins}/jest.config.js | 0 packages/{defaults => plugins}/package.json | 2 +- .../{defaults => plugins}/src/crypto/ecdsa.ts | 0 .../{defaults => plugins}/src/crypto/index.ts | 0 .../{defaults => plugins}/src/crypto/rsa.ts | 0 .../{defaults => plugins}/src/did/index.ts | 0 .../{defaults => plugins}/src/did/prefix.ts | 0 .../src/did/pubkey-compress.ts | 0 .../src/did/transformers.ts | 32 ++++++++---- .../src/did/validation.ts | 0 packages/{defaults => plugins}/src/index.ts | 2 +- .../{defaults => plugins}/src/keypair/base.ts | 0 .../src/keypair/ecdsa.ts | 0 .../src/keypair/ed25519.ts | 0 .../src/keypair/index.ts | 0 .../{defaults => plugins}/src/keypair/rsa.ts | 0 packages/plugins/src/plugins.ts | 52 +++++++++++++++++++ packages/{defaults => plugins}/src/types.ts | 0 .../tests/did-key.test.ts | 0 .../{defaults => plugins}/tests/did.test.ts | 0 .../{defaults => plugins}/tests/ecdsa.test.ts | 0 .../tests/ed25519.test.ts | 0 .../{defaults => plugins}/tests/rsa.test.ts | 0 .../tsconfig.eslint.json | 0 packages/{defaults => plugins}/tsconfig.json | 0 packages/ucans/package.json | 2 +- 33 files changed, 86 insertions(+), 53 deletions(-) delete mode 100644 packages/defaults/src/plugins.ts rename packages/{defaults => plugins}/.eslintrc.js (100%) rename packages/{defaults => plugins}/jest.config.js (100%) rename packages/{defaults => plugins}/package.json (98%) rename packages/{defaults => plugins}/src/crypto/ecdsa.ts (100%) rename packages/{defaults => plugins}/src/crypto/index.ts (100%) rename packages/{defaults => plugins}/src/crypto/rsa.ts (100%) rename packages/{defaults => plugins}/src/did/index.ts (100%) rename packages/{defaults => plugins}/src/did/prefix.ts (100%) rename packages/{defaults => plugins}/src/did/pubkey-compress.ts (100%) rename packages/{defaults => plugins}/src/did/transformers.ts (78%) rename packages/{defaults => plugins}/src/did/validation.ts (100%) rename packages/{defaults => plugins}/src/index.ts (79%) rename packages/{defaults => plugins}/src/keypair/base.ts (100%) rename packages/{defaults => plugins}/src/keypair/ecdsa.ts (100%) rename packages/{defaults => plugins}/src/keypair/ed25519.ts (100%) rename packages/{defaults => plugins}/src/keypair/index.ts (100%) rename packages/{defaults => plugins}/src/keypair/rsa.ts (100%) create mode 100644 packages/plugins/src/plugins.ts rename packages/{defaults => plugins}/src/types.ts (100%) rename packages/{defaults => plugins}/tests/did-key.test.ts (100%) rename packages/{defaults => plugins}/tests/did.test.ts (100%) rename packages/{defaults => plugins}/tests/ecdsa.test.ts (100%) rename packages/{defaults => plugins}/tests/ed25519.test.ts (100%) rename packages/{defaults => plugins}/tests/rsa.test.ts (100%) rename packages/{defaults => plugins}/tsconfig.eslint.json (100%) rename packages/{defaults => plugins}/tsconfig.json (100%) diff --git a/packages/core/package.json b/packages/core/package.json index 47a6b3f..72099d2 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -69,7 +69,7 @@ "@types/node": "^17.0.23", "@typescript-eslint/eslint-plugin": "^5.18.0", "@typescript-eslint/parser": "^5.18.0", - "@ucans/defaults": "*", + "@ucans/plugins": "*", "copyfiles": "^2.4.1", "eslint": "^8.12.0", "fast-check": "^2.24.0", diff --git a/packages/core/src/plugins.ts b/packages/core/src/plugins.ts index 5570d33..33cdebd 100644 --- a/packages/core/src/plugins.ts +++ b/packages/core/src/plugins.ts @@ -7,7 +7,7 @@ export type DidKeyPlugin = { } export type DidMethodPlugin = { - method: string + isMatch: (method: string, did: string) => boolean checkJwtAlg: (did: string, jwtAlg: string) => boolean checkSignature: (did: string, data: Uint8Array, sig: Uint8Array) => Promise } @@ -33,7 +33,7 @@ export const checkIssuer = (did: string, jwtAlg: string): boolean => { } } else { for (const didPlugin of plugins.methods) { - if(didMethod === didPlugin.method) { + if(didPlugin.isMatch(didMethod, did)) { return didPlugin.checkJwtAlg(did, jwtAlg) } } @@ -55,7 +55,7 @@ export const checkSignature = async (did: string, data: Uint8Array, sig: Uint8Ar } } else { for (const didPlugin of plugins.methods) { - if(didMethod === didPlugin.method) { + if(didPlugin.isMatch(didMethod, did)) { return didPlugin.checkSignature(did, data, sig) } } diff --git a/packages/core/tests/fixtures.ts b/packages/core/tests/fixtures.ts index 85faf7a..83708e1 100644 --- a/packages/core/tests/fixtures.ts +++ b/packages/core/tests/fixtures.ts @@ -1,4 +1,4 @@ -import { EdKeypair } from '@ucans/defaults' +import { EdKeypair } from '@ucans/plugins' /** did:key:z6Mkk89bC3JrVqKie71YEcc5M1SMVxuCgNx6zLZ8SYJsxALi */ export const alice = EdKeypair.fromSecretKey("U+bzp2GaFQHso587iSFWPSeCzbSfn/CbNHEz7ilKRZ1UQMmMS7qq4UhTzKn3X9Nj/4xgrwa+UqhMOeo4Ki8JUw==") diff --git a/packages/core/tests/setup.ts b/packages/core/tests/setup.ts index e2cbeda..769cd58 100644 --- a/packages/core/tests/setup.ts +++ b/packages/core/tests/setup.ts @@ -1,9 +1,6 @@ -import { plugins } from '@ucans/defaults' +import * as plugins from '@ucans/plugins' import { loadPlugins } from '../src/plugins' export const loadTestPlugins = () => { - loadPlugins({ - keys: [plugins.edwards, plugins.rsa, plugins.rsaOld], - methods: [], - }) + loadPlugins(plugins.defaults) } \ No newline at end of file diff --git a/packages/core/tsconfig.json b/packages/core/tsconfig.json index b59f12b..c60fdc6 100644 --- a/packages/core/tsconfig.json +++ b/packages/core/tsconfig.json @@ -4,7 +4,8 @@ "strict": true, "moduleResolution": "node", "esModuleInterop": true, - "noFallthroughCasesInSwitch": true + "noFallthroughCasesInSwitch": true, + "allowJs": true }, "include": [ "src/**/*" diff --git a/packages/defaults/src/plugins.ts b/packages/defaults/src/plugins.ts deleted file mode 100644 index 8a7b6fa..0000000 --- a/packages/defaults/src/plugins.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { DidKeyPlugin } from '@ucans/core' -import { verifySignature } from './did' - -export const edwards: DidKeyPlugin = { - prefix: new Uint8Array([ 0xed, 0x01 ]), - jwtAlg: 'EdDSA', - checkSignature: async (did, data, sig): Promise => { - const isValid = await verifySignature(data, sig, did) - return isValid - } -} - -export const rsa: DidKeyPlugin = { - prefix: new Uint8Array([ 0x85, 0x24 ]), - jwtAlg: 'RS256', - checkSignature: async (did, data, sig): Promise => { - const isValid = await verifySignature(data, sig, did) - return isValid - } -} - -export const rsaOld: DidKeyPlugin = { - prefix: new Uint8Array([ 0x00, 0xf5, 0x02 ]), - jwtAlg: 'RS256', - checkSignature: async (did, data, sig): Promise => { - const isValid = await verifySignature(data, sig, did) - return isValid - } -} \ No newline at end of file diff --git a/packages/defaults/.eslintrc.js b/packages/plugins/.eslintrc.js similarity index 100% rename from packages/defaults/.eslintrc.js rename to packages/plugins/.eslintrc.js diff --git a/packages/defaults/jest.config.js b/packages/plugins/jest.config.js similarity index 100% rename from packages/defaults/jest.config.js rename to packages/plugins/jest.config.js diff --git a/packages/defaults/package.json b/packages/plugins/package.json similarity index 98% rename from packages/defaults/package.json rename to packages/plugins/package.json index ac64e20..98abee9 100644 --- a/packages/defaults/package.json +++ b/packages/plugins/package.json @@ -1,5 +1,5 @@ { - "name": "@ucans/defaults", + "name": "@ucans/plugins", "version": "0.9.1", "description": "Typescript implementation of UCANs", "author": "Daniel Holmgren ", diff --git a/packages/defaults/src/crypto/ecdsa.ts b/packages/plugins/src/crypto/ecdsa.ts similarity index 100% rename from packages/defaults/src/crypto/ecdsa.ts rename to packages/plugins/src/crypto/ecdsa.ts diff --git a/packages/defaults/src/crypto/index.ts b/packages/plugins/src/crypto/index.ts similarity index 100% rename from packages/defaults/src/crypto/index.ts rename to packages/plugins/src/crypto/index.ts diff --git a/packages/defaults/src/crypto/rsa.ts b/packages/plugins/src/crypto/rsa.ts similarity index 100% rename from packages/defaults/src/crypto/rsa.ts rename to packages/plugins/src/crypto/rsa.ts diff --git a/packages/defaults/src/did/index.ts b/packages/plugins/src/did/index.ts similarity index 100% rename from packages/defaults/src/did/index.ts rename to packages/plugins/src/did/index.ts diff --git a/packages/defaults/src/did/prefix.ts b/packages/plugins/src/did/prefix.ts similarity index 100% rename from packages/defaults/src/did/prefix.ts rename to packages/plugins/src/did/prefix.ts diff --git a/packages/defaults/src/did/pubkey-compress.ts b/packages/plugins/src/did/pubkey-compress.ts similarity index 100% rename from packages/defaults/src/did/pubkey-compress.ts rename to packages/plugins/src/did/pubkey-compress.ts diff --git a/packages/defaults/src/did/transformers.ts b/packages/plugins/src/did/transformers.ts similarity index 78% rename from packages/defaults/src/did/transformers.ts rename to packages/plugins/src/did/transformers.ts index 07a51cd..e91fdd9 100644 --- a/packages/defaults/src/did/transformers.ts +++ b/packages/plugins/src/did/transformers.ts @@ -13,22 +13,34 @@ import { Encodings } from "@ucans/core" /** * Convert a DID (did:key) to a base64 public key. */ -export function didToPublicKey(did: string, encoding: Encodings = "base64pad"): { - publicKey: string - type: KeyType -} { - const { publicKey, type } = didToPublicKeyBytes(did) - return { - publicKey: uint8arrays.toString(publicKey, encoding), - type - } -} +// export function didToPublicKey(did: string, encoding: Encodings = "base64pad"): { +// publicKey: string +// type: KeyType +// } { +// const { publicKey, type } = didToPublicKeyBytes(did) +// return { +// publicKey: uint8arrays.toString(publicKey, encoding), +// type +// } +// } /** * Convert a DID (did:key) to the public key into bytes in SubjectPublicKeyInfo (spki) format. * * For consumption e.g. in the WebCrypto API. */ +export function keyBytesFromDid(did: string, expectedPrefix: Uint8Array): Uint8Array { + if (!did.startsWith(BASE58_DID_PREFIX)) { + throw new Error("Please use a base58-encoded DID formatted `did:key:z...`") + } + const didWithoutPrefix = did.slice(BASE58_DID_PREFIX.length) + const bytes = uint8arrays.fromString(didWithoutPrefix, "base58btc") + if(!hasPrefix(bytes, expectedPrefix)) { + throw new Error(`Expected prefix: ${expectedPrefix}`) + } + return bytes.slice(expectedPrefix.length) +} + export function didToPublicKeyBytes(did: string): { publicKey: Uint8Array type: KeyType diff --git a/packages/defaults/src/did/validation.ts b/packages/plugins/src/did/validation.ts similarity index 100% rename from packages/defaults/src/did/validation.ts rename to packages/plugins/src/did/validation.ts diff --git a/packages/defaults/src/index.ts b/packages/plugins/src/index.ts similarity index 79% rename from packages/defaults/src/index.ts rename to packages/plugins/src/index.ts index 91b5622..8602dc6 100644 --- a/packages/defaults/src/index.ts +++ b/packages/plugins/src/index.ts @@ -2,4 +2,4 @@ export * from "./keypair/ed25519.js" export * from "./keypair/rsa.js" export * from "./did/index.js" export * as keypair from "./keypair/index.js" -export * as plugins from "./plugins.js" \ No newline at end of file +export * from "./plugins.js" \ No newline at end of file diff --git a/packages/defaults/src/keypair/base.ts b/packages/plugins/src/keypair/base.ts similarity index 100% rename from packages/defaults/src/keypair/base.ts rename to packages/plugins/src/keypair/base.ts diff --git a/packages/defaults/src/keypair/ecdsa.ts b/packages/plugins/src/keypair/ecdsa.ts similarity index 100% rename from packages/defaults/src/keypair/ecdsa.ts rename to packages/plugins/src/keypair/ecdsa.ts diff --git a/packages/defaults/src/keypair/ed25519.ts b/packages/plugins/src/keypair/ed25519.ts similarity index 100% rename from packages/defaults/src/keypair/ed25519.ts rename to packages/plugins/src/keypair/ed25519.ts diff --git a/packages/defaults/src/keypair/index.ts b/packages/plugins/src/keypair/index.ts similarity index 100% rename from packages/defaults/src/keypair/index.ts rename to packages/plugins/src/keypair/index.ts diff --git a/packages/defaults/src/keypair/rsa.ts b/packages/plugins/src/keypair/rsa.ts similarity index 100% rename from packages/defaults/src/keypair/rsa.ts rename to packages/plugins/src/keypair/rsa.ts diff --git a/packages/plugins/src/plugins.ts b/packages/plugins/src/plugins.ts new file mode 100644 index 0000000..17685e2 --- /dev/null +++ b/packages/plugins/src/plugins.ts @@ -0,0 +1,52 @@ +import { DidKeyPlugin, Plugins } from '@ucans/core' +import { EDWARDS_DID_PREFIX, keyBytesFromDid, P256_DID_PREFIX, RSA_DID_PREFIX, RSA_DID_PREFIX_OLD } from './did' +import * as ed25519 from "@stablelib/ed25519" +import * as rsaCrypto from "./crypto/rsa.js" +import * as ecdsaCrypto from "./crypto/rsa.js" +import { decompressNistP256Pubkey } from "./did/pubkey-compress" + +export const edwards: DidKeyPlugin = { + prefix: EDWARDS_DID_PREFIX, + jwtAlg: 'EdDSA', + checkSignature: async (did, data, sig): Promise => { + const keyBytes = keyBytesFromDid(did, EDWARDS_DID_PREFIX) + return ed25519.verify(keyBytes, data, sig) + } +} + +export const rsa: DidKeyPlugin = { + prefix: RSA_DID_PREFIX, + jwtAlg: 'RS256', + checkSignature: async (did, data, sig): Promise => { + const keyBytes = keyBytesFromDid(did, RSA_DID_PREFIX) + const spkiBytes = rsaCrypto.convertRSAPublicKeyToSubjectPublicKeyInfo(keyBytes) + const isValid = await rsaCrypto.verify(data, sig, spkiBytes) + return isValid + } +} + +export const rsaOld: DidKeyPlugin = { + prefix: RSA_DID_PREFIX_OLD, + jwtAlg: 'RS256', + checkSignature: async (did, data, sig): Promise => { + const keyBytes = keyBytesFromDid(did, RSA_DID_PREFIX_OLD) + const isValid = await rsaCrypto.verify(data, sig, keyBytes) + return isValid + } +} + +export const p256: DidKeyPlugin = { + prefix: P256_DID_PREFIX, + jwtAlg: 'ES256', + checkSignature: async (did, data, sig): Promise => { + const keyBytes = keyBytesFromDid(did, P256_DID_PREFIX) + const decompressedKey = decompressNistP256Pubkey(keyBytes) + const isValid = await ecdsaCrypto.verify(data, sig, decompressedKey) + return isValid + } +} + +export const defaults: Plugins = { + keys: [edwards, p256, rsa, rsaOld], + methods: [], +} \ No newline at end of file diff --git a/packages/defaults/src/types.ts b/packages/plugins/src/types.ts similarity index 100% rename from packages/defaults/src/types.ts rename to packages/plugins/src/types.ts diff --git a/packages/defaults/tests/did-key.test.ts b/packages/plugins/tests/did-key.test.ts similarity index 100% rename from packages/defaults/tests/did-key.test.ts rename to packages/plugins/tests/did-key.test.ts diff --git a/packages/defaults/tests/did.test.ts b/packages/plugins/tests/did.test.ts similarity index 100% rename from packages/defaults/tests/did.test.ts rename to packages/plugins/tests/did.test.ts diff --git a/packages/defaults/tests/ecdsa.test.ts b/packages/plugins/tests/ecdsa.test.ts similarity index 100% rename from packages/defaults/tests/ecdsa.test.ts rename to packages/plugins/tests/ecdsa.test.ts diff --git a/packages/defaults/tests/ed25519.test.ts b/packages/plugins/tests/ed25519.test.ts similarity index 100% rename from packages/defaults/tests/ed25519.test.ts rename to packages/plugins/tests/ed25519.test.ts diff --git a/packages/defaults/tests/rsa.test.ts b/packages/plugins/tests/rsa.test.ts similarity index 100% rename from packages/defaults/tests/rsa.test.ts rename to packages/plugins/tests/rsa.test.ts diff --git a/packages/defaults/tsconfig.eslint.json b/packages/plugins/tsconfig.eslint.json similarity index 100% rename from packages/defaults/tsconfig.eslint.json rename to packages/plugins/tsconfig.eslint.json diff --git a/packages/defaults/tsconfig.json b/packages/plugins/tsconfig.json similarity index 100% rename from packages/defaults/tsconfig.json rename to packages/plugins/tsconfig.json diff --git a/packages/ucans/package.json b/packages/ucans/package.json index 8b0a422..9af35eb 100644 --- a/packages/ucans/package.json +++ b/packages/ucans/package.json @@ -66,7 +66,7 @@ ], "dependencies": { "@ucans/core": "*", - "@ucans/defaults": "*" + "@ucans/plugins": "*" }, "devDependencies": { "@types/jest": "^27.4.1", From 23c950bed13785cc4a69823be13387b8c3228cb0 Mon Sep 17 00:00:00 2001 From: dholms Date: Tue, 28 Jun 2022 21:40:56 -0500 Subject: [PATCH 05/25] refactored plugins library based on cryptosystem --- packages/core/src/did.ts | 109 ++++++++++ packages/core/src/plugins.ts | 7 +- packages/core/src/token.ts | 4 +- packages/core/src/types.ts | 3 - packages/plugins/src/crypto/ecdsa.ts | 90 --------- packages/plugins/src/crypto/index.ts | 2 - packages/plugins/src/default-plugins.ts | 9 + packages/plugins/src/did/index.ts | 3 - packages/plugins/src/did/prefix.ts | 104 ---------- packages/plugins/src/did/pubkey-compress.ts | 78 -------- packages/plugins/src/did/transformers.ts | 118 ----------- packages/plugins/src/did/validation.ts | 44 ----- packages/plugins/src/ed25519/crypto.ts | 10 + .../ed25519.ts => ed25519/keypair.ts} | 17 +- packages/plugins/src/ed25519/plugin.ts | 15 ++ packages/plugins/src/index.ts | 17 +- packages/plugins/src/keypair/base.ts | 32 --- packages/plugins/src/keypair/index.ts | 4 - packages/plugins/src/p256/crypto.ts | 187 ++++++++++++++++++ .../src/{keypair/ecdsa.ts => p256/keypair.ts} | 30 +-- packages/plugins/src/p256/plugin.ts | 11 ++ packages/plugins/src/plugins.ts | 52 ----- packages/plugins/src/prefixes.ts | 24 +++ .../src/{crypto/rsa.ts => rsa/crypto.ts} | 34 +++- packages/plugins/src/rsa/index.ts | 0 .../src/{keypair/rsa.ts => rsa/keypair.ts} | 22 ++- packages/plugins/src/rsa/plugin.ts | 20 ++ packages/plugins/src/util.ts | 32 +++ 28 files changed, 515 insertions(+), 563 deletions(-) create mode 100644 packages/core/src/did.ts delete mode 100644 packages/plugins/src/crypto/ecdsa.ts delete mode 100644 packages/plugins/src/crypto/index.ts create mode 100644 packages/plugins/src/default-plugins.ts delete mode 100644 packages/plugins/src/did/index.ts delete mode 100644 packages/plugins/src/did/prefix.ts delete mode 100644 packages/plugins/src/did/pubkey-compress.ts delete mode 100644 packages/plugins/src/did/transformers.ts delete mode 100644 packages/plugins/src/did/validation.ts create mode 100644 packages/plugins/src/ed25519/crypto.ts rename packages/plugins/src/{keypair/ed25519.ts => ed25519/keypair.ts} (76%) create mode 100644 packages/plugins/src/ed25519/plugin.ts delete mode 100644 packages/plugins/src/keypair/base.ts delete mode 100644 packages/plugins/src/keypair/index.ts create mode 100644 packages/plugins/src/p256/crypto.ts rename packages/plugins/src/{keypair/ecdsa.ts => p256/keypair.ts} (67%) create mode 100644 packages/plugins/src/p256/plugin.ts delete mode 100644 packages/plugins/src/plugins.ts create mode 100644 packages/plugins/src/prefixes.ts rename packages/plugins/src/{crypto/rsa.ts => rsa/crypto.ts} (79%) create mode 100644 packages/plugins/src/rsa/index.ts rename packages/plugins/src/{keypair/rsa.ts => rsa/keypair.ts} (65%) create mode 100644 packages/plugins/src/rsa/plugin.ts create mode 100644 packages/plugins/src/util.ts diff --git a/packages/core/src/did.ts b/packages/core/src/did.ts new file mode 100644 index 0000000..109e195 --- /dev/null +++ b/packages/core/src/did.ts @@ -0,0 +1,109 @@ +// import * as uint8arrays from "uint8arrays" + +// import * as compression from "../p256/pubkey-compress.js" +// import * as rsa from "../crypto/rsa.js" +// import { BASE58_DID_PREFIX, RSA_DID_PREFIX_OLD, magicBytes, parseMagicBytes, hasPrefix } from "./prefix.js" +// import { KeyType } from "../types.js" +// import { Encodings } from "@ucans/core" + + +// // DID → PUBLIC KEY + + +// /** +// * Convert a DID (did:key) to a base64 public key. +// */ +// // export function didToPublicKey(did: string, encoding: Encodings = "base64pad"): { +// // publicKey: string +// // type: KeyType +// // } { +// // const { publicKey, type } = didToPublicKeyBytes(did) +// // return { +// // publicKey: uint8arrays.toString(publicKey, encoding), +// // type +// // } +// // } + +// /** +// * Convert a DID (did:key) to the public key into bytes in SubjectPublicKeyInfo (spki) format. +// * +// * For consumption e.g. in the WebCrypto API. +// */ + +// export function didToPublicKeyBytes(did: string): { +// publicKey: Uint8Array +// type: KeyType +// } { +// if (!did.startsWith(BASE58_DID_PREFIX)) { +// throw new Error("Please use a base58-encoded DID formatted `did:key:z...`") +// } + +// const didWithoutPrefix = did.slice(BASE58_DID_PREFIX.length) +// const magicBytes = uint8arrays.fromString(didWithoutPrefix, "base58btc") +// const parsed = parseMagicBytes(magicBytes) + +// if (parsed.type === "rsa" && !hasPrefix(magicBytes, RSA_DID_PREFIX_OLD)) { +// // DID RSA keys are ASN.1 DER encoded "RSAPublicKeys" (PKCS #1). +// // But the WebCrypto API mostly works with "SubjectPublicKeyInfo" (SPKI), +// // which wraps RSAPublicKey with some metadata. +// // In an unofficial RSA multiformat we were using, we used SPKI, +// // so we have to be careful not to transform *every* RSA DID to SPKI, but +// // only newer DIDs. +// parsed.keyBytes = rsa.convertRSAPublicKeyToSubjectPublicKeyInfo(parsed.keyBytes) +// } + +// return { +// publicKey: parsed.keyBytes, +// type: parsed.type, +// } +// } + + + +// // PUBLIC KEY → DID + + +// /** +// * Convert a base64 public key to a DID (did:key). +// */ +// export function publicKeyToDid( +// publicKey: string, +// type: KeyType, +// encoding: Encodings = "base64pad" +// ): string { +// const pubKeyBytes = uint8arrays.fromString(publicKey, encoding) +// return publicKeyBytesToDid(pubKeyBytes, type) +// } + +// /** +// * Convert a public key in bytes to a DID (did:key). +// */ +// export function publicKeyBytesToDid( +// publicKeyBytes: Uint8Array, +// type: KeyType, +// ): string { +// // Prefix public-write key +// const prefix = magicBytes(type) +// if (prefix === null) { +// throw new Error(`Key type '${type}' not supported`) +// } + +// if (type === "rsa") { +// // See also the comment in didToPublicKeyBytes +// // In this library, we're assuming a single byte encoding for all types of keys. +// // For RSA that is "SubjectPublicKeyInfo", because that's what the WebCrypto API understands. +// // But DIDs assume that all public keys are encoded as "RSAPublicKey". +// publicKeyBytes = rsa.convertSubjectPublicKeyInfoToRSAPublicKey(publicKeyBytes) +// } + +// if(type === "p256") { +// publicKeyBytes = compression.compressNistP256Pubkey(publicKeyBytes) +// } + +// const prefixedBytes = uint8arrays.concat([ prefix, publicKeyBytes ]) + +// // Encode prefixed +// return BASE58_DID_PREFIX + uint8arrays.toString(prefixedBytes, "base58btc") +// } + +export const thing = 1 \ No newline at end of file diff --git a/packages/core/src/plugins.ts b/packages/core/src/plugins.ts index 33cdebd..a0fbb50 100644 --- a/packages/core/src/plugins.ts +++ b/packages/core/src/plugins.ts @@ -3,7 +3,9 @@ import * as uint8arrays from 'uint8arrays' export type DidKeyPlugin = { prefix: Uint8Array jwtAlg: string - checkSignature: (did: string, data: Uint8Array, sig: Uint8Array) => Promise + didToPublicKey: (did: string) => Uint8Array + publicKeyToDid: (pubkey: Uint8Array) => string + checkSignature: (publicKey: Uint8Array, data: Uint8Array, sig: Uint8Array) => Promise } export type DidMethodPlugin = { @@ -50,7 +52,8 @@ export const checkSignature = async (did: string, data: Uint8Array, sig: Uint8Ar const bytes = parsePrefixedBytes(did) for (const keyPlugin of plugins.keys) { if(hasPrefix(bytes, keyPlugin.prefix)) { - return keyPlugin.checkSignature(did, data, sig) + const publicKey = keyPlugin.didToPublicKey(did) + return keyPlugin.checkSignature(publicKey, data, sig) } } } else { diff --git a/packages/core/src/token.ts b/packages/core/src/token.ts index c085bfc..d549125 100644 --- a/packages/core/src/token.ts +++ b/packages/core/src/token.ts @@ -168,9 +168,11 @@ export async function signWithKeypair( payload: UcanPayload, keypair: Keypair, ): Promise { + // @TODO FIX THIS return sign( payload, - keypair.keyType, + {} as any, + // keypair.keyType, data => keypair.sign(data), ) } diff --git a/packages/core/src/types.ts b/packages/core/src/types.ts index c934614..7cbe5cb 100644 --- a/packages/core/src/types.ts +++ b/packages/core/src/types.ts @@ -58,7 +58,6 @@ export type Fact = Record /** Unlike tslib's CryptoKeyPair, this requires the `privateKey` and `publicKey` fields */ export interface Didable { - publicKeyStr: (format?: Encodings) => string did: () => string } @@ -67,8 +66,6 @@ export interface ExportableKey { } export interface Keypair { - publicKey: Uint8Array - keyType: KeyType sign: (msg: Uint8Array) => Promise } diff --git a/packages/plugins/src/crypto/ecdsa.ts b/packages/plugins/src/crypto/ecdsa.ts deleted file mode 100644 index ab1a63f..0000000 --- a/packages/plugins/src/crypto/ecdsa.ts +++ /dev/null @@ -1,90 +0,0 @@ -import { webcrypto } from "one-webcrypto" -import { AvailableCryptoKeyPair, PrivateKeyJwk } from "../types.js" - -export const ALG = "ECDSA" -export const DEFAULT_CURVE = "P-256" -export const DEFAULT_HASH_ALG = "SHA-256" - -export const generateKeypair = async (): Promise => { - return await webcrypto.subtle.generateKey( - { - name: ALG, - namedCurve: DEFAULT_CURVE, - }, - false, - [ "sign", "verify" ] - ) -} - -export const importKeypairJwk = async ( - privKeyJwk: PrivateKeyJwk, - exportable = false -): Promise => { - const privateKey = await webcrypto.subtle.importKey( - "jwk", - privKeyJwk, - { - name: ALG, - namedCurve: DEFAULT_CURVE, - }, - exportable, - ["sign" ] - ) - const { kty, crv, x, y} = privKeyJwk - const pubKeyJwk = { kty, crv, x, y} - const publicKey = await webcrypto.subtle.importKey( - "jwk", - pubKeyJwk, - { - name: ALG, - namedCurve: DEFAULT_CURVE, - }, - true, - [ "verify" ] - ) - return { privateKey, publicKey } -} - -export const exportKey = async (key: CryptoKey): Promise => { - const buf = await webcrypto.subtle.exportKey("raw", key) - return new Uint8Array(buf) -} - -export const importKey = async ( - key: Uint8Array, - namedCurve: NamedCurve -): Promise => { - return await webcrypto.subtle.importKey( - "raw", - key.buffer, - { name: ALG, namedCurve }, - true, - [ "verify" ] - ) -} - -export const sign = async ( - msg: Uint8Array, - privateKey: CryptoKey -): Promise => { - const buf = await webcrypto.subtle.sign( - { name: ALG, hash: { name: DEFAULT_HASH_ALG } }, - privateKey, - msg.buffer - ) - return new Uint8Array(buf) -} - -export const verify = async ( - msg: Uint8Array, - sig: Uint8Array, - pubKey: Uint8Array, - namedCurve: NamedCurve -): Promise => { - return await webcrypto.subtle.verify( - { name: ALG, hash: { name: DEFAULT_HASH_ALG } }, - await importKey(pubKey, namedCurve), - sig.buffer, - msg.buffer - ) -} \ No newline at end of file diff --git a/packages/plugins/src/crypto/index.ts b/packages/plugins/src/crypto/index.ts deleted file mode 100644 index 716bc77..0000000 --- a/packages/plugins/src/crypto/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * as rsa from "./rsa.js" -export * as ecdsa from "./ecdsa.js" diff --git a/packages/plugins/src/default-plugins.ts b/packages/plugins/src/default-plugins.ts new file mode 100644 index 0000000..c567371 --- /dev/null +++ b/packages/plugins/src/default-plugins.ts @@ -0,0 +1,9 @@ +import { Plugins } from '@ucans/core' +import { ed25519Plugin } from "./ed25519/plugin.js" +import { p256Plugin } from "./p256/plugin.js" +import { rsaPlugin, rsaOldPlugin } from "./rsa/plugin.js" + +export const defaults: Plugins = { + keys: [ed25519Plugin, p256Plugin, rsaPlugin, rsaOldPlugin], + methods: [], +} \ No newline at end of file diff --git a/packages/plugins/src/did/index.ts b/packages/plugins/src/did/index.ts deleted file mode 100644 index 4f1c71a..0000000 --- a/packages/plugins/src/did/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export * from "./prefix.js" -export * from "./transformers.js" -export * from "./validation.js" diff --git a/packages/plugins/src/did/prefix.ts b/packages/plugins/src/did/prefix.ts deleted file mode 100644 index 09071c6..0000000 --- a/packages/plugins/src/did/prefix.ts +++ /dev/null @@ -1,104 +0,0 @@ -import * as uint8arrays from "uint8arrays" -import * as compression from "./pubkey-compress.js" -import { KeyType } from "../types.js" - -// Each prefix is varint-encoded. So e.g. 0x1205 gets varint-encoded to 0x8524 -// The varint encoding is described here: https://github.com/multiformats/unsigned-varint -// These varints are encoded big-endian in 7-bit pieces. -// So 0x1205 is split up into 0x12 and 0x05 -// Because there's another byte to be read, the MSB of 0x05 is set: 0x85 -// The next 7 bits encode as 0x24 (instead of 0x12) => 0x8524 - -/** https://github.com/multiformats/multicodec/blob/e9ecf587558964715054a0afcc01f7ace220952c/table.csv#L94 */ -export const EDWARDS_DID_PREFIX = new Uint8Array([ 0xed, 0x01 ]) -/** https://github.com/multiformats/multicodec/blob/e9ecf587558964715054a0afcc01f7ace220952c/table.csv#L91 */ -export const BLS_DID_PREFIX = new Uint8Array([ 0xea, 0x01 ]) -/** https://github.com/multiformats/multicodec/blob/e9ecf587558964715054a0afcc01f7ace220952c/table.csv#L141 */ -export const P256_DID_PREFIX = new Uint8Array([ 0x80, 0x24 ]) -/** https://github.com/multiformats/multicodec/blob/e9ecf587558964715054a0afcc01f7ace220952c/table.csv#L142 */ -export const P384_DID_PREFIX = new Uint8Array([ 0x81, 0x24 ]) -/** https://github.com/multiformats/multicodec/blob/e9ecf587558964715054a0afcc01f7ace220952c/table.csv#L143 */ -export const P521_DID_PREFIX = new Uint8Array([ 0x82, 0x24 ]) -/** https://github.com/multiformats/multicodec/blob/e9ecf587558964715054a0afcc01f7ace220952c/table.csv#L146 */ -export const RSA_DID_PREFIX = new Uint8Array([ 0x85, 0x24 ]) -/** Old RSA DID prefix, used pre-standardisation */ -export const RSA_DID_PREFIX_OLD = new Uint8Array([ 0x00, 0xf5, 0x02 ]) - -export const BASE58_DID_PREFIX = "did:key:z" // z is the multibase prefix for base58btc byte encoding - -/** - * Magic bytes. - */ -export function magicBytes(keyType: KeyType): Uint8Array | null { - switch (keyType) { - case "ed25519": - return EDWARDS_DID_PREFIX - case "p256": - return P256_DID_PREFIX - case "rsa": - return RSA_DID_PREFIX - default: - return null - } -} - -/** - * Parse magic bytes on prefixed key-bytes - * to determine cryptosystem & the unprefixed key-bytes. - */ -export const parseMagicBytes = ( - prefixedKey: Uint8Array -): { - keyBytes: Uint8Array - type: KeyType -} => { - // RSA - if (hasPrefix(prefixedKey, RSA_DID_PREFIX)) { - return { - keyBytes: prefixedKey.slice(RSA_DID_PREFIX.byteLength), - type: "rsa", - } - - // RSA OLD - } else if (hasPrefix(prefixedKey, RSA_DID_PREFIX_OLD)) { - return { - keyBytes: prefixedKey.slice(RSA_DID_PREFIX_OLD.byteLength), - type: "rsa", - } - - // EC P-256 - } else if (hasPrefix(prefixedKey, P256_DID_PREFIX)) { - const compressedKey = prefixedKey.slice(P256_DID_PREFIX.byteLength) - const keyBytes = compression.decompressNistP256Pubkey(compressedKey) - return { - keyBytes, - type: "p256", - } - - // EDWARDS - } else if (hasPrefix(prefixedKey, EDWARDS_DID_PREFIX)) { - return { - keyBytes: prefixedKey.slice(EDWARDS_DID_PREFIX.byteLength), - type: "ed25519", - } - - // BLS - } else if (hasPrefix(prefixedKey, BLS_DID_PREFIX)) { - return { - keyBytes: prefixedKey.slice(BLS_DID_PREFIX.byteLength), - type: "bls12-381", - } - } - - throw new Error("Unsupported key algorithm. Try using RSA.") -} - -/** - * Determines if a Uint8Array has a given indeterminate length-prefix. - */ -export const hasPrefix = ( - prefixedKey: Uint8Array, - prefix: Uint8Array -): boolean => { - return uint8arrays.equals(prefix, prefixedKey.subarray(0, prefix.byteLength)) -} diff --git a/packages/plugins/src/did/pubkey-compress.ts b/packages/plugins/src/did/pubkey-compress.ts deleted file mode 100644 index fe9f7c2..0000000 --- a/packages/plugins/src/did/pubkey-compress.ts +++ /dev/null @@ -1,78 +0,0 @@ -import bigInt from "big-integer" -import * as uint8arrays from "uint8arrays" - -// Compression & Decompression algos from: -// https://stackoverflow.com/questions/48521840/biginteger-to-a-uint8array-of-bytes - -// Public key compression for NIST P-256 -export const compressNistP256Pubkey = (pubkeyBytes: Uint8Array): Uint8Array => { - if (pubkeyBytes.length !== 65) { - throw new Error("Expected 65 byte pubkey") - } else if (pubkeyBytes[0] !== 0x04) { - throw new Error("Expected first byte to be 0x04") - } - // first byte is a prefix - const x = pubkeyBytes.slice(1, 33) - const y = pubkeyBytes.slice(33, 65) - const out = new Uint8Array(x.length + 1) - - out[0] = 2 + (y[y.length - 1] & 1) - out.set(x, 1) - - return out -} - -// Public key decompression for NIST P-256 -export const decompressNistP256Pubkey = (compressed: Uint8Array): Uint8Array => { - if (compressed.length !== 33) { - throw new Error("Expected 33 byte compress pubkey") - } else if (compressed[0] !== 0x02 && compressed[0] !== 0x03) { - throw new Error("Expected first byte to be 0x02 or 0x03") - } - // Consts for P256 curve - const two = bigInt(2) - // 115792089210356248762697446949407573530086143415290314195533631308867097853951 - const prime = two - .pow(256) - .subtract(two.pow(224)) - .add(two.pow(192)) - .add(two.pow(96)) - .subtract(1) - const b = bigInt( - "41058363725152142129326129780047268409114441015993725554835256314039467401291", - ) - - // Pre-computed value, or literal - const pIdent = prime.add(1).divide(4) // 28948022302589062190674361737351893382521535853822578548883407827216774463488 - - // This value must be 2 or 3. 4 indicates an uncompressed key, and anything else is invalid. - const signY = bigInt(compressed[0] - 2) - const x = compressed.slice(1) - const xBig = bigInt(uint8arrays.toString(x, "base10")) - - // y^2 = x^3 - 3x + b - const maybeY = xBig - .pow(3) - .subtract(xBig.multiply(3)) - .add(b) - .modPow(pIdent, prime) - - let yBig - // If the parity matches, we found our root, otherwise it's the other root - if (maybeY.mod(2).equals(signY)) { - yBig = maybeY - } else { - // y = prime - y - yBig = prime.subtract(maybeY) - } - const y = uint8arrays.fromString(yBig.toString(10), "base10") - - // left-pad for smaller than 32 byte y - const offset = 32 - y.length - const yPadded = new Uint8Array(32) - yPadded.set(y, offset) - - // concat coords & prepend P-256 prefix - const publicKey = uint8arrays.concat([[0x04], x, yPadded]) - return publicKey -} diff --git a/packages/plugins/src/did/transformers.ts b/packages/plugins/src/did/transformers.ts deleted file mode 100644 index e91fdd9..0000000 --- a/packages/plugins/src/did/transformers.ts +++ /dev/null @@ -1,118 +0,0 @@ -import * as uint8arrays from "uint8arrays" - -import * as compression from "./pubkey-compress.js" -import * as rsa from "../crypto/rsa.js" -import { BASE58_DID_PREFIX, RSA_DID_PREFIX_OLD, magicBytes, parseMagicBytes, hasPrefix } from "./prefix.js" -import { KeyType } from "../types.js" -import { Encodings } from "@ucans/core" - - -// DID → PUBLIC KEY - - -/** - * Convert a DID (did:key) to a base64 public key. - */ -// export function didToPublicKey(did: string, encoding: Encodings = "base64pad"): { -// publicKey: string -// type: KeyType -// } { -// const { publicKey, type } = didToPublicKeyBytes(did) -// return { -// publicKey: uint8arrays.toString(publicKey, encoding), -// type -// } -// } - -/** - * Convert a DID (did:key) to the public key into bytes in SubjectPublicKeyInfo (spki) format. - * - * For consumption e.g. in the WebCrypto API. - */ -export function keyBytesFromDid(did: string, expectedPrefix: Uint8Array): Uint8Array { - if (!did.startsWith(BASE58_DID_PREFIX)) { - throw new Error("Please use a base58-encoded DID formatted `did:key:z...`") - } - const didWithoutPrefix = did.slice(BASE58_DID_PREFIX.length) - const bytes = uint8arrays.fromString(didWithoutPrefix, "base58btc") - if(!hasPrefix(bytes, expectedPrefix)) { - throw new Error(`Expected prefix: ${expectedPrefix}`) - } - return bytes.slice(expectedPrefix.length) -} - -export function didToPublicKeyBytes(did: string): { - publicKey: Uint8Array - type: KeyType -} { - if (!did.startsWith(BASE58_DID_PREFIX)) { - throw new Error("Please use a base58-encoded DID formatted `did:key:z...`") - } - - const didWithoutPrefix = did.slice(BASE58_DID_PREFIX.length) - const magicBytes = uint8arrays.fromString(didWithoutPrefix, "base58btc") - const parsed = parseMagicBytes(magicBytes) - - if (parsed.type === "rsa" && !hasPrefix(magicBytes, RSA_DID_PREFIX_OLD)) { - // DID RSA keys are ASN.1 DER encoded "RSAPublicKeys" (PKCS #1). - // But the WebCrypto API mostly works with "SubjectPublicKeyInfo" (SPKI), - // which wraps RSAPublicKey with some metadata. - // In an unofficial RSA multiformat we were using, we used SPKI, - // so we have to be careful not to transform *every* RSA DID to SPKI, but - // only newer DIDs. - parsed.keyBytes = rsa.convertRSAPublicKeyToSubjectPublicKeyInfo(parsed.keyBytes) - } - - return { - publicKey: parsed.keyBytes, - type: parsed.type, - } -} - - - -// PUBLIC KEY → DID - - -/** - * Convert a base64 public key to a DID (did:key). - */ -export function publicKeyToDid( - publicKey: string, - type: KeyType, - encoding: Encodings = "base64pad" -): string { - const pubKeyBytes = uint8arrays.fromString(publicKey, encoding) - return publicKeyBytesToDid(pubKeyBytes, type) -} - -/** - * Convert a public key in bytes to a DID (did:key). - */ -export function publicKeyBytesToDid( - publicKeyBytes: Uint8Array, - type: KeyType, -): string { - // Prefix public-write key - const prefix = magicBytes(type) - if (prefix === null) { - throw new Error(`Key type '${type}' not supported`) - } - - if (type === "rsa") { - // See also the comment in didToPublicKeyBytes - // In this library, we're assuming a single byte encoding for all types of keys. - // For RSA that is "SubjectPublicKeyInfo", because that's what the WebCrypto API understands. - // But DIDs assume that all public keys are encoded as "RSAPublicKey". - publicKeyBytes = rsa.convertSubjectPublicKeyInfoToRSAPublicKey(publicKeyBytes) - } - - if(type === "p256") { - publicKeyBytes = compression.compressNistP256Pubkey(publicKeyBytes) - } - - const prefixedBytes = uint8arrays.concat([ prefix, publicKeyBytes ]) - - // Encode prefixed - return BASE58_DID_PREFIX + uint8arrays.toString(prefixedBytes, "base58btc") -} diff --git a/packages/plugins/src/did/validation.ts b/packages/plugins/src/did/validation.ts deleted file mode 100644 index f472617..0000000 --- a/packages/plugins/src/did/validation.ts +++ /dev/null @@ -1,44 +0,0 @@ -import * as ed25519 from "@stablelib/ed25519" -import * as uint8arrays from "uint8arrays" - -import * as rsa from "../crypto/rsa.js" -import * as ecdsa from "../crypto/ecdsa.js" - -import { didToPublicKeyBytes } from "./transformers.js" - - -/** - * Verify the signature of some data (Uint8Array), given a DID. - */ -export async function verifySignature(data: Uint8Array, signature: Uint8Array, did: string): Promise { - try { - const { type, publicKey } = didToPublicKeyBytes(did) - - switch (type) { - - case "ed25519": - return ed25519.verify(publicKey, data, signature) - - case "rsa": - return await rsa.verify(data, signature, publicKey) - - case "p256": - return await ecdsa.verify(data, signature, publicKey, "P-256") - - default: return false - } - - } catch (_) { - return false - - } -} - -/** - * Verify the signature of some data (string encoded as utf8), given a DID. - */ -export async function verifySignatureUtf8(data: string, signature: string, did: string): Promise { - const dataBytes = uint8arrays.fromString(data, "utf8") - const sigBytes = uint8arrays.fromString(signature, "base64url") - return await verifySignature(dataBytes, sigBytes, did) -} diff --git a/packages/plugins/src/ed25519/crypto.ts b/packages/plugins/src/ed25519/crypto.ts new file mode 100644 index 0000000..3dc2002 --- /dev/null +++ b/packages/plugins/src/ed25519/crypto.ts @@ -0,0 +1,10 @@ +import { EDWARDS_DID_PREFIX } from "../prefixes" +import { didFromKeyBytes, keyBytesFromDid } from "../util" + +export const didToPublickey = (did: string): Uint8Array => { + return keyBytesFromDid(did, EDWARDS_DID_PREFIX) +} + +export const publicKeyToDid = (pubkey: Uint8Array): string => { + return didFromKeyBytes(pubkey, EDWARDS_DID_PREFIX) +} diff --git a/packages/plugins/src/keypair/ed25519.ts b/packages/plugins/src/ed25519/keypair.ts similarity index 76% rename from packages/plugins/src/keypair/ed25519.ts rename to packages/plugins/src/ed25519/keypair.ts index fc7661a..3bba1a3 100644 --- a/packages/plugins/src/keypair/ed25519.ts +++ b/packages/plugins/src/ed25519/keypair.ts @@ -1,17 +1,20 @@ -import * as ed25519 from "@stablelib/ed25519" import * as uint8arrays from "uint8arrays" +import * as ed25519 from "@stablelib/ed25519" +import * as crypto from "./crypto.js" -import BaseKeypair from "./base.js" -import { Encodings } from "@ucans/core" +import { Didable, Encodings, ExportableKey, Keypair } from "@ucans/core" -export class EdKeypair extends BaseKeypair { +export class EdKeypair implements Keypair, Didable, ExportableKey { private secretKey: Uint8Array + private publicKey: Uint8Array + private exportable: boolean constructor(secretKey: Uint8Array, publicKey: Uint8Array, exportable: boolean) { - super(publicKey, "ed25519", exportable) this.secretKey = secretKey + this.publicKey = publicKey + this.exportable = exportable } static async create(params?: { @@ -32,6 +35,10 @@ export class EdKeypair extends BaseKeypair { return new EdKeypair(secretKey, publicKey, exportable) } + did(): string { + return crypto.publicKeyToDid(this.publicKey) + } + async sign(msg: Uint8Array): Promise { return ed25519.sign(this.secretKey, msg) } diff --git a/packages/plugins/src/ed25519/plugin.ts b/packages/plugins/src/ed25519/plugin.ts new file mode 100644 index 0000000..cbbe3fc --- /dev/null +++ b/packages/plugins/src/ed25519/plugin.ts @@ -0,0 +1,15 @@ +import { DidKeyPlugin } from '@ucans/core' +import * as ed25519 from "@stablelib/ed25519" +import * as crypto from "./crypto.js" + +import { EDWARDS_DID_PREFIX } from "../prefixes.js" + +export const ed25519Plugin: DidKeyPlugin = { + prefix: EDWARDS_DID_PREFIX, + jwtAlg: 'EdDSA', + didToPublicKey: crypto.didToPublickey, + publicKeyToDid: crypto.publicKeyToDid, + checkSignature: async (publicKey: Uint8Array, data: Uint8Array, sig: Uint8Array) => { + return ed25519.verify(publicKey, data, sig) + } +} \ No newline at end of file diff --git a/packages/plugins/src/index.ts b/packages/plugins/src/index.ts index 8602dc6..4100423 100644 --- a/packages/plugins/src/index.ts +++ b/packages/plugins/src/index.ts @@ -1,5 +1,12 @@ -export * from "./keypair/ed25519.js" -export * from "./keypair/rsa.js" -export * from "./did/index.js" -export * as keypair from "./keypair/index.js" -export * from "./plugins.js" \ No newline at end of file +export * from "./ed25519/keypair.js" +export * from "./rsa/keypair.js" +export * from "./p256/keypair.js" + +export * from "./ed25519/plugin.js" +export * from "./rsa/plugin.js" +export * from "./p256/plugin.js" + +export * from "./default-plugins.js" + +export * as rsa from "./rsa/crypto.js" +export * as p256 from "./p256/crypto.js" \ No newline at end of file diff --git a/packages/plugins/src/keypair/base.ts b/packages/plugins/src/keypair/base.ts deleted file mode 100644 index c08d6d5..0000000 --- a/packages/plugins/src/keypair/base.ts +++ /dev/null @@ -1,32 +0,0 @@ -import * as uint8arrays from "uint8arrays" -import { Keypair, Didable, Encodings, ExportableKey } from '@ucans/core' - -import { publicKeyBytesToDid } from "../did/transformers.js" -import { KeyType } from "../types.js" - - - - -export default abstract class BaseKeypair implements Keypair, Didable, ExportableKey { - - publicKey: Uint8Array - keyType: KeyType - exportable: boolean - - constructor(publicKey: Uint8Array, keyType: KeyType, exportable: boolean) { - this.publicKey = publicKey - this.keyType = keyType - this.exportable = exportable - } - - publicKeyStr(encoding: Encodings = "base64pad"): string { - return uint8arrays.toString(this.publicKey, encoding) - } - - did(): string { - return publicKeyBytesToDid(this.publicKey, this.keyType) - } - - abstract sign(msg: Uint8Array): Promise - abstract export(): Promise -} diff --git a/packages/plugins/src/keypair/index.ts b/packages/plugins/src/keypair/index.ts deleted file mode 100644 index f230746..0000000 --- a/packages/plugins/src/keypair/index.ts +++ /dev/null @@ -1,4 +0,0 @@ -export * from "./ed25519.js" -export * from "./ecdsa.js" -export * from "./rsa.js" -export * from "./base.js" diff --git a/packages/plugins/src/p256/crypto.ts b/packages/plugins/src/p256/crypto.ts new file mode 100644 index 0000000..ca02f30 --- /dev/null +++ b/packages/plugins/src/p256/crypto.ts @@ -0,0 +1,187 @@ +import bigInt from "big-integer" +import * as uint8arrays from "uint8arrays" +import { webcrypto } from "one-webcrypto" +import { AvailableCryptoKeyPair, PrivateKeyJwk } from "../types.js" +import { didFromKeyBytes, keyBytesFromDid } from "../util.js" +import { P256_DID_PREFIX } from "../prefixes.js" + +export const ALG = "ECDSA" +export const DEFAULT_CURVE = "P-256" +export const DEFAULT_HASH_ALG = "SHA-256" + +export const generateKeypair = async (): Promise => { + return await webcrypto.subtle.generateKey( + { + name: ALG, + namedCurve: DEFAULT_CURVE, + }, + false, + [ "sign", "verify" ] + ) +} + +export const importKeypairJwk = async ( + privKeyJwk: PrivateKeyJwk, + exportable = false +): Promise => { + const privateKey = await webcrypto.subtle.importKey( + "jwk", + privKeyJwk, + { + name: ALG, + namedCurve: DEFAULT_CURVE, + }, + exportable, + ["sign" ] + ) + const { kty, crv, x, y} = privKeyJwk + const pubKeyJwk = { kty, crv, x, y} + const publicKey = await webcrypto.subtle.importKey( + "jwk", + pubKeyJwk, + { + name: ALG, + namedCurve: DEFAULT_CURVE, + }, + true, + [ "verify" ] + ) + return { privateKey, publicKey } +} + +export const exportKey = async (key: CryptoKey): Promise => { + const buf = await webcrypto.subtle.exportKey("raw", key) + return new Uint8Array(buf) +} + +export const importKey = async ( + key: Uint8Array +): Promise => { + return await webcrypto.subtle.importKey( + "raw", + key.buffer, + { name: ALG, namedCurve: DEFAULT_CURVE }, + true, + [ "verify" ] + ) +} + +export const sign = async ( + msg: Uint8Array, + privateKey: CryptoKey +): Promise => { + const buf = await webcrypto.subtle.sign( + { name: ALG, hash: { name: DEFAULT_HASH_ALG } }, + privateKey, + msg.buffer + ) + return new Uint8Array(buf) +} + +export const verify = async ( + pubKey: Uint8Array, + msg: Uint8Array, + sig: Uint8Array +): Promise => { + return await webcrypto.subtle.verify( + { name: ALG, hash: { name: DEFAULT_HASH_ALG } }, + await importKey(pubKey), + sig.buffer, + msg.buffer + ) +} + + +export const didToPublicKey = (did: string): Uint8Array => { + // The multiformats space (used by did:key) specifies that NIST P-256 + // keys should be encoded as the 33-byte compressed public key, + // instead of the 65-byte raw public key + const keyBytes = keyBytesFromDid(did, P256_DID_PREFIX) + return decompressP256Pubkey(keyBytes) +} + +export const publicKeyToDid = (publicKey: Uint8Array): string => { + const compressed = compressP256Pubkey(publicKey) + return didFromKeyBytes(compressed, P256_DID_PREFIX) +} + + + +// PUBLIC KEY COMPRESSION +// ------------------------- + +// Compression & Decompression algos from: +// https://stackoverflow.com/questions/48521840/biginteger-to-a-uint8array-of-bytes + +// Public key compression for NIST P-256 +export const compressP256Pubkey = (pubkeyBytes: Uint8Array): Uint8Array => { + if (pubkeyBytes.length !== 65) { + throw new Error("Expected 65 byte pubkey") + } else if (pubkeyBytes[0] !== 0x04) { + throw new Error("Expected first byte to be 0x04") + } + // first byte is a prefix + const x = pubkeyBytes.slice(1, 33) + const y = pubkeyBytes.slice(33, 65) + const out = new Uint8Array(x.length + 1) + + out[0] = 2 + (y[y.length - 1] & 1) + out.set(x, 1) + + return out +} + +// Public key decompression for NIST P-256 +export const decompressP256Pubkey = (compressed: Uint8Array): Uint8Array => { + if (compressed.length !== 33) { + throw new Error("Expected 33 byte compress pubkey") + } else if (compressed[0] !== 0x02 && compressed[0] !== 0x03) { + throw new Error("Expected first byte to be 0x02 or 0x03") + } + // Consts for P256 curve + const two = bigInt(2) + // 115792089210356248762697446949407573530086143415290314195533631308867097853951 + const prime = two + .pow(256) + .subtract(two.pow(224)) + .add(two.pow(192)) + .add(two.pow(96)) + .subtract(1) + const b = bigInt( + "41058363725152142129326129780047268409114441015993725554835256314039467401291", + ) + + // Pre-computed value, or literal + const pIdent = prime.add(1).divide(4) // 28948022302589062190674361737351893382521535853822578548883407827216774463488 + + // This value must be 2 or 3. 4 indicates an uncompressed key, and anything else is invalid. + const signY = bigInt(compressed[0] - 2) + const x = compressed.slice(1) + const xBig = bigInt(uint8arrays.toString(x, "base10")) + + // y^2 = x^3 - 3x + b + const maybeY = xBig + .pow(3) + .subtract(xBig.multiply(3)) + .add(b) + .modPow(pIdent, prime) + + let yBig + // If the parity matches, we found our root, otherwise it's the other root + if (maybeY.mod(2).equals(signY)) { + yBig = maybeY + } else { + // y = prime - y + yBig = prime.subtract(maybeY) + } + const y = uint8arrays.fromString(yBig.toString(10), "base10") + + // left-pad for smaller than 32 byte y + const offset = 32 - y.length + const yPadded = new Uint8Array(32) + yPadded.set(y, offset) + + // concat coords & prepend P-256 prefix + const publicKey = uint8arrays.concat([[0x04], x, yPadded]) + return publicKey +} diff --git a/packages/plugins/src/keypair/ecdsa.ts b/packages/plugins/src/p256/keypair.ts similarity index 67% rename from packages/plugins/src/keypair/ecdsa.ts rename to packages/plugins/src/p256/keypair.ts index a304fbf..bacbcad 100644 --- a/packages/plugins/src/keypair/ecdsa.ts +++ b/packages/plugins/src/p256/keypair.ts @@ -1,38 +1,42 @@ import { webcrypto } from "one-webcrypto" import * as uint8arrays from "uint8arrays" -import { Encodings } from '@ucans/core' +import { Didable, Encodings, ExportableKey, Keypair } from "@ucans/core" -import * as ecdsa from "../crypto/ecdsa.js" +import * as crypto from "./crypto.js" import { AvailableCryptoKeyPair, isAvailableCryptoKeyPair, PrivateKeyJwk, } from "../types.js" -import BaseKeypair from "./base.js" -export class EcdsaKeypair extends BaseKeypair { + +export class EcdsaKeypair implements Keypair, Didable, ExportableKey { + + private publicKey: Uint8Array private keypair: AvailableCryptoKeyPair + private exportable: boolean constructor( keypair: AvailableCryptoKeyPair, publicKey: Uint8Array, exportable: boolean ) { - super(publicKey, "p256", exportable) this.keypair = keypair + this.publicKey = publicKey + this.exportable = exportable } static async create(params?: { exportable?: boolean }): Promise { const { exportable = false } = params || {} - const keypair = await ecdsa.generateKeypair() + const keypair = await crypto.generateKeypair() if (!isAvailableCryptoKeyPair(keypair)) { throw new Error(`Couldn't generate valid keypair`) } - const publicKey = await ecdsa.exportKey(keypair.publicKey) + const publicKey = await crypto.exportKey(keypair.publicKey) return new EcdsaKeypair(keypair, publicKey, exportable) } @@ -42,18 +46,22 @@ export class EcdsaKeypair extends BaseKeypair { exportable?: boolean }): Promise { const { exportable = false } = params || {} - const keypair = await ecdsa.importKeypairJwk(jwk, exportable) + const keypair = await crypto.importKeypairJwk(jwk, exportable) if (!isAvailableCryptoKeyPair(keypair)) { throw new Error(`Couldn't generate valid keypair`) } - const publicKey = await ecdsa.exportKey(keypair.publicKey) + const publicKey = await crypto.exportKey(keypair.publicKey) return new EcdsaKeypair(keypair, publicKey, exportable) - } + } + + did(): string { + return crypto.publicKeyToDid(this.publicKey) + } async sign(msg: Uint8Array): Promise { - return await ecdsa.sign(msg, this.keypair.privateKey) + return await crypto.sign(msg, this.keypair.privateKey) } async export(format: Encodings = "base64pad"): Promise { diff --git a/packages/plugins/src/p256/plugin.ts b/packages/plugins/src/p256/plugin.ts new file mode 100644 index 0000000..e42a3e9 --- /dev/null +++ b/packages/plugins/src/p256/plugin.ts @@ -0,0 +1,11 @@ +import { DidKeyPlugin } from '@ucans/core' +import * as crypto from "./crypto.js" +import { P256_DID_PREFIX } from "../prefixes.js" + +export const p256Plugin: DidKeyPlugin = { + prefix: P256_DID_PREFIX, + jwtAlg: 'ES256', + didToPublicKey: crypto.didToPublicKey, + publicKeyToDid: crypto.publicKeyToDid, + checkSignature: crypto.verify, +} \ No newline at end of file diff --git a/packages/plugins/src/plugins.ts b/packages/plugins/src/plugins.ts deleted file mode 100644 index 17685e2..0000000 --- a/packages/plugins/src/plugins.ts +++ /dev/null @@ -1,52 +0,0 @@ -import { DidKeyPlugin, Plugins } from '@ucans/core' -import { EDWARDS_DID_PREFIX, keyBytesFromDid, P256_DID_PREFIX, RSA_DID_PREFIX, RSA_DID_PREFIX_OLD } from './did' -import * as ed25519 from "@stablelib/ed25519" -import * as rsaCrypto from "./crypto/rsa.js" -import * as ecdsaCrypto from "./crypto/rsa.js" -import { decompressNistP256Pubkey } from "./did/pubkey-compress" - -export const edwards: DidKeyPlugin = { - prefix: EDWARDS_DID_PREFIX, - jwtAlg: 'EdDSA', - checkSignature: async (did, data, sig): Promise => { - const keyBytes = keyBytesFromDid(did, EDWARDS_DID_PREFIX) - return ed25519.verify(keyBytes, data, sig) - } -} - -export const rsa: DidKeyPlugin = { - prefix: RSA_DID_PREFIX, - jwtAlg: 'RS256', - checkSignature: async (did, data, sig): Promise => { - const keyBytes = keyBytesFromDid(did, RSA_DID_PREFIX) - const spkiBytes = rsaCrypto.convertRSAPublicKeyToSubjectPublicKeyInfo(keyBytes) - const isValid = await rsaCrypto.verify(data, sig, spkiBytes) - return isValid - } -} - -export const rsaOld: DidKeyPlugin = { - prefix: RSA_DID_PREFIX_OLD, - jwtAlg: 'RS256', - checkSignature: async (did, data, sig): Promise => { - const keyBytes = keyBytesFromDid(did, RSA_DID_PREFIX_OLD) - const isValid = await rsaCrypto.verify(data, sig, keyBytes) - return isValid - } -} - -export const p256: DidKeyPlugin = { - prefix: P256_DID_PREFIX, - jwtAlg: 'ES256', - checkSignature: async (did, data, sig): Promise => { - const keyBytes = keyBytesFromDid(did, P256_DID_PREFIX) - const decompressedKey = decompressNistP256Pubkey(keyBytes) - const isValid = await ecdsaCrypto.verify(data, sig, decompressedKey) - return isValid - } -} - -export const defaults: Plugins = { - keys: [edwards, p256, rsa, rsaOld], - methods: [], -} \ No newline at end of file diff --git a/packages/plugins/src/prefixes.ts b/packages/plugins/src/prefixes.ts new file mode 100644 index 0000000..2ede29c --- /dev/null +++ b/packages/plugins/src/prefixes.ts @@ -0,0 +1,24 @@ +// Each prefix is varint-encoded. So e.g. 0x1205 gets varint-encoded to 0x8524 +// The varint encoding is described here: https://github.com/multiformats/unsigned-varint +// These varints are encoded big-endian in 7-bit pieces. +// So 0x1205 is split up into 0x12 and 0x05 +// Because there's another byte to be read, the MSB of 0x05 is set: 0x85 +// The next 7 bits encode as 0x24 (instead of 0x12) => 0x8524 + +/** https://github.com/multiformats/multicodec/blob/e9ecf587558964715054a0afcc01f7ace220952c/table.csv#L94 */ +export const EDWARDS_DID_PREFIX = new Uint8Array([ 0xed, 0x01 ]) +/** https://github.com/multiformats/multicodec/blob/e9ecf587558964715054a0afcc01f7ace220952c/table.csv#L91 */ +export const BLS_DID_PREFIX = new Uint8Array([ 0xea, 0x01 ]) +/** https://github.com/multiformats/multicodec/blob/e9ecf587558964715054a0afcc01f7ace220952c/table.csv#L141 */ +export const P256_DID_PREFIX = new Uint8Array([ 0x80, 0x24 ]) +/** https://github.com/multiformats/multicodec/blob/e9ecf587558964715054a0afcc01f7ace220952c/table.csv#L142 */ +export const P384_DID_PREFIX = new Uint8Array([ 0x81, 0x24 ]) +/** https://github.com/multiformats/multicodec/blob/e9ecf587558964715054a0afcc01f7ace220952c/table.csv#L143 */ +export const P521_DID_PREFIX = new Uint8Array([ 0x82, 0x24 ]) +/** https://github.com/multiformats/multicodec/blob/e9ecf587558964715054a0afcc01f7ace220952c/table.csv#L146 */ +export const RSA_DID_PREFIX = new Uint8Array([ 0x85, 0x24 ]) +/** Old RSA DID prefix, used pre-standardisation */ +export const RSA_DID_PREFIX_OLD = new Uint8Array([ 0x00, 0xf5, 0x02 ]) + +export const BASE58_DID_PREFIX = "did:key:z" // z is the multibase prefix for base58btc byte encoding + diff --git a/packages/plugins/src/crypto/rsa.ts b/packages/plugins/src/rsa/crypto.ts similarity index 79% rename from packages/plugins/src/crypto/rsa.ts rename to packages/plugins/src/rsa/crypto.ts index 1ae1a45..8c9f1fa 100644 --- a/packages/plugins/src/crypto/rsa.ts +++ b/packages/plugins/src/rsa/crypto.ts @@ -1,5 +1,7 @@ import { webcrypto } from "one-webcrypto" import * as uint8arrays from "uint8arrays" +import { RSA_DID_PREFIX } from "../prefixes" +import { didFromKeyBytes, keyBytesFromDid } from "../util.js" export const RSA_ALG = "RSASSA-PKCS1-v1_5" export const DEFAULT_KEY_SIZE = 2048 @@ -44,7 +46,7 @@ export const sign = async (msg: Uint8Array, privateKey: CryptoKey): Promise => { +export const verify = async (pubKey: Uint8Array, msg: Uint8Array, sig: Uint8Array): Promise => { return await webcrypto.subtle.verify( { name: RSA_ALG, saltLength: SALT_LEGNTH }, await importKey(pubKey), @@ -53,6 +55,36 @@ export const verify = async (msg: Uint8Array, sig: Uint8Array, pubKey: Uint8Arra ) } +export const didToPublicKey = (did: string): Uint8Array => { + // DID RSA keys are ASN.1 DER encoded "RSAPublicKeys" (PKCS #1). + // But the WebCrypto API mostly works with "SubjectPublicKeyInfo" (SPKI), + // which wraps RSAPublicKey with some metadata. + // In an unofficial RSA multiformat we were using, we used SPKI, + // so we have to be careful not to transform *every* RSA DID to SPKI, but + // only newer DIDs. + const keyBytes = keyBytesFromDid(did, RSA_DID_PREFIX) + return convertRSAPublicKeyToSubjectPublicKeyInfo(keyBytes) +} + +export const oldDidToPublicKey = (did: string): Uint8Array => { + return keyBytesFromDid(did, RSA_DID_PREFIX) +} + +export const publicKeyToDid = (pubkey: Uint8Array): string => { + // See also the comment in rsaDidToPublicKeyBytes + // In this library, we're assuming a single byte encoding for all types of keys. + // For RSA that is "SubjectPublicKeyInfo", because that's what the WebCrypto API understands. + // But DIDs assume that all public keys are encoded as "RSAPublicKey". + const convertedBytes = convertSubjectPublicKeyInfoToRSAPublicKey(pubkey) + return didFromKeyBytes(convertedBytes, RSA_DID_PREFIX) +} + +export const publicKeyToOldDid = (pubkey: Uint8Array): string => { + return didFromKeyBytes(pubkey, RSA_DID_PREFIX) +} + + + /** * The ASN.1 DER encoded header that needs to be added to an * ASN.1 DER encoded RSAPublicKey to make it a SubjectPublicKeyInfo. diff --git a/packages/plugins/src/rsa/index.ts b/packages/plugins/src/rsa/index.ts new file mode 100644 index 0000000..e69de29 diff --git a/packages/plugins/src/keypair/rsa.ts b/packages/plugins/src/rsa/keypair.ts similarity index 65% rename from packages/plugins/src/keypair/rsa.ts rename to packages/plugins/src/rsa/keypair.ts index 61acd20..741280d 100644 --- a/packages/plugins/src/keypair/rsa.ts +++ b/packages/plugins/src/rsa/keypair.ts @@ -1,19 +1,21 @@ import { webcrypto } from "one-webcrypto" import * as uint8arrays from "uint8arrays" -import * as rsa from "../crypto/rsa.js" -import BaseKeypair from "./base.js" +import * as crypto from "./crypto.js" import { AvailableCryptoKeyPair, isAvailableCryptoKeyPair } from "../types.js" -import { Encodings } from "@ucans/core" +import { Didable, Encodings, ExportableKey, Keypair } from "@ucans/core" -export class RsaKeypair extends BaseKeypair { +export class RsaKeypair implements Keypair, Didable, ExportableKey { + private publicKey: Uint8Array private keypair: AvailableCryptoKeyPair + private exportable: boolean constructor(keypair: AvailableCryptoKeyPair, publicKey: Uint8Array, exportable: boolean) { - super(publicKey, "rsa", exportable) this.keypair = keypair + this.publicKey = publicKey + this.exportable = exportable } static async create(params?: { @@ -21,16 +23,20 @@ export class RsaKeypair extends BaseKeypair { exportable?: boolean }): Promise { const { size = 2048, exportable = false } = params || {} - const keypair = await rsa.generateKeypair(size) + const keypair = await crypto.generateKeypair(size) if (!isAvailableCryptoKeyPair(keypair)) { throw new Error(`Couldn't generate valid keypair`) } - const publicKey = await rsa.exportKey(keypair.publicKey) + const publicKey = await crypto.exportKey(keypair.publicKey) return new RsaKeypair(keypair, publicKey, exportable) } + did(): string { + return crypto.publicKeyToDid(this.publicKey) + } + async sign(msg: Uint8Array): Promise { - return await rsa.sign(msg, this.keypair.privateKey) + return await crypto.sign(msg, this.keypair.privateKey) } async export(format: Encodings = "base64pad"): Promise { diff --git a/packages/plugins/src/rsa/plugin.ts b/packages/plugins/src/rsa/plugin.ts new file mode 100644 index 0000000..a37a113 --- /dev/null +++ b/packages/plugins/src/rsa/plugin.ts @@ -0,0 +1,20 @@ +import { DidKeyPlugin } from '@ucans/core' +import * as crypto from "./crypto.js" +import { RSA_DID_PREFIX, RSA_DID_PREFIX_OLD } from "../prefixes.js" + +export const rsaPlugin: DidKeyPlugin = { + prefix: RSA_DID_PREFIX, + jwtAlg: 'RS256', + didToPublicKey: crypto.didToPublicKey, + publicKeyToDid: crypto.publicKeyToDid, + checkSignature: crypto.verify, +} + +export const rsaOldPlugin: DidKeyPlugin = { + prefix: RSA_DID_PREFIX_OLD, + jwtAlg: 'RS256', + didToPublicKey: crypto.oldDidToPublicKey, + publicKeyToDid: crypto.publicKeyToOldDid, + checkSignature: crypto.verify, +} + diff --git a/packages/plugins/src/util.ts b/packages/plugins/src/util.ts new file mode 100644 index 0000000..b10f420 --- /dev/null +++ b/packages/plugins/src/util.ts @@ -0,0 +1,32 @@ +import * as uint8arrays from 'uint8arrays' +import { BASE58_DID_PREFIX } from './prefixes' + +export function keyBytesFromDid(did: string, expectedPrefix: Uint8Array): Uint8Array { + if (!did.startsWith(BASE58_DID_PREFIX)) { + throw new Error("Please use a base58-encoded DID formatted `did:key:z...`") + } + const didWithoutPrefix = did.slice(BASE58_DID_PREFIX.length) + const bytes = uint8arrays.fromString(didWithoutPrefix, "base58btc") + if(!hasPrefix(bytes, expectedPrefix)) { + throw new Error(`Expected prefix: ${expectedPrefix}`) + } + return bytes.slice(expectedPrefix.length) +} + +export function didFromKeyBytes(publicKeyBytes: Uint8Array, prefix: Uint8Array): string { + const bytes = uint8arrays.concat([prefix, publicKeyBytes]) + const base58Key = uint8arrays.toString(bytes, "base58btc") + return BASE58_DID_PREFIX + base58Key +} + + + +/** + * Determines if a Uint8Array has a given indeterminate length-prefix. + */ +export const hasPrefix = ( + prefixedKey: Uint8Array, + prefix: Uint8Array +): boolean => { + return uint8arrays.equals(prefix, prefixedKey.subarray(0, prefix.byteLength)) +} \ No newline at end of file From 91df141de31cf61543aa14f7c9a9fad2f8b74f58 Mon Sep 17 00:00:00 2001 From: dholms Date: Tue, 28 Jun 2022 21:55:41 -0500 Subject: [PATCH 06/25] cleaning up, removed KeyType --- packages/core/src/did.ts | 109 ---- packages/core/src/plugins.ts | 8 +- packages/core/src/token.ts | 32 +- packages/core/src/types.ts | 11 +- packages/core/tests/capability/wnfs.test.ts | 5 - packages/core/tests/token.test.ts | 16 +- packages/core/tests/verify.test.ts | 678 ++++++++++---------- packages/plugins/src/ed25519/keypair.ts | 2 + packages/plugins/src/p256/keypair.ts | 2 + packages/plugins/src/rsa/keypair.ts | 2 + packages/plugins/src/types.ts | 6 - packages/plugins/tests/ecdsa.test.ts | 9 +- packages/ucans/src/index.ts | 22 +- tests-copy/attenuation.test.ts | 155 ----- tests-copy/builder.test.ts | 135 ---- tests-copy/capability.test.ts | 22 - tests-copy/capability/email.ts | 82 --- tests-copy/capability/wnfs.test.ts | 238 ------- tests-copy/capability/wnfs.ts | 213 ------ tests-copy/compatibility.test.ts | 39 -- tests-copy/did-key.ts | 36 -- tests-copy/did.test.ts | 78 --- tests-copy/ecdsa.test.ts | 34 - tests-copy/ed25519.test.ts | 31 - tests-copy/fixtures.ts | 18 - tests-copy/rsa.test.ts | 96 --- tests-copy/semver.test.ts | 89 --- tests-copy/store.test.ts | 142 ---- tests-copy/token.test.ts | 175 ----- tests-copy/verify.test.ts | 350 ---------- 30 files changed, 368 insertions(+), 2467 deletions(-) delete mode 100644 packages/core/src/did.ts delete mode 100644 tests-copy/attenuation.test.ts delete mode 100644 tests-copy/builder.test.ts delete mode 100644 tests-copy/capability.test.ts delete mode 100644 tests-copy/capability/email.ts delete mode 100644 tests-copy/capability/wnfs.test.ts delete mode 100644 tests-copy/capability/wnfs.ts delete mode 100644 tests-copy/compatibility.test.ts delete mode 100644 tests-copy/did-key.ts delete mode 100644 tests-copy/did.test.ts delete mode 100644 tests-copy/ecdsa.test.ts delete mode 100644 tests-copy/ed25519.test.ts delete mode 100644 tests-copy/fixtures.ts delete mode 100644 tests-copy/rsa.test.ts delete mode 100644 tests-copy/semver.test.ts delete mode 100644 tests-copy/store.test.ts delete mode 100644 tests-copy/token.test.ts delete mode 100644 tests-copy/verify.test.ts diff --git a/packages/core/src/did.ts b/packages/core/src/did.ts deleted file mode 100644 index 109e195..0000000 --- a/packages/core/src/did.ts +++ /dev/null @@ -1,109 +0,0 @@ -// import * as uint8arrays from "uint8arrays" - -// import * as compression from "../p256/pubkey-compress.js" -// import * as rsa from "../crypto/rsa.js" -// import { BASE58_DID_PREFIX, RSA_DID_PREFIX_OLD, magicBytes, parseMagicBytes, hasPrefix } from "./prefix.js" -// import { KeyType } from "../types.js" -// import { Encodings } from "@ucans/core" - - -// // DID → PUBLIC KEY - - -// /** -// * Convert a DID (did:key) to a base64 public key. -// */ -// // export function didToPublicKey(did: string, encoding: Encodings = "base64pad"): { -// // publicKey: string -// // type: KeyType -// // } { -// // const { publicKey, type } = didToPublicKeyBytes(did) -// // return { -// // publicKey: uint8arrays.toString(publicKey, encoding), -// // type -// // } -// // } - -// /** -// * Convert a DID (did:key) to the public key into bytes in SubjectPublicKeyInfo (spki) format. -// * -// * For consumption e.g. in the WebCrypto API. -// */ - -// export function didToPublicKeyBytes(did: string): { -// publicKey: Uint8Array -// type: KeyType -// } { -// if (!did.startsWith(BASE58_DID_PREFIX)) { -// throw new Error("Please use a base58-encoded DID formatted `did:key:z...`") -// } - -// const didWithoutPrefix = did.slice(BASE58_DID_PREFIX.length) -// const magicBytes = uint8arrays.fromString(didWithoutPrefix, "base58btc") -// const parsed = parseMagicBytes(magicBytes) - -// if (parsed.type === "rsa" && !hasPrefix(magicBytes, RSA_DID_PREFIX_OLD)) { -// // DID RSA keys are ASN.1 DER encoded "RSAPublicKeys" (PKCS #1). -// // But the WebCrypto API mostly works with "SubjectPublicKeyInfo" (SPKI), -// // which wraps RSAPublicKey with some metadata. -// // In an unofficial RSA multiformat we were using, we used SPKI, -// // so we have to be careful not to transform *every* RSA DID to SPKI, but -// // only newer DIDs. -// parsed.keyBytes = rsa.convertRSAPublicKeyToSubjectPublicKeyInfo(parsed.keyBytes) -// } - -// return { -// publicKey: parsed.keyBytes, -// type: parsed.type, -// } -// } - - - -// // PUBLIC KEY → DID - - -// /** -// * Convert a base64 public key to a DID (did:key). -// */ -// export function publicKeyToDid( -// publicKey: string, -// type: KeyType, -// encoding: Encodings = "base64pad" -// ): string { -// const pubKeyBytes = uint8arrays.fromString(publicKey, encoding) -// return publicKeyBytesToDid(pubKeyBytes, type) -// } - -// /** -// * Convert a public key in bytes to a DID (did:key). -// */ -// export function publicKeyBytesToDid( -// publicKeyBytes: Uint8Array, -// type: KeyType, -// ): string { -// // Prefix public-write key -// const prefix = magicBytes(type) -// if (prefix === null) { -// throw new Error(`Key type '${type}' not supported`) -// } - -// if (type === "rsa") { -// // See also the comment in didToPublicKeyBytes -// // In this library, we're assuming a single byte encoding for all types of keys. -// // For RSA that is "SubjectPublicKeyInfo", because that's what the WebCrypto API understands. -// // But DIDs assume that all public keys are encoded as "RSAPublicKey". -// publicKeyBytes = rsa.convertSubjectPublicKeyInfoToRSAPublicKey(publicKeyBytes) -// } - -// if(type === "p256") { -// publicKeyBytes = compression.compressNistP256Pubkey(publicKeyBytes) -// } - -// const prefixedBytes = uint8arrays.concat([ prefix, publicKeyBytes ]) - -// // Encode prefixed -// return BASE58_DID_PREFIX + uint8arrays.toString(prefixedBytes, "base58btc") -// } - -export const thing = 1 \ No newline at end of file diff --git a/packages/core/src/plugins.ts b/packages/core/src/plugins.ts index a0fbb50..4d35400 100644 --- a/packages/core/src/plugins.ts +++ b/packages/core/src/plugins.ts @@ -77,14 +77,16 @@ export const hasPrefix = ( return uint8arrays.equals(prefix, prefixedKey.subarray(0, prefix.byteLength)) } -// @TODO would be better to follow the actual varint spec here: +const BASE58_DID_PREFIX = "did:key:z" + +// @TODO would be better to follow the actual varint spec here (instead of guess & check): // https://github.com/multiformats/unsigned-varint const parsePrefixedBytes = (did: string): Uint8Array => { - if(!did.startsWith("did:key:z")) { + if(!did.startsWith(BASE58_DID_PREFIX)) { throw new Error(`Not a valid base58 formatted did:key: ${did}`) } return uint8arrays.fromString( - did.replace("did:key:z", ""), + did.slice(BASE58_DID_PREFIX.length), "base58btc" ) } diff --git a/packages/core/src/token.ts b/packages/core/src/token.ts index d549125..1bb9a11 100644 --- a/packages/core/src/token.ts +++ b/packages/core/src/token.ts @@ -6,7 +6,7 @@ import * as util from "./util.js" import * as plugins from './plugins.js' import { Capability, isCapability, isEncodedCapability } from "./capability/index.js" -import { Fact, KeyType, Keypair, Didable } from "./types.js" +import { Fact, Keypair, Didable } from "./types.js" import { Ucan, UcanHeader, UcanParts, UcanPayload } from "./types.js" import { handleCompatibility } from "./compatibility.js" @@ -121,23 +121,22 @@ export function buildPayload(params: { } } -// TODO remove keyType here? /** * Encloses a UCAN payload as to form a finalised UCAN. */ export async function sign( payload: UcanPayload, - keyType: KeyType, + jwtAlg: string, signFn: (data: Uint8Array) => Promise ): Promise { const header: UcanHeader = { - alg: jwtAlgorithm(keyType), + alg: jwtAlg, typ: TYPE, ucv: VERSION, } // Issuer key type must match UCAN algorithm - if (!plugins.checkIssuer(payload.iss, jwtAlgorithm(keyType))) { + if (!plugins.checkIssuer(payload.iss, jwtAlg)) { throw new Error("The issuer's key type must match the given key type.") } @@ -168,11 +167,9 @@ export async function signWithKeypair( payload: UcanPayload, keypair: Keypair, ): Promise { - // @TODO FIX THIS return sign( payload, - {} as any, - // keypair.keyType, + keypair.jwtAlg, data => keypair.sign(data), ) } @@ -440,21 +437,4 @@ export function isExpired(ucan: Ucan): boolean { export const isTooEarly = (ucan: Ucan): boolean => { if (ucan.payload.nbf == null) return false return ucan.payload.nbf > Math.floor(Date.now() / 1000) -} - - - -// ㊙️ - - -/** - * JWT algorithm to be used in a JWT header. - */ -function jwtAlgorithm(keyType: KeyType): string { - switch (keyType) { - case "bls12-381": throw new Error(`Unknown KeyType "${keyType}"`) - case "ed25519": return "EdDSA" - case "rsa": return "RS256" - default: throw new Error(`Unknown KeyType "${keyType}"`) - } -} +} \ No newline at end of file diff --git a/packages/core/src/types.ts b/packages/core/src/types.ts index 7cbe5cb..4da97c4 100644 --- a/packages/core/src/types.ts +++ b/packages/core/src/types.ts @@ -66,16 +66,10 @@ export interface ExportableKey { } export interface Keypair { + jwtAlg: string sign: (msg: Uint8Array) => Promise } -// @TODO get rid of this -export type KeyType = - | "rsa" - | "p256" - | "ed25519" - | "bls12-381" - // MISC @@ -88,8 +82,7 @@ export type Encodings = SupportedEncodings export function isKeypair(obj: unknown): obj is Keypair { return util.isRecord(obj) - && util.hasProp(obj, "publicKey") && obj.publicKey instanceof Uint8Array - && util.hasProp(obj, "keyType") && typeof obj.keyType === "string" + && util.hasProp(obj, "jwtAlg") && typeof obj.jwtAlg === "string" && util.hasProp(obj, "sign") && typeof obj.sign === "function" } diff --git a/packages/core/tests/capability/wnfs.test.ts b/packages/core/tests/capability/wnfs.test.ts index 390a0b0..01888b6 100644 --- a/packages/core/tests/capability/wnfs.test.ts +++ b/packages/core/tests/capability/wnfs.test.ts @@ -12,11 +12,6 @@ describe("wnfs public capability", () => { beforeAll(loadTestPlugins) - // @TODO undo - it('passes', () => { - expect(true) - }) - it("works with a simple example", async () => { const { ucan } = await makeSimpleDelegation( [ wnfsCapability("//boris.fission.name/public/Apps/", "OVERWRITE") ], diff --git a/packages/core/tests/token.test.ts b/packages/core/tests/token.test.ts index 4945215..b3fbb79 100644 --- a/packages/core/tests/token.test.ts +++ b/packages/core/tests/token.test.ts @@ -2,7 +2,6 @@ import * as uint8arrays from "uint8arrays" import * as capability from "../src/capability" import * as token from "../src/token" -// import { verifySignatureUtf8 } from "../src/did" import { loadTestPlugins } from "./setup.js" import { alice, bob } from "./fixtures" @@ -168,17 +167,4 @@ describe("token.validate", () => { } expect(token.isTooEarly(activeUcan)).toBe(false) }) -}) - -// @TODO Move this?? -// describe("verifySignatureUtf8", () => { - -// it("works with an example", async () => { -// const [ header, payload, signature ] = token.encode(await token.build({ -// issuer: alice, -// audience: bob.did(), -// })).split(".") -// expect(await verifySignatureUtf8(`${header}.${payload}`, signature, alice.did())).toEqual(true) -// }) - -// }) +}) \ No newline at end of file diff --git a/packages/core/tests/verify.test.ts b/packages/core/tests/verify.test.ts index f9d3f80..7a1ad09 100644 --- a/packages/core/tests/verify.test.ts +++ b/packages/core/tests/verify.test.ts @@ -5,351 +5,349 @@ import { verify } from "../src/verify" import { emailCapability } from "./capability/email" import { alice, bob, mallory } from "./fixtures" import { REDELEGATE } from "../src/capability/ability" +import { loadTestPlugins } from "./setup" describe("verify", () => { - // @TODO undo - it('passes', () => { - expect(true) + beforeAll(loadTestPlugins) + + async function aliceEmailDelegationExample(expiration?: number) { + // alice -> bob, bob -> mallory + // alice delegates access to sending email as her to bob + // and bob delegates it further to mallory + const leafUcan = await token.build({ + issuer: alice, + audience: bob.did(), + expiration, + capabilities: [ emailCapability("alice@email.com") ] + }) + + const ucan = await token.build({ + issuer: bob, + audience: mallory.did(), + expiration, + capabilities: [ emailCapability("alice@email.com") ], + proofs: [ token.encode(leafUcan) ] + }) + + return token.encode(ucan) + } + + const alicesEmail = { + capability: emailCapability("alice@email.com"), + rootIssuer: alice.did(), + } + + it("verifies a delegation chain", async () => { + const ucan = await aliceEmailDelegationExample() + + const result = await verify(ucan, { + audience: mallory.did(), + requiredCapabilities: [ alicesEmail ] + }) + + if (result.ok === false) { + console.log(result.error) + } + + expect(result.ok).toEqual(true) + + if (!result.ok) return + + expect(result.value[ 0 ]?.rootIssuer).toEqual(alice.did()) + expect(result.value[ 0 ]?.capability).toEqual(emailCapability("alice@email.com")) }) - // async function aliceEmailDelegationExample(expiration?: number) { - // // alice -> bob, bob -> mallory - // // alice delegates access to sending email as her to bob - // // and bob delegates it further to mallory - // const leafUcan = await token.build({ - // issuer: alice, - // audience: bob.did(), - // expiration, - // capabilities: [ emailCapability("alice@email.com") ] - // }) - - // const ucan = await token.build({ - // issuer: bob, - // audience: mallory.did(), - // expiration, - // capabilities: [ emailCapability("alice@email.com") ], - // proofs: [ token.encode(leafUcan) ] - // }) - - // return token.encode(ucan) - // } - - // const alicesEmail = { - // capability: emailCapability("alice@email.com"), - // rootIssuer: alice.did(), - // } - - // it("verifies a delegation chain", async () => { - // const ucan = await aliceEmailDelegationExample() - - // const result = await verify(ucan, { - // audience: mallory.did(), - // requiredCapabilities: [ alicesEmail ] - // }) - - // if (result.ok === false) { - // console.log(result.error) - // } - - // expect(result.ok).toEqual(true) - - // if (!result.ok) return - - // expect(result.value[ 0 ]?.rootIssuer).toEqual(alice.did()) - // expect(result.value[ 0 ]?.capability).toEqual(emailCapability("alice@email.com")) - // }) - - // it("rejects an invalid escalation", async () => { - // const ucan = await aliceEmailDelegationExample() - - // const result = await verify(ucan, { - // audience: mallory.did(), - // requiredCapabilities: [ { - // capability: { - // ...emailCapability("alice@email.com"), - // can: SUPERUSER, - // }, - // rootIssuer: alice.did() - // } ] - // }) - - // expect(result.ok).toEqual(false) - // }) - - // it("rejects for an invalid audience", async () => { - // const ucan = await aliceEmailDelegationExample() - - // const result = await verify(ucan, { - // audience: bob.did(), - // requiredCapabilities: [ alicesEmail ] - // }) - - // expect(result.ok).toEqual(false) - // }) - - // it("rejects for an invalid rootIssuer", async () => { - // const ucan = await aliceEmailDelegationExample() - - // const result = await verify(ucan, { - // audience: mallory.did(), - // requiredCapabilities: [ { - // capability: emailCapability("alice@email.com"), - // // an invalid rootIssuer - // rootIssuer: "did:someone-else", - // } ] - // }) - - // expect(result.ok).toEqual(false) - // }) - - // it("rejects for an expired capability", async () => { - // // unix timestamp in seconds. Will be after - // const nowInSeconds = Math.floor(Date.now() / 1000) - // // expiry is in the past - // const ucan = await aliceEmailDelegationExample(nowInSeconds - 60 * 60 * 24) - - // const result = await verify(ucan, { - // audience: mallory.did(), - // requiredCapabilities: [ alicesEmail ] - // }) - - // expect(result.ok).toEqual(false) - // }) - - // it("supports redelegation with a `prf:*` capability", async () => { - // // alice -> bob - // // alice delegates access to sending email as her to bob - // // and bob delegates it further to mallory - // const leafUcan = await token.build({ - // issuer: alice, - // audience: bob.did(), - // capabilities: [ emailCapability("alice@email.com") ] - // }) - - // const ucan = await token.build({ - // issuer: bob, - // audience: mallory.did(), - // capabilities: [ capability.prf(capability.superUser.SUPERUSER, REDELEGATE) ], - // proofs: [ token.encode(leafUcan) ] - // }) - - // const result = await verify(token.encode(ucan), { - // audience: mallory.did(), - // requiredCapabilities: [ alicesEmail ] - // }) - - // expect(result.ok).toEqual(true) - - // if (!result.ok) return - - // expect(result.value[ 0 ]?.rootIssuer).toEqual(alice.did()) - // expect(result.value[ 0 ]?.capability).toEqual(emailCapability("alice@email.com")) - // }) - - // it("supports redelegation with a `prf:1` capability", async () => { - // // alice -> bob - // // alice delegates access to sending email as her to bob - // // and bob delegates it further to mallory - // const leafUcanA = await token.build({ - // issuer: alice, - // audience: bob.did(), - // capabilities: [ emailCapability("ignore-me@email.com") ] - // }) - - // const leafUcanB = await token.build({ - // issuer: alice, - // audience: bob.did(), - // capabilities: [ emailCapability("alice@email.com") ] - // }) - - // const ucan = await token.build({ - // issuer: bob, - // audience: mallory.did(), - // capabilities: [ capability.prf(1, REDELEGATE) ], - // proofs: [ token.encode(leafUcanA), token.encode(leafUcanB) ] - // }) - - // const result = await verify(token.encode(ucan), { - // audience: mallory.did(), - // requiredCapabilities: [ alicesEmail ] - // }) - - // expect(result.ok).toEqual(true) - - // if (!result.ok) return - - // expect(result.value[ 0 ]?.rootIssuer).toEqual(alice.did()) - // expect(result.value[ 0 ]?.capability).toEqual(emailCapability("alice@email.com")) - // }) - - // it("ignores other proofs not referred to by `prf:0`", async () => { - // const leafUcanA = await token.build({ - // issuer: alice, - // audience: bob.did(), - // capabilities: [ emailCapability("ignore-me@email.com") ] - // }) - - // const leafUcanB = await token.build({ - // issuer: alice, - // audience: bob.did(), - // capabilities: [ emailCapability("alice@email.com") ] - // }) - - // const faultyUcan = await token.build({ - // issuer: bob, - // audience: mallory.did(), - // capabilities: [ capability.prf(0, REDELEGATE) ], - // proofs: [ token.encode(leafUcanA), token.encode(leafUcanB) ] - // }) - - // const result = await verify(token.encode(faultyUcan), { - // audience: mallory.did(), - // requiredCapabilities: [ alicesEmail ] - // }) - - // expect(result.ok).toEqual(false) - // }) - - // it("rejects an improper `prf` redelegation", async () => { - // // alice -> bob - // // alice delegates access to sending email as her to bob - // // and bob delegates it further to mallory - // const leafUcan = await token.build({ - // issuer: alice, - // audience: bob.did(), - // capabilities: [ emailCapability("invalid@email.com") ] - // }) - - // const ucan = await token.build({ - // issuer: bob, - // audience: mallory.did(), - // capabilities: [ capability.prf(capability.superUser.SUPERUSER, REDELEGATE) ], - // proofs: [ token.encode(leafUcan) ] - // }) - - // const result = await verify(token.encode(ucan), { - // audience: mallory.did(), - // requiredCapabilities: [ alicesEmail ] - // }) - - // expect(result.ok).toEqual(false) - // }) - - // it("supports redelegation with a `my` capability", async () => { - // // alice -> bob - // // alice delegates access to sending email as her to bob - // // and bob delegates it further to mallory - // const leafUcan = await token.build({ - // issuer: alice, - // audience: bob.did(), - // capabilities: [ capability.my(capability.superUser.SUPERUSER) ] - // }) - - // const ucan = await token.build({ - // issuer: bob, - // audience: mallory.did(), - // capabilities: [ emailCapability("alice@email.com") ], - // proofs: [ token.encode(leafUcan) ] - // }) - - // const result = await verify(token.encode(ucan), { - // audience: mallory.did(), - // requiredCapabilities: [ alicesEmail ], - // }) - - // expect(result.ok).toEqual(true) - - // if (!result.ok) return - - // expect(result.value[ 0 ]?.rootIssuer).toEqual(alice.did()) - // }) - - // it("supports redelegation with a `my` & `as` capability", async () => { - // // alice -> bob, bob -> mallory, mallory -> "someone" - // // alice delegates access to sending email as her to bob - // // and bob delegates it further to mallory - // const leafUcan = await token.build({ - // issuer: alice, - // audience: bob.did(), - // capabilities: [ capability.my(capability.superUser.SUPERUSER) ] - // }) - - // const middleUcan = await token.build({ - // issuer: bob, - // audience: mallory.did(), - // capabilities: [ capability.as(alice.did(), SUPERUSER) ], - // proofs: [ token.encode(leafUcan) ] - // }) - - // const ucan = await token.build({ - // issuer: mallory, - // audience: "did:key:someone", - // capabilities: [ emailCapability("alice@email.com") ], - // proofs: [ token.encode(middleUcan) ] - // }) - - // const result = await verify(token.encode(ucan), { - // audience: "did:key:someone", - // requiredCapabilities: [ alicesEmail ] - // }) - - // expect(result.ok).toEqual(true) - - // if (!result.ok) return - - // expect(result.value[ 0 ]?.rootIssuer).toEqual(alice.did()) - // }) - - // it("rejects an improper `my` redelegation", async () => { - // // alice -> bob - // // alice delegates access to sending email as her to bob - // // and bob delegates it further to mallory - // const leafUcan = await token.build({ - // issuer: alice, - // audience: bob.did(), - // capabilities: [] - // }) - - // const ucan = await token.build({ - // issuer: bob, - // audience: mallory.did(), - // capabilities: [ emailCapability("alice@email.com") ], - // proofs: [ token.encode(leafUcan) ] - // }) - - // const result = await verify(token.encode(ucan), { - // audience: mallory.did(), - // requiredCapabilities: [ alicesEmail ] - // }) - - // expect(result.ok).toEqual(false) - // }) - - // it("rejects an improper `as` redelegation - no `my`", async () => { - // // alice -> bob - // // alice delegates access to sending email as her to bob - // // and bob delegates it further to mallory - // const leafUcan = await token.build({ - // issuer: alice, - // audience: bob.did(), - // capabilities: [ capability.as(bob.did(), SUPERUSER) ] - // }) - - // const ucan = await token.build({ - // issuer: bob, - // audience: mallory.did(), - // capabilities: [ emailCapability("alice@email.com") ], - // proofs: [ token.encode(leafUcan) ] - // }) - - // const result = await verify(token.encode(ucan), { - // audience: mallory.did(), - // requiredCapabilities: [ alicesEmail ] - // }) - - // expect(result.ok).toEqual(false) - // }) + it("rejects an invalid escalation", async () => { + const ucan = await aliceEmailDelegationExample() + + const result = await verify(ucan, { + audience: mallory.did(), + requiredCapabilities: [ { + capability: { + ...emailCapability("alice@email.com"), + can: SUPERUSER, + }, + rootIssuer: alice.did() + } ] + }) + + expect(result.ok).toEqual(false) + }) + + it("rejects for an invalid audience", async () => { + const ucan = await aliceEmailDelegationExample() + + const result = await verify(ucan, { + audience: bob.did(), + requiredCapabilities: [ alicesEmail ] + }) + + expect(result.ok).toEqual(false) + }) + + it("rejects for an invalid rootIssuer", async () => { + const ucan = await aliceEmailDelegationExample() + + const result = await verify(ucan, { + audience: mallory.did(), + requiredCapabilities: [ { + capability: emailCapability("alice@email.com"), + // an invalid rootIssuer + rootIssuer: "did:someone-else", + } ] + }) + + expect(result.ok).toEqual(false) + }) + + it("rejects for an expired capability", async () => { + // unix timestamp in seconds. Will be after + const nowInSeconds = Math.floor(Date.now() / 1000) + // expiry is in the past + const ucan = await aliceEmailDelegationExample(nowInSeconds - 60 * 60 * 24) + + const result = await verify(ucan, { + audience: mallory.did(), + requiredCapabilities: [ alicesEmail ] + }) + + expect(result.ok).toEqual(false) + }) + + it("supports redelegation with a `prf:*` capability", async () => { + // alice -> bob + // alice delegates access to sending email as her to bob + // and bob delegates it further to mallory + const leafUcan = await token.build({ + issuer: alice, + audience: bob.did(), + capabilities: [ emailCapability("alice@email.com") ] + }) + + const ucan = await token.build({ + issuer: bob, + audience: mallory.did(), + capabilities: [ capability.prf(capability.superUser.SUPERUSER, REDELEGATE) ], + proofs: [ token.encode(leafUcan) ] + }) + + const result = await verify(token.encode(ucan), { + audience: mallory.did(), + requiredCapabilities: [ alicesEmail ] + }) + + expect(result.ok).toEqual(true) + + if (!result.ok) return + + expect(result.value[ 0 ]?.rootIssuer).toEqual(alice.did()) + expect(result.value[ 0 ]?.capability).toEqual(emailCapability("alice@email.com")) + }) + + it("supports redelegation with a `prf:1` capability", async () => { + // alice -> bob + // alice delegates access to sending email as her to bob + // and bob delegates it further to mallory + const leafUcanA = await token.build({ + issuer: alice, + audience: bob.did(), + capabilities: [ emailCapability("ignore-me@email.com") ] + }) + + const leafUcanB = await token.build({ + issuer: alice, + audience: bob.did(), + capabilities: [ emailCapability("alice@email.com") ] + }) + + const ucan = await token.build({ + issuer: bob, + audience: mallory.did(), + capabilities: [ capability.prf(1, REDELEGATE) ], + proofs: [ token.encode(leafUcanA), token.encode(leafUcanB) ] + }) + + const result = await verify(token.encode(ucan), { + audience: mallory.did(), + requiredCapabilities: [ alicesEmail ] + }) + + expect(result.ok).toEqual(true) + + if (!result.ok) return + + expect(result.value[ 0 ]?.rootIssuer).toEqual(alice.did()) + expect(result.value[ 0 ]?.capability).toEqual(emailCapability("alice@email.com")) + }) + + it("ignores other proofs not referred to by `prf:0`", async () => { + const leafUcanA = await token.build({ + issuer: alice, + audience: bob.did(), + capabilities: [ emailCapability("ignore-me@email.com") ] + }) + + const leafUcanB = await token.build({ + issuer: alice, + audience: bob.did(), + capabilities: [ emailCapability("alice@email.com") ] + }) + + const faultyUcan = await token.build({ + issuer: bob, + audience: mallory.did(), + capabilities: [ capability.prf(0, REDELEGATE) ], + proofs: [ token.encode(leafUcanA), token.encode(leafUcanB) ] + }) + + const result = await verify(token.encode(faultyUcan), { + audience: mallory.did(), + requiredCapabilities: [ alicesEmail ] + }) + + expect(result.ok).toEqual(false) + }) + + it("rejects an improper `prf` redelegation", async () => { + // alice -> bob + // alice delegates access to sending email as her to bob + // and bob delegates it further to mallory + const leafUcan = await token.build({ + issuer: alice, + audience: bob.did(), + capabilities: [ emailCapability("invalid@email.com") ] + }) + + const ucan = await token.build({ + issuer: bob, + audience: mallory.did(), + capabilities: [ capability.prf(capability.superUser.SUPERUSER, REDELEGATE) ], + proofs: [ token.encode(leafUcan) ] + }) + + const result = await verify(token.encode(ucan), { + audience: mallory.did(), + requiredCapabilities: [ alicesEmail ] + }) + + expect(result.ok).toEqual(false) + }) + + it("supports redelegation with a `my` capability", async () => { + // alice -> bob + // alice delegates access to sending email as her to bob + // and bob delegates it further to mallory + const leafUcan = await token.build({ + issuer: alice, + audience: bob.did(), + capabilities: [ capability.my(capability.superUser.SUPERUSER) ] + }) + + const ucan = await token.build({ + issuer: bob, + audience: mallory.did(), + capabilities: [ emailCapability("alice@email.com") ], + proofs: [ token.encode(leafUcan) ] + }) + + const result = await verify(token.encode(ucan), { + audience: mallory.did(), + requiredCapabilities: [ alicesEmail ], + }) + + expect(result.ok).toEqual(true) + + if (!result.ok) return + + expect(result.value[ 0 ]?.rootIssuer).toEqual(alice.did()) + }) + + it("supports redelegation with a `my` & `as` capability", async () => { + // alice -> bob, bob -> mallory, mallory -> "someone" + // alice delegates access to sending email as her to bob + // and bob delegates it further to mallory + const leafUcan = await token.build({ + issuer: alice, + audience: bob.did(), + capabilities: [ capability.my(capability.superUser.SUPERUSER) ] + }) + + const middleUcan = await token.build({ + issuer: bob, + audience: mallory.did(), + capabilities: [ capability.as(alice.did(), SUPERUSER) ], + proofs: [ token.encode(leafUcan) ] + }) + + const ucan = await token.build({ + issuer: mallory, + audience: "did:key:someone", + capabilities: [ emailCapability("alice@email.com") ], + proofs: [ token.encode(middleUcan) ] + }) + + const result = await verify(token.encode(ucan), { + audience: "did:key:someone", + requiredCapabilities: [ alicesEmail ] + }) + + expect(result.ok).toEqual(true) + + if (!result.ok) return + + expect(result.value[ 0 ]?.rootIssuer).toEqual(alice.did()) + }) + + it("rejects an improper `my` redelegation", async () => { + // alice -> bob + // alice delegates access to sending email as her to bob + // and bob delegates it further to mallory + const leafUcan = await token.build({ + issuer: alice, + audience: bob.did(), + capabilities: [] + }) + + const ucan = await token.build({ + issuer: bob, + audience: mallory.did(), + capabilities: [ emailCapability("alice@email.com") ], + proofs: [ token.encode(leafUcan) ] + }) + + const result = await verify(token.encode(ucan), { + audience: mallory.did(), + requiredCapabilities: [ alicesEmail ] + }) + + expect(result.ok).toEqual(false) + }) + + it("rejects an improper `as` redelegation - no `my`", async () => { + // alice -> bob + // alice delegates access to sending email as her to bob + // and bob delegates it further to mallory + const leafUcan = await token.build({ + issuer: alice, + audience: bob.did(), + capabilities: [ capability.as(bob.did(), SUPERUSER) ] + }) + + const ucan = await token.build({ + issuer: bob, + audience: mallory.did(), + capabilities: [ emailCapability("alice@email.com") ], + proofs: [ token.encode(leafUcan) ] + }) + + const result = await verify(token.encode(ucan), { + audience: mallory.did(), + requiredCapabilities: [ alicesEmail ] + }) + + expect(result.ok).toEqual(false) + }) }) diff --git a/packages/plugins/src/ed25519/keypair.ts b/packages/plugins/src/ed25519/keypair.ts index 3bba1a3..51a6113 100644 --- a/packages/plugins/src/ed25519/keypair.ts +++ b/packages/plugins/src/ed25519/keypair.ts @@ -7,6 +7,8 @@ import { Didable, Encodings, ExportableKey, Keypair } from "@ucans/core" export class EdKeypair implements Keypair, Didable, ExportableKey { + public jwtAlg = "EdDSA" + private secretKey: Uint8Array private publicKey: Uint8Array private exportable: boolean diff --git a/packages/plugins/src/p256/keypair.ts b/packages/plugins/src/p256/keypair.ts index bacbcad..b831ce5 100644 --- a/packages/plugins/src/p256/keypair.ts +++ b/packages/plugins/src/p256/keypair.ts @@ -12,6 +12,8 @@ import { export class EcdsaKeypair implements Keypair, Didable, ExportableKey { + public jwtAlg = "ES256" + private publicKey: Uint8Array private keypair: AvailableCryptoKeyPair private exportable: boolean diff --git a/packages/plugins/src/rsa/keypair.ts b/packages/plugins/src/rsa/keypair.ts index 741280d..c303b38 100644 --- a/packages/plugins/src/rsa/keypair.ts +++ b/packages/plugins/src/rsa/keypair.ts @@ -8,6 +8,8 @@ import { Didable, Encodings, ExportableKey, Keypair } from "@ucans/core" export class RsaKeypair implements Keypair, Didable, ExportableKey { + public jwtAlg = "RS256" + private publicKey: Uint8Array private keypair: AvailableCryptoKeyPair private exportable: boolean diff --git a/packages/plugins/src/types.ts b/packages/plugins/src/types.ts index 082388f..23489be 100644 --- a/packages/plugins/src/types.ts +++ b/packages/plugins/src/types.ts @@ -10,12 +10,6 @@ export type PublicKeyJwk = { y: string } -export type KeyType = - | "rsa" - | "p256" - | "ed25519" - | "bls12-381" - export type PrivateKeyJwk = PublicKeyJwk & { d: string } diff --git a/packages/plugins/tests/ecdsa.test.ts b/packages/plugins/tests/ecdsa.test.ts index 3d74917..a1aa69b 100644 --- a/packages/plugins/tests/ecdsa.test.ts +++ b/packages/plugins/tests/ecdsa.test.ts @@ -1,17 +1,18 @@ -import * as did from "../src/did" -import ECDSAKeyPair from "../src/keypair/ecdsa" +import { p256Plugin } from "../src/p256/plugin.js" +// import * as did from "../src/did" +import EcdsaKeyPair from "../src/p256/keypair.js" describe("ecdsa", () => { - let p256Keypair: ECDSAKeyPair + let p256Keypair: EcdsaKeyPair let p256Signature: Uint8Array const data = new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8, 9]) it("creates an ecdsa keypairs with different curves", async () => { - p256Keypair = await ECDSAKeyPair.create() + p256Keypair = await EcdsaKeyPair.create() }) it("returns a publicKeyStr and did: curve P-256", () => { diff --git a/packages/ucans/src/index.ts b/packages/ucans/src/index.ts index 9d9e80f..6f22420 100644 --- a/packages/ucans/src/index.ts +++ b/packages/ucans/src/index.ts @@ -1,19 +1,7 @@ -export * from "@ucans/core" -export * from "@ucans/defaults" - - +import { loadPlugins } from "@ucans/core" +import * as plugins from "@ucans/plugins" -// export * from "./attenuation.js" -// export * from "./builder.js" -// export * from "./did.js" -// export * from "./keypair/ed25519.js" -// export * from "./keypair/rsa.js" -// export * from "./store.js" -// export * from "./token.js" -// export * from "./types.js" -// export * from "./verify.js" +loadPlugins(plugins.defaults) -// export * as keypair from "./keypair/index.js" -// export * as capability from "./capability/index.js" - -// export { Capability, EncodedCapability, isCapability } from "./capability/index.js" \ No newline at end of file +export * from "@ucans/core" +export * from "@ucans/plugins" \ No newline at end of file diff --git a/tests-copy/attenuation.test.ts b/tests-copy/attenuation.test.ts deleted file mode 100644 index a798d30..0000000 --- a/tests-copy/attenuation.test.ts +++ /dev/null @@ -1,155 +0,0 @@ -import * as token from "../src/token" -import { emailCapabilities, emailCapability } from "./capability/email" - -import { alice, bob, mallory } from "./fixtures" -import { all } from "../src/util" - - -describe("attenuation.emailCapabilities", () => { - - it("works with a simple example", async () => { - // alice -> bob, bob -> mallory - // alice delegates access to sending email as her to bob - // and bob delegates it further to mallory - const leafUcan = await token.build({ - issuer: alice, - audience: bob.did(), - capabilities: [ emailCapability("alice@email.com") ] - }) - - const ucan = await token.build({ - issuer: bob, - audience: mallory.did(), - capabilities: [ emailCapability("alice@email.com") ], - proofs: [ token.encode(leafUcan) ] - }) - - expect(await all(emailCapabilities(ucan))).toEqual([ - { - rootIssuer: bob.did(), - capability: emailCapability("alice@email.com") - }, - { - rootIssuer: alice.did(), - capability: emailCapability("alice@email.com") - } - ]) - }) - - it("reports the first issuer in the chain as originator", async () => { - // alice -> bob, bob -> mallory - // alice delegates nothing to bob - // and bob delegates his email to mallory - const leafUcan = await token.build({ - issuer: alice, - audience: bob.did(), - }) - - const ucan = await token.build({ - issuer: bob, - audience: mallory.did(), - capabilities: [ emailCapability("bob@email.com") ], - proofs: [ token.encode(leafUcan) ] - }) - - // we implicitly expect the originator to become bob - expect(await all(emailCapabilities(ucan))).toEqual([ { - rootIssuer: bob.did(), - capability: emailCapability("bob@email.com"), - } ]) - }) - - it("finds the right proof chain for the originator", async () => { - // alice -> mallory, bob -> mallory, mallory -> alice - // both alice and bob delegate their email access to mallory - // mallory then creates a UCAN with capability to send both - const leafUcanAlice = await token.build({ - issuer: alice, - audience: mallory.did(), - capabilities: [ emailCapability("alice@email.com") ] - }) - - const leafUcanBob = await token.build({ - issuer: bob, - audience: mallory.did(), - capabilities: [ emailCapability("bob@email.com") ] - }) - - const ucan = await token.build({ - issuer: mallory, - audience: alice.did(), - capabilities: [ - emailCapability("alice@email.com"), - emailCapability("bob@email.com") - ], - proofs: [ token.encode(leafUcanAlice), token.encode(leafUcanBob) ] - }) - - const chains = await all(emailCapabilities(ucan)) - - expect(chains).toEqual([ - // We expect two capabilities from parenthood: - { - rootIssuer: mallory.did(), - capability: emailCapability("alice@email.com") - }, - { - rootIssuer: mallory.did(), - capability: emailCapability("bob@email.com") - }, - // Then there's also the delegations - { - rootIssuer: alice.did(), - capability: emailCapability("alice@email.com") - }, - { - rootIssuer: bob.did(), - capability: emailCapability("bob@email.com") - } - ]) - }) - - it("reports all chain options", async () => { - // alice -> mallory, bob -> mallory, mallory -> alice - // both alice and bob claim to have access to alice@email.com - // and both grant that capability to mallory - // a verifier needs to know both to verify valid email access - - const aliceEmail = emailCapability("alice@email.com") - - const leafUcanAlice = await token.build({ - issuer: alice, - audience: mallory.did(), - capabilities: [ aliceEmail ] - }) - - const leafUcanBob = await token.build({ - issuer: bob, - audience: mallory.did(), - capabilities: [ aliceEmail ] - }) - - const ucan = await token.build({ - issuer: mallory, - audience: alice.did(), - capabilities: [ aliceEmail ], - proofs: [ token.encode(leafUcanAlice), token.encode(leafUcanBob) ] - }) - - expect(await all(emailCapabilities(ucan))).toEqual([ - { - rootIssuer: mallory.did(), - capability: aliceEmail - }, - { - rootIssuer: alice.did(), - capability: aliceEmail - }, - { - rootIssuer: bob.did(), - capability: aliceEmail - } - ]) - }) - -}) diff --git a/tests-copy/builder.test.ts b/tests-copy/builder.test.ts deleted file mode 100644 index 144ec0f..0000000 --- a/tests-copy/builder.test.ts +++ /dev/null @@ -1,135 +0,0 @@ -import * as token from "../src/token" -import { Builder } from "../src/builder" -import { emailCapability } from "./capability/email" -import { wnfsCapability, wnfsPublicSemantics } from "./capability/wnfs" -import { EMAIL_SEMANTICS } from "./capability/email" -import { alice, bob, mallory } from "./fixtures" -import { delegationChains } from "../src/attenuation" -import { first } from "../src/util" - - -describe("Builder", () => { - - it("builds with a simple example", async () => { - const fact1 = { test: true } - const fact2 = { preimage: "abc", hash: "sth" } - const cap1 = emailCapability("alice@email.com") - const cap2 = wnfsCapability("alice.fission.name/public/", "SUPER_USER") - const expiration = Math.floor(Date.now() / 1000) + 30 - const notBefore = Math.floor(Date.now() / 1000) - 30 - - const ucan = await Builder.create() - .issuedBy(alice) - .toAudience(bob.did()) - .withExpiration(expiration) - .withNotBefore(notBefore) - .withFact(fact1, fact2) - .withNonce() - .claimCapability(cap1, cap2) - .build() - - expect(ucan.payload.iss).toEqual(alice.did()) - expect(ucan.payload.aud).toEqual(bob.did()) - expect(ucan.payload.exp).toEqual(expiration) - expect(ucan.payload.nbf).toEqual(notBefore) - expect(ucan.payload.fct).toEqual([ fact1, fact2 ]) - expect(ucan.payload.att).toEqual([ cap1, cap2 ]) - expect(ucan.payload.nnc).toBeDefined() - }) - - it("builds with lifetimeInSeconds", async () => { - const payload = Builder.create() - .issuedBy(alice) - .toAudience(bob.did()) - .withLifetimeInSeconds(300) - .buildPayload() - - expect(payload.exp).toBeGreaterThan(Date.now() / 1000 + 290) - }) - - it("prevents duplicate proofs", async () => { - const ucan = await Builder.create() - .issuedBy(alice) - .toAudience(bob.did()) - .withLifetimeInSeconds(30) - .claimCapability(wnfsCapability("alice.fission.name/public/", "SUPER_USER")) - .build() - - const publicCapability = await first(delegationChains(wnfsPublicSemantics, ucan)) - - if (publicCapability == null) { - throw "no capabilities" - } - - if (publicCapability instanceof Error) { - throw publicCapability - } - - const payload = Builder.create() - .issuedBy(bob) - .toAudience(mallory.did()) - .withLifetimeInSeconds(30) - .delegateCapability(wnfsCapability("alice.fission.name/public/Apps", "CREATE"), publicCapability, wnfsPublicSemantics) - .delegateCapability(wnfsCapability("alice.fission.name/public/Documents", "OVERWRITE"), publicCapability, wnfsPublicSemantics) - .buildPayload() - - expect(payload.prf).toEqual([ token.encode(ucan) ]) - }) - - it("throws when it's not ready to be built", () => { - expect(() => { - Builder.create() - .buildPayload() - }).toThrow() - // issuer missing - expect(() => { - Builder.create() - .toAudience(bob.did()) - .withLifetimeInSeconds(1) - .buildPayload() - }).toThrow() - // audience missing - expect(() => { - Builder.create() - .issuedBy(alice) - .withLifetimeInSeconds(1) - .buildPayload() - }).toThrow() - // expiration missing - expect(() => { - Builder.create() - .issuedBy(alice) - .toAudience(bob.did()) - .buildPayload() - }).toThrow() - }) - - it("throws when trying to delegate unproven capabilities", async () => { - const ucan = await Builder.create() - .issuedBy(alice) - .toAudience(bob.did()) - .withLifetimeInSeconds(30) - .claimCapability(emailCapability("alice@email.com")) - .build() - - const delegationChain = await first(delegationChains(EMAIL_SEMANTICS, ucan)) - - if (delegationChain == null) { - throw "no capabilities" - } - - if (delegationChain instanceof Error) { - throw delegationChain - } - - expect(() => { - Builder.create() - .issuedBy(bob) - .toAudience(mallory.did()) - .withLifetimeInSeconds(30) - .delegateCapability(emailCapability("bob@email.com"), delegationChain, EMAIL_SEMANTICS) - .buildPayload() - }).toThrow() - }) - -}) diff --git a/tests-copy/capability.test.ts b/tests-copy/capability.test.ts deleted file mode 100644 index 671469c..0000000 --- a/tests-copy/capability.test.ts +++ /dev/null @@ -1,22 +0,0 @@ -import * as capability from "../src/capability" - - -describe("capability.isEqual", () => { - - it("is able to compare two equal capabilities", () => { - const a = { - with: { scheme: "scheme", hierPart: "hierPart" }, - can: { namespace: "namespace", segments: [ "a", "B" ] } - } - - const b = { - with: { scheme: "SCHEME", hierPart: "hierPart" }, - can: { namespace: "NAMESPACE", segments: [ "A", "b" ] } - } - - expect(capability.isEqual(a, b)).toBe(true) - expect(capability.resourcePointer.isEqual(a.with, b.with)).toBe(true) - expect(capability.ability.isEqual(a.can, b.can)).toBe(true) - }) - -}) \ No newline at end of file diff --git a/tests-copy/capability/email.ts b/tests-copy/capability/email.ts deleted file mode 100644 index 63ad868..0000000 --- a/tests-copy/capability/email.ts +++ /dev/null @@ -1,82 +0,0 @@ -import { Ucan } from "../../src/types" -import { DelegationSemantics, delegationChains, rootIssuer } from "../../src/attenuation" -import { Ability } from "../../src/capability/ability" -import { Capability } from "../../src/capability" -import { SUPERUSER } from "../../src/capability/super-user" -import { ResourcePointer } from "../../src/capability/resource-pointer" - - -// 🌸 - - -export interface EmailCapability { - with: ResourcePointer - can: Ability -} - - - -// 🏔 - - -export const SEND_ABILITY: Ability = { namespace: "msg", segments: [ "SEND" ] } - - -export const EMAIL_SEMANTICS: DelegationSemantics = { - - canDelegateResource(parentResource, resource) { - if (parentResource.scheme !== "email") { - return false - } - if (resource.scheme !== "email") { - return false - } - return parentResource.hierPart === resource.hierPart - }, - - canDelegateAbility(parentAbility, ability) { - if (parentAbility === SUPERUSER) { - return true - } - if (ability === SUPERUSER) { - return false - } - return parentAbility.namespace === "msg" - && parentAbility.segments.length === 1 - && parentAbility.segments[0] === "SEND" - && ability.namespace === "msg" - && ability.segments.length === 1 - && ability.segments[0] === "SEND" - } - -} - - - -// 🛠 - - -export function emailResourcePointer(emailAddress: string): ResourcePointer { - return { scheme: "email", hierPart: emailAddress } -} - - -export function emailCapability(emailAddress: string): Capability { - return { - with: emailResourcePointer(emailAddress), - can: SEND_ABILITY - } -} - - -export async function * emailCapabilities(ucan: Ucan): AsyncIterable<{ capability: EmailCapability; rootIssuer: string }> { - for await (const delegationChain of delegationChains(EMAIL_SEMANTICS, ucan)) { - if (delegationChain instanceof Error || "ownershipDID" in delegationChain) { - continue - } - yield { - rootIssuer: rootIssuer(delegationChain), - capability: delegationChain.capability - } - } -} diff --git a/tests-copy/capability/wnfs.test.ts b/tests-copy/capability/wnfs.test.ts deleted file mode 100644 index 58f9bdb..0000000 --- a/tests-copy/capability/wnfs.test.ts +++ /dev/null @@ -1,238 +0,0 @@ -import * as token from "../../src/token" -import { Capability } from "../../src/capability" -import { wnfsCapability, wnfsPrivateCapabilities, wnfsPublicCapabilities } from "./wnfs" - -import { alice, bob, mallory } from "../fixtures" -import { all } from "../../src/util" - - - -describe("wnfs public capability", () => { - - it("works with a simple example", async () => { - const { ucan } = await makeSimpleDelegation( - [ wnfsCapability("//boris.fission.name/public/Apps/", "OVERWRITE") ], - [ wnfsCapability("//boris.fission.name/public/Apps/appinator/", "REVISE") ] - ) - - expect(await all(wnfsPublicCapabilities(ucan))).toEqual([ - { - rootIssuer: bob.did(), - capability: wnfsCapability("//boris.fission.name/public/Apps/appinator/", "REVISE") - }, - { - rootIssuer: alice.did(), - capability: wnfsCapability("//boris.fission.name/public/Apps/appinator/", "REVISE") - } - ]) - }) - - it("detects capability escalations", async () => { - const { ucan } = await makeSimpleDelegation( - [ wnfsCapability("//boris.fission.name/public/Apps/", "CREATE") ], - [ wnfsCapability("//boris.fission.name/public/Apps/appinator/", "OVERWRITE") ] - ) - - expect(await all(wnfsPublicCapabilities(ucan))).toEqual([ - { - rootIssuer: bob.did(), - capability: wnfsCapability("//boris.fission.name/public/Apps/appinator/", "OVERWRITE") - } - ]) - }) - - it("detects capability escalations, even if there's valid capabilities", async () => { - const { ucan } = await makeSimpleDelegation( - [ - wnfsCapability("//boris.fission.name/public/Apps/", "CREATE"), - wnfsCapability("//boris.fission.name/public/Apps/", "SUPER_USER") - ], - [ wnfsCapability("//boris.fission.name/public/Apps/appinator/", "OVERWRITE") - ] - ) - - expect(await all(wnfsPublicCapabilities(ucan))).toEqual([ - { - rootIssuer: bob.did(), - capability: wnfsCapability("//boris.fission.name/public/Apps/appinator/", "OVERWRITE") - }, - { - rootIssuer: alice.did(), - capability: wnfsCapability("//boris.fission.name/public/Apps/appinator/", "OVERWRITE") - } - ]) - }) - -}) - -describe("wnfs private capability", () => { - - it("works with a simple example", async () => { - const { ucan } = await makeSimpleDelegation( - [ wnfsCapability("//boris.fission.name/private/abc", "OVERWRITE") ], - [ wnfsCapability("//boris.fission.name/private/def", "REVISE") ] - ) - - expect(await all(wnfsPrivateCapabilities(ucan))).toEqual([ - { - rootIssuer: bob.did(), - capability: wnfsCapability("//boris.fission.name/private/def", "REVISE"), - requiredINumbers: new Set(["def"]) - }, - { - rootIssuer: alice.did(), - capability: wnfsCapability("//boris.fission.name/private/def", "REVISE"), - requiredINumbers: new Set(["abc", "def"]) - } - ]) - }) - - it("detects capability escalations", async () => { - const { ucan } = await makeSimpleDelegation( - [ wnfsCapability("//boris.fission.name/private/abc", "OVERWRITE") ], - [ wnfsCapability("//boris.fission.name/private/def", "SUPER_USER") ] - ) - - expect(await all(wnfsPrivateCapabilities(ucan))).toEqual([ - { - rootIssuer: bob.did(), - capability: wnfsCapability("//boris.fission.name/private/def", "SUPER_USER"), - requiredINumbers: new Set(["def"]) - }, - ]) - }) - - it("detects capability escalations, but still returns valid delegations", async () => { - const { ucan } = await makeSimpleDelegation( - [ wnfsCapability("//boris.fission.name/private/abc", "OVERWRITE") ], - [ - wnfsCapability("//boris.fission.name/private/def", "SUPER_USER"), - wnfsCapability("//boris.fission.name/private/ghi", "CREATE") - ] - ) - - expect(await all(wnfsPrivateCapabilities(ucan))).toEqual([ - { - rootIssuer: bob.did(), - capability: wnfsCapability("//boris.fission.name/private/def", "SUPER_USER"), - requiredINumbers: new Set(["def"]) - }, - { - rootIssuer: bob.did(), - capability: wnfsCapability("//boris.fission.name/private/ghi", "CREATE"), - requiredINumbers: new Set(["ghi"]) - }, - { - rootIssuer: alice.did(), - capability: wnfsCapability("//boris.fission.name/private/ghi", "CREATE"), - requiredINumbers: new Set(["ghi", "abc"]) - } - ]) - }) - - it("lists all possible inumber combinations", async () => { - const { ucan } = await makeComplexDelegation( - { - alice: [ wnfsCapability("//boris.fission.name/private/inumalice", "OVERWRITE") ], - bob: [ wnfsCapability("//boris.fission.name/private/inumbob", "OVERWRITE") ] - }, - [ wnfsCapability("//boris.fission.name/private/subinum", "OVERWRITE") ] - ) - - expect(await all(wnfsPrivateCapabilities(ucan))).toEqual([ - { - rootIssuer: mallory.did(), - capability: wnfsCapability("//boris.fission.name/private/subinum", "OVERWRITE"), - requiredINumbers: new Set(["subinum"]) - }, - { - rootIssuer: alice.did(), - capability: wnfsCapability("//boris.fission.name/private/subinum", "OVERWRITE"), - requiredINumbers: new Set(["subinum", "inumalice"]) - }, - { - rootIssuer: bob.did(), - capability: wnfsCapability("//boris.fission.name/private/subinum", "OVERWRITE"), - requiredINumbers: new Set(["subinum", "inumbob"]) - } - ]) - }) - - it("lists all possible inumber combinations except escalations", async () => { - const { ucan } = await makeComplexDelegation( - { - alice: [ wnfsCapability("//boris.fission.name/private/inumalice", "CREATE") ], - bob: [ wnfsCapability("//boris.fission.name/private/inumbob", "OVERWRITE") ] - }, - [ wnfsCapability("//boris.fission.name/private/subinum", "OVERWRITE") ] - ) - - expect(await all(wnfsPrivateCapabilities(ucan))).toEqual([ - { - rootIssuer: mallory.did(), - capability: wnfsCapability("//boris.fission.name/private/subinum", "OVERWRITE"), - requiredINumbers: new Set(["subinum"]) - }, - { - rootIssuer: bob.did(), - capability: wnfsCapability("//boris.fission.name/private/subinum", "OVERWRITE"), - requiredINumbers: new Set(["subinum", "inumbob"]) - } - ]) - }) - -}) - -/** - * A linear delegation chain: - * alice -> bob -> mallory - * - * The arguments are the capabilities delegated in the first and second arrow, respectively. - */ -async function makeSimpleDelegation(aliceCapabilities: Capability[], bobCapabilities: Capability[]) { - const leaf = await token.build({ - issuer: alice, - audience: bob.did(), - capabilities: aliceCapabilities - }) - - const ucan = await token.build({ - issuer: bob, - audience: mallory.did(), - capabilities: bobCapabilities, - proofs: [ token.encode(leaf) ] - }) - - return { leaf, ucan } -} - - -/** - * A tree-like delegation ucan: - * alice & bob => mallory -> alice - * - * The first argument are the capabilities delegated in the first two arrows, - * the second argument are the capabilities delegated in the last arrow. - */ -async function makeComplexDelegation(proofs: { alice: Capability[]; bob: Capability[] }, final: Capability[]) { - const leafAlice = await token.build({ - issuer: alice, - audience: mallory.did(), - capabilities: proofs.alice, - }) - - const leafBob = await token.build({ - issuer: bob, - audience: mallory.did(), - capabilities: proofs.bob, - }) - - const ucan = await token.build({ - issuer: mallory, - audience: alice.did(), - capabilities: final, - proofs: [ token.encode(leafAlice), token.encode(leafBob) ], - }) - - return { leafAlice, leafBob, ucan } -} diff --git a/tests-copy/capability/wnfs.ts b/tests-copy/capability/wnfs.ts deleted file mode 100644 index 68b189f..0000000 --- a/tests-copy/capability/wnfs.ts +++ /dev/null @@ -1,213 +0,0 @@ -import { Ability, isAbility } from "../../src/capability/ability" -import { Capability } from "../../src/capability" -import { DelegationSemantics, DelegatedCapability, DelegatedOwnership, delegationChains, rootIssuer } from "../../src/attenuation" -import { SUPERUSER } from "../../src/capability/super-user" -import { Ucan } from "../../src/types" -import { ResourcePointer } from "../../src/capability/resource-pointer" - - -export const WNFS_ABILITY_LEVELS = { - "SUPER_USER": 0, - "OVERWRITE": -1, - "SOFT_DELETE": -2, - "REVISE": -3, - "CREATE": -4, -} - -export const WNFS_ABILITIES: string[] = Object.keys(WNFS_ABILITY_LEVELS) - -export type WnfsAbility = keyof typeof WNFS_ABILITY_LEVELS - -export function isWnfsCap(cap: Capability): boolean { - return cap.with.scheme === "wnfs" && isWnfsAbility(cap.can) -} - -export function isWnfsAbility(ability: unknown): ability is WnfsAbility { - if (!isAbility(ability)) return false - if (ability === SUPERUSER) return true - const abilitySegment = ability.segments[ 0 ] - const isWnfsAbilitySegment = !!abilitySegment && WNFS_ABILITIES.includes(abilitySegment) - return isWnfsAbilitySegment && ability.namespace.toLowerCase() === "wnfs" -} - -export function wnfsAbilityFromAbility(ability: Ability): WnfsAbility | null { - if (ability === SUPERUSER) return "SUPER_USER" - if (isWnfsAbility(ability)) return ability.segments[ 0 ] as WnfsAbility - return null -} - -export function wnfsCapability(path: string, ability: WnfsAbility): Capability { - return { - with: { scheme: "wnfs", hierPart: path }, - can: { namespace: "wnfs", segments: [ ability ] } - } -} - - - -////////////////////////////// -// Public WNFS Capabilities // -////////////////////////////// - - -interface WnfsPublicResourcePointer { - user: string // e.g. matheus23.fission.name - publicPath: string[] -} - -function tryParseWnfsPublicResource(pointer: ResourcePointer): WnfsPublicResourcePointer | null { - if (pointer.scheme !== "wnfs") return null - - // remove trailing slash - const path = pointer.hierPart.replace(/^\/\//, "") - const trimmed = path.endsWith("/") ? path.slice(0, -1) : path - const split = trimmed.split("/") - const user = split[ 0 ] - const publicPath = split.slice(2) // drop first two: matheus23.fission.name/public/keep/this - if (user == null || split[ 1 ] !== "public") return null - - return { - user, - publicPath, - } -} - -export const wnfsPublicSemantics: DelegationSemantics = { - - canDelegateResource(parentResource, childResource) { - const parent = tryParseWnfsPublicResource(parentResource) - const child = tryParseWnfsPublicResource(childResource) - - if (parent == null || child == null) { - return false - } - - if (parent.user !== child.user) { - return false - } - - // parentCap path must be a prefix of childCap path - if (child.publicPath.length < parent.publicPath.length) { - return false - } - - for (let i = 0; i < parent.publicPath.length; i++) { - if (child.publicPath[ i ] !== parent.publicPath[ i ]) { - return false - } - } - - return true - }, - - canDelegateAbility(parentAbility, childAbility) { - const parent = wnfsAbilityFromAbility(parentAbility) - const child = wnfsAbilityFromAbility(childAbility) - - if (parent == null || child == null) { - return false - } - - if (WNFS_ABILITY_LEVELS[ child ] > WNFS_ABILITY_LEVELS[ parent ]) { - return false - } - - return true - } - -} - -export async function * wnfsPublicCapabilities(ucan: Ucan) { - for await (const delegationChain of delegationChains(wnfsPublicSemantics, ucan)) { - if (delegationChain instanceof Error || "ownershipDID" in delegationChain) { - continue - } - yield { - capability: delegationChain.capability, - rootIssuer: rootIssuer(delegationChain), - } - } -} - - - -/////////////////////////////// -// Private WNFS Capabilities // -/////////////////////////////// - - -interface WnfsPrivateResourcePointer { - user: string - requiredINumbers: Set -} - -function tryParseWnfsPrivateResource(pointer: ResourcePointer): WnfsPrivateResourcePointer | null { - if (pointer.scheme !== "wnfs") return null - - // split up "boris.fission.name/private/fccXmZ8HYmpwxkvPSjwW9A" into "/private/" - const split = pointer.hierPart.replace(/^\/\//, "").split("/") - const user = split[ 0 ] - const inumberBase64url = split[ 2 ] - - if (user == null || split[ 1 ] !== "private" || inumberBase64url == null) return null - - return { - user, - requiredINumbers: new Set([ inumberBase64url ]), - } -} - -const wnfsPrivateSemantics: DelegationSemantics = { - - canDelegateResource(parentResource, childResource) { - const parent = tryParseWnfsPrivateResource(parentResource) - const child = tryParseWnfsPrivateResource(childResource) - - if (parent == null || child == null) { - return false - } - - // There's more tests that need to be run on the resulting delegation chain. - return true - }, - - canDelegateAbility(parentAbility, childAbility) { - const parent = wnfsAbilityFromAbility(parentAbility) - const child = wnfsAbilityFromAbility(childAbility) - - if (parent == null || child == null) { - return false - } - - if (WNFS_ABILITY_LEVELS[ child ] > WNFS_ABILITY_LEVELS[ parent ]) { - return false - } - - return true - } - -} - -export async function * wnfsPrivateCapabilities(ucan: Ucan) { - for await (const delegationChain of delegationChains(wnfsPrivateSemantics, ucan)) { - if (delegationChain instanceof Error || "ownershipDID" in delegationChain) { - continue - } - - const requiredINumbers = new Set() - let chainStep: DelegatedCapability | DelegatedOwnership | undefined = delegationChain - - while (chainStep != null && "capability" in chainStep) { - const hierSplit = chainStep.capability.with.hierPart.split("/") - const inumber = hierSplit[hierSplit.length - 1] - requiredINumbers.add(inumber) - chainStep = chainStep.chainStep - } - - yield { - capability: delegationChain.capability, - requiredINumbers, - rootIssuer: rootIssuer(delegationChain), - } - } -} diff --git a/tests-copy/compatibility.test.ts b/tests-copy/compatibility.test.ts deleted file mode 100644 index fca9af9..0000000 --- a/tests-copy/compatibility.test.ts +++ /dev/null @@ -1,39 +0,0 @@ -import * as uint8arrays from "uint8arrays" -import * as token from "../src/token" - - -const oldUcan = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsInVhdiI6IjEuMC4wIn0.eyJhdWQiOiJkaWQ6a2V5OnoxM1YzU29nMllhVUtoZEdDbWd4OVVadVcxbzFTaEZKWWM2RHZHWWU3TlR0Njg5Tm9MMXRrZUd3NGMydGFQa2dBdWloUjh0cmg2azg2VHRVaTNIR2ZrNEh1NDg3czNiTWY4V1MzWjJoU3VwRktiNmhnV3VwajFIRzhheUxRdDFmeWJSdThjTGdBMkNKanFRYm16YzRFOEFKU0tKeDNndVFYa2F4c3R2Um5RRGN1eDFkZzhVR1BRS3haN2lLeUFKWkFuQlcyWXJUM2o0TVQxdTJNcWZQWG9RYU01WFZQMk04clBFN0FCSEREOXdMbWlKdjkzUUFDRFR5MllnZkVSS3JualNWaTdFb3RNOFR3NHg3M1pNUXJEQnZRRW01Zm9tTWZVaTZVSmJUTmVaaldDTUJQYllNbXRKUDZQZlRpaWZYZG0zdXprVFg5NnExUkVFOExodkU2Rzg2cUR0Wjg5MzdFYUdXdXFpNkRHVDFvc2FRMUVnR3NFN3Jac2JSdDFLNnRXeTZpYktlNTlKZWtnTWFlNW9XNER2IiwiZXhwIjozMjc0NDE5MTQyMywiZmN0IjpbXSwiaXNzIjoiZGlkOmtleTp6MTNWM1NvZzJZYVVLaGRHQ21neDlVWnVXMW8xU2hGSlljNkR2R1llN05UdDY4OU5vTDJWanZBR2JXdTFrdmZWUWFyVTVWMXBTUnNjOWFwR2h2dDdaODJmUWg1QWE1NW41Zm0zZGs2SnFuTXczZGU4WG91dWZUV2Z1eHpEVkhrSFNGV0sxOW1SWWI4d205d1VwZkxtUWl4QVdtMndFWVZqU2dENEd6YzhVUDlDSjFxMkY4ZXlpVXViMThGbld4Y2djUWhqdXB3OTNxUlMzWDlXUDViemlSYjE4TTZ0Vm8zaUJ4ZUozb2lrRTNaa3RScEtTZDlkcHU5WWNXZFhoeDZDQmY5NTZ1UXhkTDZoTkppNmVMbmZ1eFY2NEhpZU1rZFVoTTJSeThRd3lqZjQ4ZnZWMVhFVU1zeEM5YWFjNEtCcGJONDJHR3U4UmFkRDU3cjZuMWFOc2IyTjU3RkNOYnFIMXVLdHhNTmVHZHJ2QWlUUGRzVjJBRmppczJvN243ajhMNW41YmJ4TFl4VThNVHB3QVphdFpkSiIsIm5iZiI6MTY0MDE5MTQ1NywicHJmIjoiZXlKaGJHY2lPaUpTVXpJMU5pSXNJblI1Y0NJNklrcFhWQ0lzSW5WaGRpSTZJakV1TUM0d0luMC5leUpoZFdRaU9pSmthV1E2YTJWNU9ub3hNMVl6VTI5bk1sbGhWVXRvWkVkRGJXZDRPVlZhZFZjeGJ6RlRhRVpLV1dNMlJIWkhXV1UzVGxSME5qZzVUbTlNTWxacWRrRkhZbGQxTVd0MlpsWlJZWEpWTlZZeGNGTlNjMk01WVhCSGFIWjBOMW80TW1aUmFEVkJZVFUxYmpWbWJUTmthelpLY1c1TmR6TmtaVGhZYjNWMVpsUlhablY0ZWtSV1NHdElVMFpYU3pFNWJWSlpZamgzYlRsM1ZYQm1URzFSYVhoQlYyMHlkMFZaVm1wVFowUTBSM3BqT0ZWUU9VTktNWEV5UmpobGVXbFZkV0l4T0VadVYzaGpaMk5SYUdwMWNIYzVNM0ZTVXpOWU9WZFFOV0o2YVZKaU1UaE5OblJXYnpOcFFuaGxTak52YVd0Rk0xcHJkRkp3UzFOa09XUndkVGxaWTFka1dHaDROa05DWmprMU5uVlJlR1JNTm1oT1NtazJaVXh1Wm5WNFZqWTBTR2xsVFd0a1ZXaE5NbEo1T0ZGM2VXcG1ORGhtZGxZeFdFVlZUWE40UXpsaFlXTTBTMEp3WWs0ME1rZEhkVGhTWVdSRU5UZHlObTR4WVU1ellqSk9OVGRHUTA1aWNVZ3hkVXQwZUUxT1pVZGtjblpCYVZSUVpITldNa0ZHYW1sek1tODNiamRxT0V3MWJqVmlZbmhNV1hoVk9FMVVjSGRCV21GMFdtUktJaXdpWlhod0lqb3pNamMwTkRFNU1UUXlNeXdpWm1OMElqcGJYU3dpYVhOeklqb2laR2xrT210bGVUcDZNVE5XTTFOdlp6SlpZVlZMYUdSSFEyMW5lRGxWV25WWE1XOHhVMmhHU2xsak5rUjJSMWxsTjA1VWREWTRPVTV2VERKaE5VcE9hMlI0VmpabWJYVm9WbU5SWkRkSVIycHhkRXBRYVc1WlZWQTRRMUp4Y21veVkyVm5hVTFyT1RKUlNIazJRbWRXT1hveVVGQnJWMkZZU0RkUlRsQmlRekphZEUxNWFXbGFjWGRLUkVOd05sZG9VbkZVUzJodVFtaENUbWQ1WkRkTFJuUTNjRkkyTkhCa1ZIQjZUbXRNUlZKNGFHNTNUVUZqZURKcVJGZFlOelpDVG5SS04xUTFWVXQ0TTIxcWRHWTBaak0wWjJwVGRUaHJkME5UY0V0alFuQTRWV2RwU0hkdllVSkhkREUxVkZjNVUzQlNXVkoxYUZKdk1tdEljVFZ5Y0ROTmRFSnFSa2QyVUdZeVRsTlpZbUUzTmxoSGJYcFhlVEZyZUZOelEySTVUSGhqTW5welEwdG1lSEF5ZUd0VVFqWmtPVVJDUlVwVE5sUnhXbFo1WkhKU05GWmFNVkE1ZFhJeGRGcHBlbk5qYWtWd1kzVlViV1EzV0VRemRYSjZVelpqY0RSdU1sZHdSbFZNYjNsMk5tOW5ibWxaZEVOSGFUVlVlbWxEY2pKT1FWRjNWMEZYY25CMldVMWllbVEyVmt0a2RUVmpaekZZUWxoTVZFNWhUQ0lzSW01aVppSTZNVFkwTURFNU1UTTJNeXdpY0hSaklqb2lVMVZRUlZKZlZWTkZVaUlzSW5Kell5STZJaW9pZlEuQ0k5SjlOLVhUZUxQNEM5WTktUl9TcEE1aE80dHdpNUQxNFpTR2lwUzdjNS1jTlJWTVItc285Z0JZMlQzSFNaTHFmQ2xyMEtlQVJicFk2TFBwSm1NRGQ1ODdvck1TVVRnMndqN043eUNVeksxSWhOazhQMkQ3RGVlSHNxQ1lsTVotdXpjMHBSbnFJb3dPTWl6MVFkbHZXaTZ0UHNxZkZVYnl4bEx1bXRHdjV1a1hqc1FZcmYzdko3aU5DMkJibWotMGhTV25wNTNBN01TQTllLWFXVGpLUWEwSkpXVVVhWG5XS19CNjRaa3NyTWRXdW5mVFNuSE9lR2o3MFRuSXhieVcxbFhodk5pcnhIUV90ZVlKZ2xIZTRBbldEQXdUa2dnaVotdkp0WUhsYnVwQkt4S1YtNm9OMTlXS3dUT3U3QnpPX2QyUHAtWVVyY1RSSS1KZ0F2NUpnIiwicHRjIjoiU1VQRVJfVVNFUiIsInJzYyI6IioifQ.CRLB4gBBHhnsbfUhLALiCfo6mHnHSlEUczyZsWhh9TNjv9UxdgvsSWQsehGIT4XR0jQeYZo2OhasEVaF-Gtt_qqtUQIrducKngd0qzmpfjVbicsQPKVdJjlcTwm9dqhLSEtL195El0oucLzYdqMEZMf-txEmyhCd_Q8CaNExhAnwN32v1salnO6vrAw33ZJID7ZaFmBleoGUXBHQwnkv9_m_P6Fh-UGIKjaOuNmBkGXGn-4irm-eXrne2OPZCoPjhiaf0xTONu4ROrQQYykG8CppvsSXeiylOFY11Ot0sdAlHGSlyZk1_chJ3ud17K9S-CKWK9NtqiMNcUdQGFnNQQ" -const [ encodedHeader, encodedPayload, encodedSignature ] = oldUcan.split(".") -const signature = encodedSignature -const header = JSON.parse(uint8arrays.toString(uint8arrays.fromString(encodedHeader, "base64url"))) -const payload = JSON.parse(uint8arrays.toString(uint8arrays.fromString(encodedPayload, "base64url"))) - -describe("compatibility", () => { - - it("allows parsing UCANs with 'uav: 1.0.0' into 'ucv: 0.3.0'", async () => { - const ucan = await token.validate(oldUcan, { checkIsExpired: false, checkIsTooEarly: false, checkSignature: false }) - expect(ucan).toEqual({ - header: { - alg: header.alg, // "RS256", - typ: header.typ, // "JWT", - ucv: { major: 0, minor: 3, patch: 0 } // we translate uav: 1.0.0 to ucv: 0.3.0 - }, - payload: { - iss: payload.iss, // "did:key:z13V3Sog2YaUKhdGCmgx9UZuW1o1ShFJYc6DvGYe7NTt689NoL2VjvAGbWu1kvfVQarU5V1pSRsc9apGhvt7Z82fQh5Aa55n5fm3dk6JqnMw3de8XouufTWfuxzDVHkHSFWK19mRYb8wm9wUpfLmQixAWm2wEYVjSgD4Gzc8UP9CJ1q2F8eyiUub18FnWxcgcQhjupw93qRS3X9WP5bziRb18M6tVo3iBxeJ3oikE3ZktRpKSd9dpu9YcWdXhx6CBf956uQxdL6hNJi6eLnfuxV64HieMkdUhM2Ry8Qwyjf48fvV1XEUMsxC9aac4KBpbN42GGu8RadD57r6n1aNsb2N57FCNbqH1uKtxMNeGdrvAiTPdsV2AFjis2o7n7j8L5n5bbxLYxU8MTpwAZatZdJ", - aud: payload.aud, // "did:key:z13V3Sog2YaUKhdGCmgx9UZuW1o1ShFJYc6DvGYe7NTt689NoL1tkeGw4c2taPkgAuihR8trh6k86TtUi3HGfk4Hu487s3bMf8WS3Z2hSupFKb6hgWupj1HG8ayLQt1fybRu8cLgA2CJjqQbmzc4E8AJSKJx3guQXkaxstvRnQDcux1dg8UGPQKxZ7iKyAJZAnBW2YrT3j4MT1u2MqfPXoQaM5XVP2M8rPE7ABHDD9wLmiJv93QACDTy2YgfERKrnjSVi7EotM8Tw4x73ZMQrDBvQEm5fomMfUi6UJbTNeZjWCMBPbYMmtJP6PfTiifXdm3uzkTX96q1REE8LhvE6G86qDtZ8937EaGWuqi6DGT1osaQ1EgGsE7rZsbRt1K6tWy6ibKe59JekgMae5oW4Dv", - nbf: payload.nbf, // 1640191457, - exp: payload.exp, // 32744191423, - att: [ { - can: payload.rsc, // "*", - with: { scheme: "my", hierPart: payload.rsc } - } ], - prf: [ - payload.prf, // "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsInVhdiI6IjEuMC4wIn0.eyJhdWQiOiJkaWQ6a2V5OnoxM1YzU29nMllhVUtoZEdDbWd4OVVadVcxbzFTaEZKWWM2RHZHWWU3TlR0Njg5Tm9MMlZqdkFHYld1MWt2ZlZRYXJVNVYxcFNSc2M5YXBHaHZ0N1o4MmZRaDVBYTU1bjVmbTNkazZKcW5NdzNkZThYb3V1ZlRXZnV4ekRWSGtIU0ZXSzE5bVJZYjh3bTl3VXBmTG1RaXhBV20yd0VZVmpTZ0Q0R3pjOFVQOUNKMXEyRjhleWlVdWIxOEZuV3hjZ2NRaGp1cHc5M3FSUzNYOVdQNWJ6aVJiMThNNnRWbzNpQnhlSjNvaWtFM1prdFJwS1NkOWRwdTlZY1dkWGh4NkNCZjk1NnVReGRMNmhOSmk2ZUxuZnV4VjY0SGllTWtkVWhNMlJ5OFF3eWpmNDhmdlYxWEVVTXN4QzlhYWM0S0JwYk40MkdHdThSYWRENTdyNm4xYU5zYjJONTdGQ05icUgxdUt0eE1OZUdkcnZBaVRQZHNWMkFGamlzMm83bjdqOEw1bjViYnhMWXhVOE1UcHdBWmF0WmRKIiwiZXhwIjozMjc0NDE5MTQyMywiZmN0IjpbXSwiaXNzIjoiZGlkOmtleTp6MTNWM1NvZzJZYVVLaGRHQ21neDlVWnVXMW8xU2hGSlljNkR2R1llN05UdDY4OU5vTDJhNUpOa2R4VjZmbXVoVmNRZDdIR2pxdEpQaW5ZVVA4Q1JxcmoyY2VnaU1rOTJRSHk2QmdWOXoyUFBrV2FYSDdRTlBiQzJadE15aWlacXdKRENwNldoUnFUS2huQmhCTmd5ZDdLRnQ3cFI2NHBkVHB6TmtMRVJ4aG53TUFjeDJqRFdYNzZCTnRKN1Q1VUt4M21qdGY0ZjM0Z2pTdThrd0NTcEtjQnA4VWdpSHdvYUJHdDE1VFc5U3BSWVJ1aFJvMmtIcTVycDNNdEJqRkd2UGYyTlNZYmE3NlhHbXpXeTFreFNzQ2I5THhjMnpzQ0tmeHAyeGtUQjZkOURCRUpTNlRxWlZ5ZHJSNFZaMVA5dXIxdFppenNjakVwY3VUbWQ3WEQzdXJ6UzZjcDRuMldwRlVMb3l2Nm9nbmlZdENHaTVUemlDcjJOQVF3V0FXcnB2WU1iemQ2VktkdTVjZzFYQlhMVE5hTCIsIm5iZiI6MTY0MDE5MTM2MywicHRjIjoiU1VQRVJfVVNFUiIsInJzYyI6IioifQ.CI9J9N-XTeLP4C9Y9-R_SpA5hO4twi5D14ZSGipS7c5-cNRVMR-so9gBY2T3HSZLqfClr0KeARbpY6LPpJmMDd587orMSUTg2wj7N7yCUzK1IhNk8P2D7DeeHsqCYlMZ-uzc0pRnqIowOMiz1QdlvWi6tPsqfFUbyxlLumtGv5ukXjsQYrf3vJ7iNC2Bbmj-0hSWnp53A7MSA9e-aWTjKQa0JJWUUaXnWK_B64ZksrMdWunfTSnHOeGj70TnIxbyW1lXhvNirxHQ_teYJglHe4AnWDAwTkggiZ-vJtYHlbupBKxKV-6oN19WKwTOu7BzO_d2Pp-YUrcTRI-JgAv5Jg", - ], - }, - signedData: `${encodedHeader}.${encodedPayload}`, - signature // "CRLB4gBBHhnsbfUhLALiCfo6mHnHSlEUczyZsWhh9TNjv9UxdgvsSWQsehGIT4XR0jQeYZo2OhasEVaF-Gtt_qqtUQIrducKngd0qzmpfjVbicsQPKVdJjlcTwm9dqhLSEtL195El0oucLzYdqMEZMf-txEmyhCd_Q8CaNExhAnwN32v1salnO6vrAw33ZJID7ZaFmBleoGUXBHQwnkv9_m_P6Fh-UGIKjaOuNmBkGXGn-4irm-eXrne2OPZCoPjhiaf0xTONu4ROrQQYykG8CppvsSXeiylOFY11Ot0sdAlHGSlyZk1_chJ3ud17K9S-CKWK9NtqiMNcUdQGFnNQQ" - }) - }) - -}) diff --git a/tests-copy/did-key.ts b/tests-copy/did-key.ts deleted file mode 100644 index 9ad2824..0000000 --- a/tests-copy/did-key.ts +++ /dev/null @@ -1,36 +0,0 @@ -import EcdsaKeypair from "../src/keypair/ecdsa.js" - -// did:key test vectors from W3C -// https://github.com/w3c-ccg/did-method-key/blob/main/test-vectors/nist-curves.json -const testVectors = [ - { - id: "did:key:zDnaerx9CtbPJ1q36T5Ln5wYt3MQYeGRG5ehnPAmxcf5mDZpv", - jwk: { - kty: "EC", - crv: "P-256", - x: "igrFmi0whuihKnj9R3Om1SoMph72wUGeFaBbzG2vzns", - y: "efsX5b10x8yjyrj4ny3pGfLcY7Xby1KzgqOdqnsrJIM", - d: "gPh-VvVS8MbvKQ9LSVVmfnxnKjHn4Tqj0bmbpehRlpc" - } - }, - { - id: "did:key:zDnaerDaTF5BXEavCrfRZEk316dpbLsfPDZ3WJ5hRTPFU2169", - jwk: { - kty: "EC", - crv: "P-256", - x: "fyNYMN0976ci7xqiSdag3buk-ZCwgXU4kz9XNkBlNUI", - y: "hW2ojTNfH7Jbi8--CJUo3OCbH3y5n91g-IMA9MLMbTU", - d: "YjRs6vNvw4sYrzVVY8ipkEpDAD9PFqw1sUnvPRMA-WI" - } - } -] - -describe("did:key ECDSA P-256", () => { - it("derives the correct DID from the JWK", async () => { - for(const vector of testVectors) { - const keypair = await EcdsaKeypair.importFromJwk(vector.jwk) - const did = keypair.did() - expect(did).toEqual(vector.id) - } - }) -}) \ No newline at end of file diff --git a/tests-copy/did.test.ts b/tests-copy/did.test.ts deleted file mode 100644 index 02abca7..0000000 --- a/tests-copy/did.test.ts +++ /dev/null @@ -1,78 +0,0 @@ -import * as did from "../src/did" - -describe("publicKeyToDid", () => { - - it("handles RSA Keys", async () => { - const pubkey = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAnzyis1ZjfNB0bBgKFMSvvkTtwlvBsaJq7S5wA+kzeVOVpVWwkWdVha4s38XM/pa/yr47av7+z3VTmvDRyAHcaT92whREFpLv9cj5lTeJSibyr/Mrm/YtjCZVWgaOYIhwrXwKLqPr/11inWsAkfIytvHWTxZYEcXLgAXFuUuaS3uF9gEiNQwzGTU1v0FqkqTBr4B8nW3HCN47XUu0t8Y0e+lf4s4OxQawWD79J9/5d3Ry0vbV3Am1FtGJiJvOwRsIfVChDpYStTcHTCMqtvWbV6L11BWkpzGXSW4Hv43qa+GSYOD2QU68Mb59oSk2OB+BtOLpJofmbGEGgvmwyCI9MwIDAQAB" - // old: const expectedDid = "did:key:z13V3Sog2YaUKhdGCmgx9UZuW1o1ShFJYc6DvGYe7NTt689NoL2RtpVs65Zw899YrTN9WuxdEEDm54YxWuQHQvcKfkZwa8HTgokHxGDPEmNLhvh69zUMEP4zjuARQ3T8bMUumkSLGpxNe1bfQX624ef45GhWb3S9HM3gvAJ7Qftm8iqnDQVcxwKHjmkV4hveKMTix4bTRhieVHi1oqU4QCVy4QPWpAAympuCP9dAoJFxSP6TNBLY9vPKLazsg7XcFov6UuLWsEaxJ5SomCpDx181mEgW2qTug5oQbrJwExbD9CMgXHLVDE2QgLoQMmgsrPevX57dH715NXC2uY6vo2mYCzRY4KuDRUsrkuYCkewL8q2oK1BEDVvi3Sg8pbC9QYQ5mMiHf8uxiHxTAmPedv8" - const expectedDid = "did:key:z4MXj1wBzi9jUstyNvmiK5WLRRL4rr9UvzPxhry1CudCLKWLyMbP1WoTwDfttBTpxDKf5hAJEjqNbeYx2EEvrJmSWHAu7TJRPTrE3QodbMfRvRNRDyYvaN1FSQus2ziS1rWXwAi5Gpc16bY3JwjyLCPJLfdRWHZhRXiay5FWEkfoSKy6aftnzAvqNkKBg2AxgzGMinR6d1WiH4w5mEXFtUeZkeo4uwtRTd8rD9BoVaHVkGwJkksDybE23CsBNXiNfbweFVRcwfTMhcQsTsYhUWDcSC6QE3zt9h4Rsrj7XRYdwYSK5bc1qFRsg5HULKBp2uZ1gcayiW2FqHFcMRjBieC4LnSMSD1AZB1WUncVRbPpVkn1UGhCU" - const result = did.publicKeyToDid(pubkey, "rsa") - expect(result).toEqual(expectedDid) - }) - - it("handles Ed25519 Keys", async () => { - const pubkey = "Hv+AVRD2WUjUFOsSNbsmrp9fokuwrUnjBcr92f0kxw4=" - const expectedDid = "did:key:z6MkgYGF3thn8k1Fv4p4dWXKtsXCnLH7q9yw4QgNPULDmDKB" - const result = did.publicKeyToDid(pubkey, "ed25519") - expect(result).toEqual(expectedDid) - }) - - it("handles BLS12-381 Keys", async () => { - const pubkey = "Hv+AVRD2WUjUFOsSNbsmrp9fokuwrUnjBcr92f0kxw4=" - const expectedDid = "did:key:z6HpYD1br5P4QVh5rjRGAkBfKMWes44uhKmKdJ6dN2Nm9gHK" - const result = did.publicKeyToDid(pubkey, "bls12-381") - expect(result).toEqual(expectedDid) - }) - - it("handles NIST P-256 Keys", async () => { - const pubkey = "BEgg6smRlRQXKqzJHu0w5/nHcy7zYNxFXwTKUyyCOoDMztUvQn5VZg668v4MecHGUZTeHkaZhSraK7RtJuXfWp4=" - const expectedDid = "did:key:zDnaeVHXWgKUpJFc8AUfRDuRWfsAQejmZu5HrERh41iGumZVu" - const result = did.publicKeyToDid(pubkey, "p256") - expect(result).toEqual(expectedDid) - }) - -}) - -describe("didToPublicKey", () => { - - it("handles old RSA Keys", async () => { - const toDecode = "did:key:z13V3Sog2YaUKhdGCmgx9UZuW1o1ShFJYc6DvGYe7NTt689NoL2RtpVs65Zw899YrTN9WuxdEEDm54YxWuQHQvcKfkZwa8HTgokHxGDPEmNLhvh69zUMEP4zjuARQ3T8bMUumkSLGpxNe1bfQX624ef45GhWb3S9HM3gvAJ7Qftm8iqnDQVcxwKHjmkV4hveKMTix4bTRhieVHi1oqU4QCVy4QPWpAAympuCP9dAoJFxSP6TNBLY9vPKLazsg7XcFov6UuLWsEaxJ5SomCpDx181mEgW2qTug5oQbrJwExbD9CMgXHLVDE2QgLoQMmgsrPevX57dH715NXC2uY6vo2mYCzRY4KuDRUsrkuYCkewL8q2oK1BEDVvi3Sg8pbC9QYQ5mMiHf8uxiHxTAmPedv8" - const expectedKey = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAnzyis1ZjfNB0bBgKFMSvvkTtwlvBsaJq7S5wA+kzeVOVpVWwkWdVha4s38XM/pa/yr47av7+z3VTmvDRyAHcaT92whREFpLv9cj5lTeJSibyr/Mrm/YtjCZVWgaOYIhwrXwKLqPr/11inWsAkfIytvHWTxZYEcXLgAXFuUuaS3uF9gEiNQwzGTU1v0FqkqTBr4B8nW3HCN47XUu0t8Y0e+lf4s4OxQawWD79J9/5d3Ry0vbV3Am1FtGJiJvOwRsIfVChDpYStTcHTCMqtvWbV6L11BWkpzGXSW4Hv43qa+GSYOD2QU68Mb59oSk2OB+BtOLpJofmbGEGgvmwyCI9MwIDAQAB" - const { publicKey, type } = did.didToPublicKey(toDecode) - expect(publicKey).toEqual(expectedKey) - expect(type).toEqual("rsa") - }) - - it("handles standardized RSA Keys", async () => { - const toDecode = "did:key:z4MXj1wBzi9jUstyNvmiK5WLRRL4rr9UvzPxhry1CudCLKWLyMbP1WoTwDfttBTpxDKf5hAJEjqNbeYx2EEvrJmSWHAu7TJRPTrE3QodbMfRvRNRDyYvaN1FSQus2ziS1rWXwAi5Gpc16bY3JwjyLCPJLfdRWHZhRXiay5FWEkfoSKy6aftnzAvqNkKBg2AxgzGMinR6d1WiH4w5mEXFtUeZkeo4uwtRTd8rD9BoVaHVkGwJkksDybE23CsBNXiNfbweFVRcwfTMhcQsTsYhUWDcSC6QE3zt9h4Rsrj7XRYdwYSK5bc1qFRsg5HULKBp2uZ1gcayiW2FqHFcMRjBieC4LnSMSD1AZB1WUncVRbPpVkn1UGhCU" - const expectedKey = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAnzyis1ZjfNB0bBgKFMSvvkTtwlvBsaJq7S5wA+kzeVOVpVWwkWdVha4s38XM/pa/yr47av7+z3VTmvDRyAHcaT92whREFpLv9cj5lTeJSibyr/Mrm/YtjCZVWgaOYIhwrXwKLqPr/11inWsAkfIytvHWTxZYEcXLgAXFuUuaS3uF9gEiNQwzGTU1v0FqkqTBr4B8nW3HCN47XUu0t8Y0e+lf4s4OxQawWD79J9/5d3Ry0vbV3Am1FtGJiJvOwRsIfVChDpYStTcHTCMqtvWbV6L11BWkpzGXSW4Hv43qa+GSYOD2QU68Mb59oSk2OB+BtOLpJofmbGEGgvmwyCI9MwIDAQAB" - const { publicKey, type } = did.didToPublicKey(toDecode) - expect(publicKey).toEqual(expectedKey) - expect(type).toEqual("rsa") - }) - - it("handles Ed25519 Keys", async () => { - const toDecode = "did:key:z6MkgYGF3thn8k1Fv4p4dWXKtsXCnLH7q9yw4QgNPULDmDKB" - const expectedKey = "Hv+AVRD2WUjUFOsSNbsmrp9fokuwrUnjBcr92f0kxw4=" - const { publicKey, type } = did.didToPublicKey(toDecode) - expect(publicKey).toEqual(expectedKey) - expect(type).toEqual("ed25519") - }) - - it("handles BLS12-381 Keys", async () => { - const toDecode = "did:key:z6HpYD1br5P4QVh5rjRGAkBfKMWes44uhKmKdJ6dN2Nm9gHK" - const expectedKey = "Hv+AVRD2WUjUFOsSNbsmrp9fokuwrUnjBcr92f0kxw4=" - const { publicKey, type } = did.didToPublicKey(toDecode) - expect(publicKey).toEqual(expectedKey) - expect(type).toEqual("bls12-381") - }) - - it("handles NIST P-256 Keys", async () => { - const toDecode = "did:key:zDnaeVHXWgKUpJFc8AUfRDuRWfsAQejmZu5HrERh41iGumZVu" - const expectedKey = "BEgg6smRlRQXKqzJHu0w5/nHcy7zYNxFXwTKUyyCOoDMztUvQn5VZg668v4MecHGUZTeHkaZhSraK7RtJuXfWp4=" - const { publicKey, type } = did.didToPublicKey(toDecode) - expect(publicKey).toEqual(expectedKey) - expect(type).toEqual("p256") - }) - -}) diff --git a/tests-copy/ecdsa.test.ts b/tests-copy/ecdsa.test.ts deleted file mode 100644 index 3d74917..0000000 --- a/tests-copy/ecdsa.test.ts +++ /dev/null @@ -1,34 +0,0 @@ -import * as did from "../src/did" -import ECDSAKeyPair from "../src/keypair/ecdsa" - - -describe("ecdsa", () => { - - let p256Keypair: ECDSAKeyPair - - let p256Signature: Uint8Array - - const data = new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8, 9]) - - it("creates an ecdsa keypairs with different curves", async () => { - p256Keypair = await ECDSAKeyPair.create() - }) - - it("returns a publicKeyStr and did: curve P-256", () => { - const keyDid = p256Keypair.did() - const publicKey = p256Keypair.publicKeyStr() - const transformed = did.didToPublicKey(keyDid) - expect(transformed.publicKey).toEqual(publicKey) - expect(transformed.type).toEqual("p256") - }) - - it("signs data: curve P-256", async () => { - p256Signature = await p256Keypair.sign(data) - }) - - it("can verify signature: P-256", async () => { - const isValid = await did.verifySignature(data, p256Signature, p256Keypair.did()) - expect(isValid).toBeTruthy() - }) - -}) diff --git a/tests-copy/ed25519.test.ts b/tests-copy/ed25519.test.ts deleted file mode 100644 index 65769d9..0000000 --- a/tests-copy/ed25519.test.ts +++ /dev/null @@ -1,31 +0,0 @@ -import * as did from "../src/did" -import EdwardsKey from "../src/keypair/ed25519" - -describe("ed25519", () => { - - let keypair: EdwardsKey - let signature: Uint8Array - const data = new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8, 9]) - - it("creates an edwards curve keypair", async () => { - keypair = await EdwardsKey.create() - }) - - it("returns a publicKeyStr and did", () => { - const publicKey = keypair.publicKeyStr() - const keyDid = keypair.did() - const transformed = did.didToPublicKey(keyDid) - expect(transformed.publicKey).toEqual(publicKey) - expect(transformed.type).toEqual("ed25519") - }) - - it("signs data", async () => { - signature = await keypair.sign(data) - }) - - it("can verify signature", async () => { - const isValid = await did.verifySignature(data, signature, keypair.did()) - expect(isValid).toEqual(true) - }) - -}) diff --git a/tests-copy/fixtures.ts b/tests-copy/fixtures.ts deleted file mode 100644 index 8d0c9fc..0000000 --- a/tests-copy/fixtures.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { EdKeypair } from "../src/keypair/ed25519" - -/** did:key:z6Mkk89bC3JrVqKie71YEcc5M1SMVxuCgNx6zLZ8SYJsxALi */ -export const alice = EdKeypair.fromSecretKey("U+bzp2GaFQHso587iSFWPSeCzbSfn/CbNHEz7ilKRZ1UQMmMS7qq4UhTzKn3X9Nj/4xgrwa+UqhMOeo4Ki8JUw==") - -/** did:key:z6MkffDZCkCTWreg8868fG1FGFogcJj5X6PY93pPcWDn9bob */ -export const bob = EdKeypair.fromSecretKey("G4+QCX1b3a45IzQsQd4gFMMe0UB1UOx9bCsh8uOiKLER69eAvVXvc8P2yc4Iig42Bv7JD2zJxhyFALyTKBHipg==") - -/** did:key:z6MktafZTREjJkvV5mfJxcLpNBoVPwDLhTuMg9ng7dY4zMAL */ -export const mallory = EdKeypair.fromSecretKey("LR9AL2MYkMARuvmV3MJV8sKvbSOdBtpggFCW8K62oZDR6UViSXdSV/dDcD8S9xVjS61vh62JITx7qmLgfQUSZQ==") - - -export function didToName(did: string) { - if (did === alice.did()) return "alice" - if (did === bob.did()) return "bob" - if (did === mallory.did()) return "mallory" - return did -} diff --git a/tests-copy/rsa.test.ts b/tests-copy/rsa.test.ts deleted file mode 100644 index 70950ce..0000000 --- a/tests-copy/rsa.test.ts +++ /dev/null @@ -1,96 +0,0 @@ -import * as fc from "fast-check" -import * as uint8arrays from "uint8arrays" -import * as did from "../src/did" -import * as rsaCrypto from "../src/crypto/rsa" -import RSAKeypair from "../src/keypair/rsa" - - -describe("rsa", () => { - - let keypair: RSAKeypair - let signature: Uint8Array - const data = new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8, 9]) - - it("creates an rsa keypair", async () => { - keypair = await RSAKeypair.create() - }) - - it("returns a publicKeyStr and did", () => { - const publicKey = keypair.publicKeyStr() - const keyDid = keypair.did() - const transformed = did.didToPublicKey(keyDid) - expect(transformed.publicKey).toEqual(publicKey) - expect(transformed.type).toEqual("rsa") - }) - - it("signs data", async () => { - signature = await keypair.sign(data) - }) - - it("can verify signature", async () => { - const isValid = await did.verifySignature(data, signature, keypair.did()) - expect(isValid).toEqual(true) - }) - -}) - -describe("ASN", () => { - - describe("asn1DERLengthEncode/Decode", () => { - - it("works with simple examples", () => { - // 82 - bigger than 127 & 2 length octets - // 01 - 1 * 256^1 + - // b3 - 179 * 256^0 - // = 435 - // Example from https://en.wikipedia.org/wiki/X.690#Length_octets - expect(uint8arrays.toString(rsaCrypto.asn1DERLengthEncode(435), "hex")).toEqual("8201b3") - }) - - it("round-trips", () => { - fc.assert(fc.property(fc.nat(), n => { - expect(rsaCrypto.asn1DERLengthDecode(rsaCrypto.asn1DERLengthEncode(n))).toEqual(n) - })) - }) - - it("encodes in a simple way until 127", () => { - for (let i = 0; i < 128; i++) { - expect(`Encoded ${i}: ${uint8arrays.toString(rsaCrypto.asn1DERLengthEncode(i), "hex")}`) - .toEqual(`Encoded ${i}: ${uint8arrays.toString(new Uint8Array([i]), "hex")}`) - } - }) - }) - - describe("SPKI/PKCS1 conversion", () => { - - it("round trips with webcrypto-generated spki keys", async () => { - await fc.assert( - fc.asyncProperty( - fc.constantFrom(1024, 2048, 3072, 4096), - async size => { - const key = await rsaCrypto.generateKeypair(size) - if (key.publicKey == null) { - expect(key.publicKey).toBeDefined() - throw "public key is undefined" - } - const spki = await rsaCrypto.exportKey(key.publicKey) - const converted = - rsaCrypto.convertRSAPublicKeyToSubjectPublicKeyInfo( - rsaCrypto.convertSubjectPublicKeyInfoToRSAPublicKey( - spki - ) - ) - - // I find hex dumps the most readable when it comes to ASN1 - expect(uint8arrays.toString(converted, "hex")).toEqual(uint8arrays.toString(spki, "hex")) - } - ), - { - numRuns: 5, // unfortunately, generating rsa keys is quite slow. Let's try to reliably keep below the 5s timeout - examples: [[1024], [2048], [3072], [4096]], // ensure we're testing each variant at least once - } - ) - }) - - }) -}) diff --git a/tests-copy/semver.test.ts b/tests-copy/semver.test.ts deleted file mode 100644 index 70667a8..0000000 --- a/tests-copy/semver.test.ts +++ /dev/null @@ -1,89 +0,0 @@ -import * as fc from "fast-check" - -import { SemVer, parse, format, compare, GT, EQ, LT } from "../src/semver" - -const arbitrarySemVer = fc.record({ - major: fc.nat(100), - minor: fc.nat(100), - patch: fc.nat(100) -}) - -const toNum = (semVer: SemVer) => - 100 * 100 * semVer.major - + 100 * semVer.minor - + semVer.patch - -describe("SemVer", () => { - - it("has the property parse(format(x)) == x", () => { - fc.property(arbitrarySemVer, semVer => { - expect(parse(format(semVer))).toEqual(semVer) - }) - }) - - it("has the property compare(x, y) == flip(compare(y, x))", () => { - fc.property(arbitrarySemVer, arbitrarySemVer, (l, r) => { - expect(compare(l, r)).toEqual(flip(compare(r, l))) - }) - }) - - it("has the property compare(x, x) == EQ", () => { - fc.property(arbitrarySemVer, semVer => { - expect(compare(semVer, semVer)).toEqual(EQ) - }) - }) - - it("has the property compare(x, y) == compareNum(toNum(x), toNum(y))", () => { - fc.property(arbitrarySemVer, arbitrarySemVer, (x, y) => { - expect(compare(x, y)).toEqual(compareNum(toNum(x), toNum(y))) - }) - }) - - describe("parse", () => { - - it("parses 0.1.2", () => { - expect(parse("0.1.2")).toEqual({ - major: 0, - minor: 1, - patch: 2, - }) - }) - - it("parses 11.22.33", () => { - expect(parse("11.22.33")).toEqual({ - major: 11, - minor: 22, - patch: 33, - }) - }) - - it("doesn't parse negative integers", () => { - expect(parse("0.-1.1")).toEqual(null) - - }) - - it("doesn't parse octal integers", () => { - expect(parse("0.010.0")).toEqual(null) - }) - - it("doesn't parse scientific integers", () => { - expect(parse("1e5.0.0")).toEqual(null) - }) - - }) - -}) - -function flip(sign: GT | EQ | LT): GT | EQ | LT { - switch (sign) { - case GT: return LT - case LT: return GT - default: return EQ - } -} - -function compareNum(l: number, r: number): LT | EQ | GT { - if (l < r) return LT - if (l > r) return GT - return EQ -} diff --git a/tests-copy/store.test.ts b/tests-copy/store.test.ts deleted file mode 100644 index da7864f..0000000 --- a/tests-copy/store.test.ts +++ /dev/null @@ -1,142 +0,0 @@ -import * as token from "../src/token" -import { Store } from "../src/store" -import { Builder } from "../src/builder" -import { alice, bob, mallory } from "./fixtures" -import { wnfsCapability, wnfsPublicSemantics } from "./capability/wnfs" -import { Ucan } from "../src/types" -import { equalCanDelegate } from "../src/attenuation" -import { all } from "../src/util" - - -describe("Store.add", () => { - - it("makes added items retrievable with findByAudience", async () => { - const ucan = await Builder.create() - .issuedBy(alice) - .toAudience(bob.did()) - .withLifetimeInSeconds(30) - .build() - - const encoded = token.encode(ucan) - - const store = await Store.fromTokens(equalCanDelegate, []) - await store.add(ucan) - expect(encodeOrNull(store.findByAudience(ucan.payload.aud, find => token.encode(find) === encoded))).toEqual(encoded) - }) - - it("makes added items retrievable with findByAudience among multiple others", async () => { - const ucan = await Builder.create() - .issuedBy(alice) - .toAudience(bob.did()) - .withLifetimeInSeconds(30) - .build() - - const ucan2 = await Builder.create() - .issuedBy(alice) - .toAudience(mallory.did()) - .withLifetimeInSeconds(30) - .build() - - const encoded = token.encode(ucan) - const store = await Store.fromTokens(equalCanDelegate, []) - await store.add(ucan2) - await store.add(ucan) - expect(encodeOrNull(store.findByAudience(ucan.payload.aud, find => token.encode(find) === encoded))).toEqual(encoded) - }) - - it("doesn't add items twice", async () => { - const ucan = await Builder.create() - .issuedBy(alice) - .toAudience(bob.did()) - .withLifetimeInSeconds(30) - .build() - - const store = await Store.fromTokens(equalCanDelegate, []) - await store.add(ucan) - await store.add(ucan) - expect(store.getByAudience(ucan.payload.aud)).toEqual([ ucan ]) - }) - -}) - -describe("Store.findByAudience", () => { - - it("only returns ucans with given audience", async () => { - const ucanBob = await Builder.create() - .issuedBy(alice) - .toAudience(bob.did()) - .withLifetimeInSeconds(30) - .build() - - const ucanAlice = await Builder.create() - .issuedBy(bob) - .toAudience(alice.did()) - .withLifetimeInSeconds(30) - .build() - - const store = await Store.fromTokens(equalCanDelegate, [ ucanBob, ucanAlice ].map(ucan => token.encode(ucan))) - expect(store.findByAudience(mallory.did(), () => true)).toEqual(null) - expect(encodeOrNull(store.findByAudience(bob.did(), () => true))).toEqual(token.encode(ucanBob)) - expect(encodeOrNull(store.findByAudience(alice.did(), () => true))).toEqual(token.encode(ucanAlice)) - }) - -}) - -describe("Store.findWithCapability", () => { - - it("finds ucans with more capabilities than the given", async () => { - const ucan = await Builder.create() - .issuedBy(alice) - .toAudience(bob.did()) - .withLifetimeInSeconds(30) - .claimCapability(wnfsCapability("alice.fission.name/public/", "SUPER_USER")) - .build() - - const store = await Store.fromTokens(wnfsPublicSemantics, [ token.encode(ucan) ]) - - const results = all(store.findWithCapability( - bob.did(), - wnfsCapability("alice.fission.name/public/Apps", "OVERWRITE"), - alice.did() - )) - - if (!("capability" in results[0])) { - throw "no capability" - } - - expect(encodeOrNull(results[0]?.ucan)).toEqual(token.encode(ucan)) - }) - - it("reports an error if the capability can't be found with given audience", async () => { - const ucanBob = await Builder.create() - .issuedBy(alice) - .toAudience(bob.did()) - .withLifetimeInSeconds(30) - .claimCapability(wnfsCapability("alice.fission.name/public/", "SUPER_USER")) - .build() - - const ucanAlice = await Builder.create() - .issuedBy(alice) - .toAudience(bob.did()) - .withLifetimeInSeconds(30) - .build() - - const store = await Store.fromTokens(wnfsPublicSemantics, [ token.encode(ucanAlice), token.encode(ucanBob) ]) - - const results = all(store.findWithCapability( - alice.did(), - wnfsCapability("alice.fission.name/public/Apps", "OVERWRITE"), - alice.did() - )) - - expect(results).toEqual([]) - }) - -}) - -function encodeOrNull(ucan: Ucan | null): string { - if (ucan == null) { - return "null" - } - return token.encode(ucan) -} diff --git a/tests-copy/token.test.ts b/tests-copy/token.test.ts deleted file mode 100644 index b625a78..0000000 --- a/tests-copy/token.test.ts +++ /dev/null @@ -1,175 +0,0 @@ -import * as uint8arrays from "uint8arrays" - -import * as capability from "../src/capability" -import * as token from "../src/token" -import { verifySignatureUtf8 } from "../src/did" -import { alice, bob } from "./fixtures" - - -// COMPOSING - - -describe("token.build", () => { - - it("can build payloads without nbf", () => { - const payload = token.buildPayload({ - issuer: alice.did(), - audience: bob.did(), - }) - expect(payload.nbf).not.toBeDefined() - }) - - it("builds payloads that expire in the future", () => { - const payload = token.buildPayload({ - issuer: alice.did(), - audience: bob.did(), - - lifetimeInSeconds: 30, - }) - expect(payload.exp).toBeGreaterThan(Date.now() / 1000) - }) - - it("throws when enclosing tokens with an invalid key type", async () => { - await expect(() => { - const payload = token.buildPayload({ - issuer: alice.did(), - audience: bob.did(), - }) - - return token.sign( - payload, - "rsa", - data => alice.sign(data) - ) - }).rejects.toBeDefined() - }) - -}) - - - -// ENCODING - - -describe("token.encodePayload", () => { - - it("encodes capabilities", () => { - const encodedCaps = { - with: "wnfs://boris.fission.name/public/photos/", - can: "crud/DELETE" - } - - const payload = token.buildPayload({ - issuer: alice.did(), - audience: bob.did(), - capabilities: [ capability.parse(encodedCaps) ] - }) - - const encoded = token.encodePayload(payload) - const decodedString = uint8arrays.toString( - uint8arrays.fromString(encoded, "base64url"), - "utf8" - ) - - const decoded = JSON.parse(decodedString) - - expect( - JSON.stringify(decoded.att) - ).toEqual( - JSON.stringify([ encodedCaps ]) - ) - }) - -}) - - - -// VALIDATION - - -describe("token.validate", () => { - async function makeUcan() { - return await token.build({ - audience: bob.did(), - issuer: alice, - capabilities: [ - { - "with": { scheme: "wnfs", hierPart: "//boris.fission.name/public/photos/" }, - "can": { namespace: "crud", segments: [ "DELETE" ] } - }, - { - "with": { scheme: "wnfs", hierPart: "//boris.fission.name/private/84MZ7aqwKn7sNiMGsSbaxsEa6EPnQLoKYbXByxNBrCEr" }, - "can": { namespace: "wnfs", segments: [ "APPEND" ] } - }, - { - "with": { scheme: "mailto", hierPart: "boris@fission.codes" }, - "can": { namespace: "msg", segments: [ "SEND" ] } - } - ] - }) - } - - it("round-trips with token.build", async () => { - const ucan = await makeUcan() - const parsedUcan = await token.validate(token.encode(ucan)) - expect(parsedUcan).toBeDefined() - }) - - it("throws with a bad audience", async () => { - const ucan = await makeUcan() - const badPayload = { - ...ucan.payload, - aud: "fakeaudience" - } - const badUcan = `${token.encodeHeader(ucan.header)}.${token.encodePayload(badPayload)}.${ucan.signature}` - await expect(() => token.validate(badUcan)).rejects.toBeDefined() - }) - - it("throws with a bad issuer", async () => { - const ucan = await makeUcan() - const badHeader = { - ...ucan.header, - alg: "RS256" - } - const badUcan = `${token.encodeHeader(badHeader)}.${token.encodePayload(ucan.payload)}.${ucan.signature}` - await expect(() => token.validate(badUcan)).rejects.toBeDefined() - }) - - it("identifies a ucan that is not active yet", async () => { - const ucan = await makeUcan() - const badUcan = { - ...ucan, - payload: { - ...ucan.payload, - nbf: 2637252774, - exp: 2637352774 - } - } - expect(token.isTooEarly(badUcan)).toBe(true) - }) - - it("identifies a ucan that has become active", async () => { - const ucan = await makeUcan() - const activeUcan = { - ...ucan, - payload: { - ...ucan.payload, - nbf: Math.floor(Date.now() / 1000), - lifetimeInSeonds: 30 - } - } - expect(token.isTooEarly(activeUcan)).toBe(false) - }) -}) - -describe("verifySignatureUtf8", () => { - - it("works with an example", async () => { - const [ header, payload, signature ] = token.encode(await token.build({ - issuer: alice, - audience: bob.did(), - })).split(".") - expect(await verifySignatureUtf8(`${header}.${payload}`, signature, alice.did())).toEqual(true) - }) - -}) diff --git a/tests-copy/verify.test.ts b/tests-copy/verify.test.ts deleted file mode 100644 index ba91658..0000000 --- a/tests-copy/verify.test.ts +++ /dev/null @@ -1,350 +0,0 @@ -import * as token from "../src/token" -import * as capability from "../src/capability" -import { SUPERUSER } from "../src/capability/super-user" -import { verify } from "../src/verify" -import { emailCapability } from "./capability/email" -import { alice, bob, mallory } from "./fixtures" -import { REDELEGATE } from "../src/capability/ability" - - -describe("verify", () => { - - async function aliceEmailDelegationExample(expiration?: number) { - // alice -> bob, bob -> mallory - // alice delegates access to sending email as her to bob - // and bob delegates it further to mallory - const leafUcan = await token.build({ - issuer: alice, - audience: bob.did(), - expiration, - capabilities: [ emailCapability("alice@email.com") ] - }) - - const ucan = await token.build({ - issuer: bob, - audience: mallory.did(), - expiration, - capabilities: [ emailCapability("alice@email.com") ], - proofs: [ token.encode(leafUcan) ] - }) - - return token.encode(ucan) - } - - const alicesEmail = { - capability: emailCapability("alice@email.com"), - rootIssuer: alice.did(), - } - - it("verifies a delegation chain", async () => { - const ucan = await aliceEmailDelegationExample() - - const result = await verify(ucan, { - audience: mallory.did(), - requiredCapabilities: [ alicesEmail ] - }) - - if (result.ok === false) { - console.log(result.error) - } - - expect(result.ok).toEqual(true) - - if (!result.ok) return - - expect(result.value[ 0 ]?.rootIssuer).toEqual(alice.did()) - expect(result.value[ 0 ]?.capability).toEqual(emailCapability("alice@email.com")) - }) - - it("rejects an invalid escalation", async () => { - const ucan = await aliceEmailDelegationExample() - - const result = await verify(ucan, { - audience: mallory.did(), - requiredCapabilities: [ { - capability: { - ...emailCapability("alice@email.com"), - can: SUPERUSER, - }, - rootIssuer: alice.did() - } ] - }) - - expect(result.ok).toEqual(false) - }) - - it("rejects for an invalid audience", async () => { - const ucan = await aliceEmailDelegationExample() - - const result = await verify(ucan, { - audience: bob.did(), - requiredCapabilities: [ alicesEmail ] - }) - - expect(result.ok).toEqual(false) - }) - - it("rejects for an invalid rootIssuer", async () => { - const ucan = await aliceEmailDelegationExample() - - const result = await verify(ucan, { - audience: mallory.did(), - requiredCapabilities: [ { - capability: emailCapability("alice@email.com"), - // an invalid rootIssuer - rootIssuer: "did:someone-else", - } ] - }) - - expect(result.ok).toEqual(false) - }) - - it("rejects for an expired capability", async () => { - // unix timestamp in seconds. Will be after - const nowInSeconds = Math.floor(Date.now() / 1000) - // expiry is in the past - const ucan = await aliceEmailDelegationExample(nowInSeconds - 60 * 60 * 24) - - const result = await verify(ucan, { - audience: mallory.did(), - requiredCapabilities: [ alicesEmail ] - }) - - expect(result.ok).toEqual(false) - }) - - it("supports redelegation with a `prf:*` capability", async () => { - // alice -> bob - // alice delegates access to sending email as her to bob - // and bob delegates it further to mallory - const leafUcan = await token.build({ - issuer: alice, - audience: bob.did(), - capabilities: [ emailCapability("alice@email.com") ] - }) - - const ucan = await token.build({ - issuer: bob, - audience: mallory.did(), - capabilities: [ capability.prf(capability.superUser.SUPERUSER, REDELEGATE) ], - proofs: [ token.encode(leafUcan) ] - }) - - const result = await verify(token.encode(ucan), { - audience: mallory.did(), - requiredCapabilities: [ alicesEmail ] - }) - - expect(result.ok).toEqual(true) - - if (!result.ok) return - - expect(result.value[ 0 ]?.rootIssuer).toEqual(alice.did()) - expect(result.value[ 0 ]?.capability).toEqual(emailCapability("alice@email.com")) - }) - - it("supports redelegation with a `prf:1` capability", async () => { - // alice -> bob - // alice delegates access to sending email as her to bob - // and bob delegates it further to mallory - const leafUcanA = await token.build({ - issuer: alice, - audience: bob.did(), - capabilities: [ emailCapability("ignore-me@email.com") ] - }) - - const leafUcanB = await token.build({ - issuer: alice, - audience: bob.did(), - capabilities: [ emailCapability("alice@email.com") ] - }) - - const ucan = await token.build({ - issuer: bob, - audience: mallory.did(), - capabilities: [ capability.prf(1, REDELEGATE) ], - proofs: [ token.encode(leafUcanA), token.encode(leafUcanB) ] - }) - - const result = await verify(token.encode(ucan), { - audience: mallory.did(), - requiredCapabilities: [ alicesEmail ] - }) - - expect(result.ok).toEqual(true) - - if (!result.ok) return - - expect(result.value[ 0 ]?.rootIssuer).toEqual(alice.did()) - expect(result.value[ 0 ]?.capability).toEqual(emailCapability("alice@email.com")) - }) - - it("ignores other proofs not referred to by `prf:0`", async () => { - const leafUcanA = await token.build({ - issuer: alice, - audience: bob.did(), - capabilities: [ emailCapability("ignore-me@email.com") ] - }) - - const leafUcanB = await token.build({ - issuer: alice, - audience: bob.did(), - capabilities: [ emailCapability("alice@email.com") ] - }) - - const faultyUcan = await token.build({ - issuer: bob, - audience: mallory.did(), - capabilities: [ capability.prf(0, REDELEGATE) ], - proofs: [ token.encode(leafUcanA), token.encode(leafUcanB) ] - }) - - const result = await verify(token.encode(faultyUcan), { - audience: mallory.did(), - requiredCapabilities: [ alicesEmail ] - }) - - expect(result.ok).toEqual(false) - }) - - it("rejects an improper `prf` redelegation", async () => { - // alice -> bob - // alice delegates access to sending email as her to bob - // and bob delegates it further to mallory - const leafUcan = await token.build({ - issuer: alice, - audience: bob.did(), - capabilities: [ emailCapability("invalid@email.com") ] - }) - - const ucan = await token.build({ - issuer: bob, - audience: mallory.did(), - capabilities: [ capability.prf(capability.superUser.SUPERUSER, REDELEGATE) ], - proofs: [ token.encode(leafUcan) ] - }) - - const result = await verify(token.encode(ucan), { - audience: mallory.did(), - requiredCapabilities: [ alicesEmail ] - }) - - expect(result.ok).toEqual(false) - }) - - it("supports redelegation with a `my` capability", async () => { - // alice -> bob - // alice delegates access to sending email as her to bob - // and bob delegates it further to mallory - const leafUcan = await token.build({ - issuer: alice, - audience: bob.did(), - capabilities: [ capability.my(capability.superUser.SUPERUSER) ] - }) - - const ucan = await token.build({ - issuer: bob, - audience: mallory.did(), - capabilities: [ emailCapability("alice@email.com") ], - proofs: [ token.encode(leafUcan) ] - }) - - const result = await verify(token.encode(ucan), { - audience: mallory.did(), - requiredCapabilities: [ alicesEmail ], - }) - - expect(result.ok).toEqual(true) - - if (!result.ok) return - - expect(result.value[ 0 ]?.rootIssuer).toEqual(alice.did()) - }) - - it("supports redelegation with a `my` & `as` capability", async () => { - // alice -> bob, bob -> mallory, mallory -> "someone" - // alice delegates access to sending email as her to bob - // and bob delegates it further to mallory - const leafUcan = await token.build({ - issuer: alice, - audience: bob.did(), - capabilities: [ capability.my(capability.superUser.SUPERUSER) ] - }) - - const middleUcan = await token.build({ - issuer: bob, - audience: mallory.did(), - capabilities: [ capability.as(alice.did(), SUPERUSER) ], - proofs: [ token.encode(leafUcan) ] - }) - - const ucan = await token.build({ - issuer: mallory, - audience: "did:key:someone", - capabilities: [ emailCapability("alice@email.com") ], - proofs: [ token.encode(middleUcan) ] - }) - - const result = await verify(token.encode(ucan), { - audience: "did:key:someone", - requiredCapabilities: [ alicesEmail ] - }) - - expect(result.ok).toEqual(true) - - if (!result.ok) return - - expect(result.value[ 0 ]?.rootIssuer).toEqual(alice.did()) - }) - - it("rejects an improper `my` redelegation", async () => { - // alice -> bob - // alice delegates access to sending email as her to bob - // and bob delegates it further to mallory - const leafUcan = await token.build({ - issuer: alice, - audience: bob.did(), - capabilities: [] - }) - - const ucan = await token.build({ - issuer: bob, - audience: mallory.did(), - capabilities: [ emailCapability("alice@email.com") ], - proofs: [ token.encode(leafUcan) ] - }) - - const result = await verify(token.encode(ucan), { - audience: mallory.did(), - requiredCapabilities: [ alicesEmail ] - }) - - expect(result.ok).toEqual(false) - }) - - it("rejects an improper `as` redelegation - no `my`", async () => { - // alice -> bob - // alice delegates access to sending email as her to bob - // and bob delegates it further to mallory - const leafUcan = await token.build({ - issuer: alice, - audience: bob.did(), - capabilities: [ capability.as(bob.did(), SUPERUSER) ] - }) - - const ucan = await token.build({ - issuer: bob, - audience: mallory.did(), - capabilities: [ emailCapability("alice@email.com") ], - proofs: [ token.encode(leafUcan) ] - }) - - const result = await verify(token.encode(ucan), { - audience: mallory.did(), - requiredCapabilities: [ alicesEmail ] - }) - - expect(result.ok).toEqual(false) - }) - -}) From f820eee42a0938ba23f759a820160dde12f8966b Mon Sep 17 00:00:00 2001 From: dholms Date: Wed, 29 Jun 2022 09:47:10 -0500 Subject: [PATCH 07/25] patched up DID operations --- packages/core/src/did.ts | 64 +++++++++++++++++ packages/core/src/plugins.ts | 47 ++++++++++--- packages/core/src/token.ts | 6 +- packages/{plugins => core}/tests/did.test.ts | 42 +++++------- packages/plugins/src/ed25519/plugin.ts | 2 +- packages/plugins/src/p256/plugin.ts | 2 +- packages/plugins/src/rsa/crypto.ts | 6 +- packages/plugins/src/rsa/plugin.ts | 4 +- packages/plugins/tests/did-key.test.ts | 36 ---------- packages/plugins/tests/ecdsa.test.ts | 72 +++++++++++++++----- packages/plugins/tests/ed25519.test.ts | 24 ++++--- packages/plugins/tests/rsa.test.ts | 24 ++++--- 12 files changed, 211 insertions(+), 118 deletions(-) create mode 100644 packages/core/src/did.ts rename packages/{plugins => core}/tests/did.test.ts (78%) delete mode 100644 packages/plugins/tests/did-key.test.ts diff --git a/packages/core/src/did.ts b/packages/core/src/did.ts new file mode 100644 index 0000000..5065f3e --- /dev/null +++ b/packages/core/src/did.ts @@ -0,0 +1,64 @@ +import * as uint8arrays from "uint8arrays" + +import * as plugins from "./plugins.js" +// import * as compression from "./pubkey-compress.js" +// import * as rsa from "../crypto/rsa.js" +// import { BASE58_DID_PREFIX, RSA_DID_PREFIX_OLD, magicBytes, parseMagicBytes, hasPrefix } from "./prefix.js" +import { Encodings } from "./types.js" + + +// DID → PUBLIC KEY + + +/** + * Convert a DID (did:key) to a base64 public key. + */ +export function didToPublicKey(did: string, encoding: Encodings = "base64pad"): { + publicKey: string + jwtAlg: string +} { + const { publicKey, jwtAlg } = didToPublicKeyBytes(did) + return { + publicKey: uint8arrays.toString(publicKey, encoding), + jwtAlg + } +} + +/** + * Convert a DID (did:key) to the public key into bytes in SubjectPublicKeyInfo (spki) format. + * + * For consumption e.g. in the WebCrypto API. + */ +export function didToPublicKeyBytes(did: string): { + publicKey: Uint8Array + jwtAlg: string +} { + return plugins.didToPublicKeyBytes(did) +} + + + +// PUBLIC KEY → DID + + +/** + * Convert a base64 public key to a DID (did:key). + */ +export function publicKeyToDid( + publicKey: string, + jwtAlg: string, + encoding: Encodings = "base64pad" +): string { + const pubKeyBytes = uint8arrays.fromString(publicKey, encoding) + return publicKeyBytesToDid(pubKeyBytes, jwtAlg) +} + +/** + * Convert a public key in bytes to a DID (did:key). + */ +export function publicKeyBytesToDid( + publicKeyBytes: Uint8Array, + jwtAlg: string, +): string { + return plugins.publicKeyBytesToDid(publicKeyBytes, jwtAlg) +} \ No newline at end of file diff --git a/packages/core/src/plugins.ts b/packages/core/src/plugins.ts index 4d35400..d68264a 100644 --- a/packages/core/src/plugins.ts +++ b/packages/core/src/plugins.ts @@ -5,13 +5,13 @@ export type DidKeyPlugin = { jwtAlg: string didToPublicKey: (did: string) => Uint8Array publicKeyToDid: (pubkey: Uint8Array) => string - checkSignature: (publicKey: Uint8Array, data: Uint8Array, sig: Uint8Array) => Promise + verifySignature: (publicKey: Uint8Array, data: Uint8Array, sig: Uint8Array) => Promise } export type DidMethodPlugin = { isMatch: (method: string, did: string) => boolean checkJwtAlg: (did: string, jwtAlg: string) => boolean - checkSignature: (did: string, data: Uint8Array, sig: Uint8Array) => Promise + verifySignature: (did: string, data: Uint8Array, sig: Uint8Array) => Promise } export type Plugins = { @@ -21,7 +21,11 @@ export type Plugins = { let plugins: Plugins | null = null -export const checkIssuer = (did: string, jwtAlg: string): boolean => { +export const loadPlugins = (toLoad: Plugins): void => { + plugins = toLoad +} + +export const verifyIssuerAlg = (did: string, jwtAlg: string): boolean => { if(plugins === null) { throw new Error("No plugins loaded") } @@ -43,7 +47,7 @@ export const checkIssuer = (did: string, jwtAlg: string): boolean => { throw new Error(`DID method not supported by plugins: ${did}`) } -export const checkSignature = async (did: string, data: Uint8Array, sig: Uint8Array): Promise => { +export const verifySignature = async (did: string, data: Uint8Array, sig: Uint8Array): Promise => { if(plugins === null) { throw new Error("No plugins loaded") } @@ -53,21 +57,48 @@ export const checkSignature = async (did: string, data: Uint8Array, sig: Uint8Ar for (const keyPlugin of plugins.keys) { if(hasPrefix(bytes, keyPlugin.prefix)) { const publicKey = keyPlugin.didToPublicKey(did) - return keyPlugin.checkSignature(publicKey, data, sig) + return keyPlugin.verifySignature(publicKey, data, sig) } } } else { for (const didPlugin of plugins.methods) { if(didPlugin.isMatch(didMethod, did)) { - return didPlugin.checkSignature(did, data, sig) + return didPlugin.verifySignature(did, data, sig) } } } throw new Error(`DID method not supported by plugins: ${did}`) } -export const loadPlugins = (toLoad: Plugins): void => { - plugins = toLoad +export const didToPublicKeyBytes = (did: string): { publicKey: Uint8Array, jwtAlg: string } => { + if(plugins === null) { + throw new Error("No plugins loaded") + } + const didMethod = parseDidMethod(did) + if(didMethod === 'key') { + const bytes = parsePrefixedBytes(did) + for (const keyPlugin of plugins.keys) { + if(hasPrefix(bytes, keyPlugin.prefix)) { + return { + publicKey: keyPlugin.didToPublicKey(did), + jwtAlg: keyPlugin.jwtAlg + } + } + } + } + throw new Error(`DID method not supported by plugins: ${did}`) +} + +export const publicKeyBytesToDid = (publicKey: Uint8Array, jwtAlg: string): string => { + if(plugins === null) { + throw new Error("No plugins loaded") + } + for(const keyPlugin of plugins.keys) { + if(keyPlugin.jwtAlg === jwtAlg) { + return keyPlugin.publicKeyToDid(publicKey) + } + } + throw new Error(`JWT alg not supported by plugins: ${jwtAlg}`) } export const hasPrefix = ( diff --git a/packages/core/src/token.ts b/packages/core/src/token.ts index 1bb9a11..96f9ceb 100644 --- a/packages/core/src/token.ts +++ b/packages/core/src/token.ts @@ -136,7 +136,7 @@ export async function sign( } // Issuer key type must match UCAN algorithm - if (!plugins.checkIssuer(payload.iss, jwtAlg)) { + if (!plugins.verifyIssuerAlg(payload.iss, jwtAlg)) { throw new Error("The issuer's key type must match the given key type.") } @@ -325,7 +325,7 @@ export async function validate(encodedUcan: string, opts?: Partial { + + beforeAll(loadTestPlugins) it("handles RSA Keys", async () => { const pubkey = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAnzyis1ZjfNB0bBgKFMSvvkTtwlvBsaJq7S5wA+kzeVOVpVWwkWdVha4s38XM/pa/yr47av7+z3VTmvDRyAHcaT92whREFpLv9cj5lTeJSibyr/Mrm/YtjCZVWgaOYIhwrXwKLqPr/11inWsAkfIytvHWTxZYEcXLgAXFuUuaS3uF9gEiNQwzGTU1v0FqkqTBr4B8nW3HCN47XUu0t8Y0e+lf4s4OxQawWD79J9/5d3Ry0vbV3Am1FtGJiJvOwRsIfVChDpYStTcHTCMqtvWbV6L11BWkpzGXSW4Hv43qa+GSYOD2QU68Mb59oSk2OB+BtOLpJofmbGEGgvmwyCI9MwIDAQAB" // old: const expectedDid = "did:key:z13V3Sog2YaUKhdGCmgx9UZuW1o1ShFJYc6DvGYe7NTt689NoL2RtpVs65Zw899YrTN9WuxdEEDm54YxWuQHQvcKfkZwa8HTgokHxGDPEmNLhvh69zUMEP4zjuARQ3T8bMUumkSLGpxNe1bfQX624ef45GhWb3S9HM3gvAJ7Qftm8iqnDQVcxwKHjmkV4hveKMTix4bTRhieVHi1oqU4QCVy4QPWpAAympuCP9dAoJFxSP6TNBLY9vPKLazsg7XcFov6UuLWsEaxJ5SomCpDx181mEgW2qTug5oQbrJwExbD9CMgXHLVDE2QgLoQMmgsrPevX57dH715NXC2uY6vo2mYCzRY4KuDRUsrkuYCkewL8q2oK1BEDVvi3Sg8pbC9QYQ5mMiHf8uxiHxTAmPedv8" const expectedDid = "did:key:z4MXj1wBzi9jUstyNvmiK5WLRRL4rr9UvzPxhry1CudCLKWLyMbP1WoTwDfttBTpxDKf5hAJEjqNbeYx2EEvrJmSWHAu7TJRPTrE3QodbMfRvRNRDyYvaN1FSQus2ziS1rWXwAi5Gpc16bY3JwjyLCPJLfdRWHZhRXiay5FWEkfoSKy6aftnzAvqNkKBg2AxgzGMinR6d1WiH4w5mEXFtUeZkeo4uwtRTd8rD9BoVaHVkGwJkksDybE23CsBNXiNfbweFVRcwfTMhcQsTsYhUWDcSC6QE3zt9h4Rsrj7XRYdwYSK5bc1qFRsg5HULKBp2uZ1gcayiW2FqHFcMRjBieC4LnSMSD1AZB1WUncVRbPpVkn1UGhCU" - const result = did.publicKeyToDid(pubkey, "rsa") + const result = did.publicKeyToDid(pubkey, "RS256") expect(result).toEqual(expectedDid) }) it("handles Ed25519 Keys", async () => { const pubkey = "Hv+AVRD2WUjUFOsSNbsmrp9fokuwrUnjBcr92f0kxw4=" const expectedDid = "did:key:z6MkgYGF3thn8k1Fv4p4dWXKtsXCnLH7q9yw4QgNPULDmDKB" - const result = did.publicKeyToDid(pubkey, "ed25519") - expect(result).toEqual(expectedDid) - }) - - it("handles BLS12-381 Keys", async () => { - const pubkey = "Hv+AVRD2WUjUFOsSNbsmrp9fokuwrUnjBcr92f0kxw4=" - const expectedDid = "did:key:z6HpYD1br5P4QVh5rjRGAkBfKMWes44uhKmKdJ6dN2Nm9gHK" - const result = did.publicKeyToDid(pubkey, "bls12-381") + const result = did.publicKeyToDid(pubkey, "EdDSA") expect(result).toEqual(expectedDid) }) it("handles NIST P-256 Keys", async () => { const pubkey = "BEgg6smRlRQXKqzJHu0w5/nHcy7zYNxFXwTKUyyCOoDMztUvQn5VZg668v4MecHGUZTeHkaZhSraK7RtJuXfWp4=" const expectedDid = "did:key:zDnaeVHXWgKUpJFc8AUfRDuRWfsAQejmZu5HrERh41iGumZVu" - const result = did.publicKeyToDid(pubkey, "p256") + const result = did.publicKeyToDid(pubkey, "ES256") expect(result).toEqual(expectedDid) }) @@ -35,44 +31,38 @@ describe("publicKeyToDid", () => { describe("didToPublicKey", () => { + beforeAll(loadTestPlugins) + it("handles old RSA Keys", async () => { const toDecode = "did:key:z13V3Sog2YaUKhdGCmgx9UZuW1o1ShFJYc6DvGYe7NTt689NoL2RtpVs65Zw899YrTN9WuxdEEDm54YxWuQHQvcKfkZwa8HTgokHxGDPEmNLhvh69zUMEP4zjuARQ3T8bMUumkSLGpxNe1bfQX624ef45GhWb3S9HM3gvAJ7Qftm8iqnDQVcxwKHjmkV4hveKMTix4bTRhieVHi1oqU4QCVy4QPWpAAympuCP9dAoJFxSP6TNBLY9vPKLazsg7XcFov6UuLWsEaxJ5SomCpDx181mEgW2qTug5oQbrJwExbD9CMgXHLVDE2QgLoQMmgsrPevX57dH715NXC2uY6vo2mYCzRY4KuDRUsrkuYCkewL8q2oK1BEDVvi3Sg8pbC9QYQ5mMiHf8uxiHxTAmPedv8" const expectedKey = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAnzyis1ZjfNB0bBgKFMSvvkTtwlvBsaJq7S5wA+kzeVOVpVWwkWdVha4s38XM/pa/yr47av7+z3VTmvDRyAHcaT92whREFpLv9cj5lTeJSibyr/Mrm/YtjCZVWgaOYIhwrXwKLqPr/11inWsAkfIytvHWTxZYEcXLgAXFuUuaS3uF9gEiNQwzGTU1v0FqkqTBr4B8nW3HCN47XUu0t8Y0e+lf4s4OxQawWD79J9/5d3Ry0vbV3Am1FtGJiJvOwRsIfVChDpYStTcHTCMqtvWbV6L11BWkpzGXSW4Hv43qa+GSYOD2QU68Mb59oSk2OB+BtOLpJofmbGEGgvmwyCI9MwIDAQAB" - const { publicKey, type } = did.didToPublicKey(toDecode) + const { publicKey, jwtAlg } = did.didToPublicKey(toDecode) expect(publicKey).toEqual(expectedKey) - expect(type).toEqual("rsa") + expect(jwtAlg).toEqual("RS256") }) it("handles standardized RSA Keys", async () => { const toDecode = "did:key:z4MXj1wBzi9jUstyNvmiK5WLRRL4rr9UvzPxhry1CudCLKWLyMbP1WoTwDfttBTpxDKf5hAJEjqNbeYx2EEvrJmSWHAu7TJRPTrE3QodbMfRvRNRDyYvaN1FSQus2ziS1rWXwAi5Gpc16bY3JwjyLCPJLfdRWHZhRXiay5FWEkfoSKy6aftnzAvqNkKBg2AxgzGMinR6d1WiH4w5mEXFtUeZkeo4uwtRTd8rD9BoVaHVkGwJkksDybE23CsBNXiNfbweFVRcwfTMhcQsTsYhUWDcSC6QE3zt9h4Rsrj7XRYdwYSK5bc1qFRsg5HULKBp2uZ1gcayiW2FqHFcMRjBieC4LnSMSD1AZB1WUncVRbPpVkn1UGhCU" const expectedKey = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAnzyis1ZjfNB0bBgKFMSvvkTtwlvBsaJq7S5wA+kzeVOVpVWwkWdVha4s38XM/pa/yr47av7+z3VTmvDRyAHcaT92whREFpLv9cj5lTeJSibyr/Mrm/YtjCZVWgaOYIhwrXwKLqPr/11inWsAkfIytvHWTxZYEcXLgAXFuUuaS3uF9gEiNQwzGTU1v0FqkqTBr4B8nW3HCN47XUu0t8Y0e+lf4s4OxQawWD79J9/5d3Ry0vbV3Am1FtGJiJvOwRsIfVChDpYStTcHTCMqtvWbV6L11BWkpzGXSW4Hv43qa+GSYOD2QU68Mb59oSk2OB+BtOLpJofmbGEGgvmwyCI9MwIDAQAB" - const { publicKey, type } = did.didToPublicKey(toDecode) + const { publicKey, jwtAlg } = did.didToPublicKey(toDecode) expect(publicKey).toEqual(expectedKey) - expect(type).toEqual("rsa") + expect(jwtAlg).toEqual("RS256") }) it("handles Ed25519 Keys", async () => { const toDecode = "did:key:z6MkgYGF3thn8k1Fv4p4dWXKtsXCnLH7q9yw4QgNPULDmDKB" const expectedKey = "Hv+AVRD2WUjUFOsSNbsmrp9fokuwrUnjBcr92f0kxw4=" - const { publicKey, type } = did.didToPublicKey(toDecode) - expect(publicKey).toEqual(expectedKey) - expect(type).toEqual("ed25519") - }) - - it("handles BLS12-381 Keys", async () => { - const toDecode = "did:key:z6HpYD1br5P4QVh5rjRGAkBfKMWes44uhKmKdJ6dN2Nm9gHK" - const expectedKey = "Hv+AVRD2WUjUFOsSNbsmrp9fokuwrUnjBcr92f0kxw4=" - const { publicKey, type } = did.didToPublicKey(toDecode) + const { publicKey, jwtAlg } = did.didToPublicKey(toDecode) expect(publicKey).toEqual(expectedKey) - expect(type).toEqual("bls12-381") + expect(jwtAlg).toEqual("EdDSA") }) it("handles NIST P-256 Keys", async () => { const toDecode = "did:key:zDnaeVHXWgKUpJFc8AUfRDuRWfsAQejmZu5HrERh41iGumZVu" const expectedKey = "BEgg6smRlRQXKqzJHu0w5/nHcy7zYNxFXwTKUyyCOoDMztUvQn5VZg668v4MecHGUZTeHkaZhSraK7RtJuXfWp4=" - const { publicKey, type } = did.didToPublicKey(toDecode) + const { publicKey, jwtAlg } = did.didToPublicKey(toDecode) expect(publicKey).toEqual(expectedKey) - expect(type).toEqual("p256") + expect(jwtAlg).toEqual("ES256") }) }) diff --git a/packages/plugins/src/ed25519/plugin.ts b/packages/plugins/src/ed25519/plugin.ts index cbbe3fc..867cfcc 100644 --- a/packages/plugins/src/ed25519/plugin.ts +++ b/packages/plugins/src/ed25519/plugin.ts @@ -9,7 +9,7 @@ export const ed25519Plugin: DidKeyPlugin = { jwtAlg: 'EdDSA', didToPublicKey: crypto.didToPublickey, publicKeyToDid: crypto.publicKeyToDid, - checkSignature: async (publicKey: Uint8Array, data: Uint8Array, sig: Uint8Array) => { + verifySignature: async (publicKey: Uint8Array, data: Uint8Array, sig: Uint8Array) => { return ed25519.verify(publicKey, data, sig) } } \ No newline at end of file diff --git a/packages/plugins/src/p256/plugin.ts b/packages/plugins/src/p256/plugin.ts index e42a3e9..b8aca14 100644 --- a/packages/plugins/src/p256/plugin.ts +++ b/packages/plugins/src/p256/plugin.ts @@ -7,5 +7,5 @@ export const p256Plugin: DidKeyPlugin = { jwtAlg: 'ES256', didToPublicKey: crypto.didToPublicKey, publicKeyToDid: crypto.publicKeyToDid, - checkSignature: crypto.verify, + verifySignature: crypto.verify, } \ No newline at end of file diff --git a/packages/plugins/src/rsa/crypto.ts b/packages/plugins/src/rsa/crypto.ts index 8c9f1fa..c4be4ca 100644 --- a/packages/plugins/src/rsa/crypto.ts +++ b/packages/plugins/src/rsa/crypto.ts @@ -1,6 +1,6 @@ import { webcrypto } from "one-webcrypto" import * as uint8arrays from "uint8arrays" -import { RSA_DID_PREFIX } from "../prefixes" +import { RSA_DID_PREFIX, RSA_DID_PREFIX_OLD } from "../prefixes" import { didFromKeyBytes, keyBytesFromDid } from "../util.js" export const RSA_ALG = "RSASSA-PKCS1-v1_5" @@ -67,7 +67,7 @@ export const didToPublicKey = (did: string): Uint8Array => { } export const oldDidToPublicKey = (did: string): Uint8Array => { - return keyBytesFromDid(did, RSA_DID_PREFIX) + return keyBytesFromDid(did, RSA_DID_PREFIX_OLD) } export const publicKeyToDid = (pubkey: Uint8Array): string => { @@ -80,7 +80,7 @@ export const publicKeyToDid = (pubkey: Uint8Array): string => { } export const publicKeyToOldDid = (pubkey: Uint8Array): string => { - return didFromKeyBytes(pubkey, RSA_DID_PREFIX) + return didFromKeyBytes(pubkey, RSA_DID_PREFIX_OLD) } diff --git a/packages/plugins/src/rsa/plugin.ts b/packages/plugins/src/rsa/plugin.ts index a37a113..c2b396d 100644 --- a/packages/plugins/src/rsa/plugin.ts +++ b/packages/plugins/src/rsa/plugin.ts @@ -7,7 +7,7 @@ export const rsaPlugin: DidKeyPlugin = { jwtAlg: 'RS256', didToPublicKey: crypto.didToPublicKey, publicKeyToDid: crypto.publicKeyToDid, - checkSignature: crypto.verify, + verifySignature: crypto.verify, } export const rsaOldPlugin: DidKeyPlugin = { @@ -15,6 +15,6 @@ export const rsaOldPlugin: DidKeyPlugin = { jwtAlg: 'RS256', didToPublicKey: crypto.oldDidToPublicKey, publicKeyToDid: crypto.publicKeyToOldDid, - checkSignature: crypto.verify, + verifySignature: crypto.verify, } diff --git a/packages/plugins/tests/did-key.test.ts b/packages/plugins/tests/did-key.test.ts deleted file mode 100644 index 9ad2824..0000000 --- a/packages/plugins/tests/did-key.test.ts +++ /dev/null @@ -1,36 +0,0 @@ -import EcdsaKeypair from "../src/keypair/ecdsa.js" - -// did:key test vectors from W3C -// https://github.com/w3c-ccg/did-method-key/blob/main/test-vectors/nist-curves.json -const testVectors = [ - { - id: "did:key:zDnaerx9CtbPJ1q36T5Ln5wYt3MQYeGRG5ehnPAmxcf5mDZpv", - jwk: { - kty: "EC", - crv: "P-256", - x: "igrFmi0whuihKnj9R3Om1SoMph72wUGeFaBbzG2vzns", - y: "efsX5b10x8yjyrj4ny3pGfLcY7Xby1KzgqOdqnsrJIM", - d: "gPh-VvVS8MbvKQ9LSVVmfnxnKjHn4Tqj0bmbpehRlpc" - } - }, - { - id: "did:key:zDnaerDaTF5BXEavCrfRZEk316dpbLsfPDZ3WJ5hRTPFU2169", - jwk: { - kty: "EC", - crv: "P-256", - x: "fyNYMN0976ci7xqiSdag3buk-ZCwgXU4kz9XNkBlNUI", - y: "hW2ojTNfH7Jbi8--CJUo3OCbH3y5n91g-IMA9MLMbTU", - d: "YjRs6vNvw4sYrzVVY8ipkEpDAD9PFqw1sUnvPRMA-WI" - } - } -] - -describe("did:key ECDSA P-256", () => { - it("derives the correct DID from the JWK", async () => { - for(const vector of testVectors) { - const keypair = await EcdsaKeypair.importFromJwk(vector.jwk) - const did = keypair.did() - expect(did).toEqual(vector.id) - } - }) -}) \ No newline at end of file diff --git a/packages/plugins/tests/ecdsa.test.ts b/packages/plugins/tests/ecdsa.test.ts index a1aa69b..c7642e1 100644 --- a/packages/plugins/tests/ecdsa.test.ts +++ b/packages/plugins/tests/ecdsa.test.ts @@ -1,35 +1,71 @@ import { p256Plugin } from "../src/p256/plugin.js" -// import * as did from "../src/did" -import EcdsaKeyPair from "../src/p256/keypair.js" +import EcdsaKeypair from "../src/p256/keypair.js" describe("ecdsa", () => { - let p256Keypair: EcdsaKeyPair - - let p256Signature: Uint8Array - + let keypair: EcdsaKeypair + let signature: Uint8Array const data = new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8, 9]) - it("creates an ecdsa keypairs with different curves", async () => { - p256Keypair = await EcdsaKeyPair.create() + it("creates an ecdsa keypair", async () => { + keypair = await EcdsaKeypair.create() + }) + + it("has the correct JWT alg", async () => { + expect(keypair.jwtAlg).toEqual("ES256") }) - it("returns a publicKeyStr and did: curve P-256", () => { - const keyDid = p256Keypair.did() - const publicKey = p256Keypair.publicKeyStr() - const transformed = did.didToPublicKey(keyDid) - expect(transformed.publicKey).toEqual(publicKey) - expect(transformed.type).toEqual("p256") + it("can transform between DID & public key", () => { + const did = keypair.did() + const publicKey = p256Plugin.didToPublicKey(did) + const didAgain = p256Plugin.publicKeyToDid(publicKey) + expect(did).toEqual(didAgain) }) - it("signs data: curve P-256", async () => { - p256Signature = await p256Keypair.sign(data) + it("signs data", async () => { + signature = await keypair.sign(data) }) - it("can verify signature: P-256", async () => { - const isValid = await did.verifySignature(data, p256Signature, p256Keypair.did()) + it("can verify signature", async () => { + const publicKey = p256Plugin.didToPublicKey(keypair.did()) + const isValid = await p256Plugin.verifySignature(publicKey, data, signature) expect(isValid).toBeTruthy() }) }) + +// did:key test vectors from W3C +// https://github.com/w3c-ccg/did-method-key/blob/main/test-vectors/nist-curves.json +const testVectors = [ + { + id: "did:key:zDnaerx9CtbPJ1q36T5Ln5wYt3MQYeGRG5ehnPAmxcf5mDZpv", + jwk: { + kty: "EC", + crv: "P-256", + x: "igrFmi0whuihKnj9R3Om1SoMph72wUGeFaBbzG2vzns", + y: "efsX5b10x8yjyrj4ny3pGfLcY7Xby1KzgqOdqnsrJIM", + d: "gPh-VvVS8MbvKQ9LSVVmfnxnKjHn4Tqj0bmbpehRlpc" + } + }, + { + id: "did:key:zDnaerDaTF5BXEavCrfRZEk316dpbLsfPDZ3WJ5hRTPFU2169", + jwk: { + kty: "EC", + crv: "P-256", + x: "fyNYMN0976ci7xqiSdag3buk-ZCwgXU4kz9XNkBlNUI", + y: "hW2ojTNfH7Jbi8--CJUo3OCbH3y5n91g-IMA9MLMbTU", + d: "YjRs6vNvw4sYrzVVY8ipkEpDAD9PFqw1sUnvPRMA-WI" + } + } +] + +describe("ecdsa did:key", () => { + it("derives the correct DID from the JWK", async () => { + for(const vector of testVectors) { + const keypair = await EcdsaKeypair.importFromJwk(vector.jwk) + const did = keypair.did() + expect(did).toEqual(vector.id) + } + }) +}) \ No newline at end of file diff --git a/packages/plugins/tests/ed25519.test.ts b/packages/plugins/tests/ed25519.test.ts index 65769d9..338dbe2 100644 --- a/packages/plugins/tests/ed25519.test.ts +++ b/packages/plugins/tests/ed25519.test.ts @@ -1,5 +1,5 @@ -import * as did from "../src/did" -import EdwardsKey from "../src/keypair/ed25519" +import { ed25519Plugin } from "../src/ed25519/plugin.js" +import EdwardsKey from "../src/ed25519/keypair.js" describe("ed25519", () => { @@ -11,12 +11,15 @@ describe("ed25519", () => { keypair = await EdwardsKey.create() }) - it("returns a publicKeyStr and did", () => { - const publicKey = keypair.publicKeyStr() - const keyDid = keypair.did() - const transformed = did.didToPublicKey(keyDid) - expect(transformed.publicKey).toEqual(publicKey) - expect(transformed.type).toEqual("ed25519") + it("has the correct JWT alg", async () => { + expect(keypair.jwtAlg).toEqual("EdDSA") + }) + + it("can transform between DID & public key", () => { + const did = keypair.did() + const publicKey = ed25519Plugin.didToPublicKey(did) + const didAgain = ed25519Plugin.publicKeyToDid(publicKey) + expect(did).toEqual(didAgain) }) it("signs data", async () => { @@ -24,8 +27,9 @@ describe("ed25519", () => { }) it("can verify signature", async () => { - const isValid = await did.verifySignature(data, signature, keypair.did()) - expect(isValid).toEqual(true) + const publicKey = ed25519Plugin.didToPublicKey(keypair.did()) + const isValid = await ed25519Plugin.verifySignature(publicKey, data, signature) + expect(isValid).toBeTruthy() }) }) diff --git a/packages/plugins/tests/rsa.test.ts b/packages/plugins/tests/rsa.test.ts index 70950ce..ab47538 100644 --- a/packages/plugins/tests/rsa.test.ts +++ b/packages/plugins/tests/rsa.test.ts @@ -1,8 +1,8 @@ import * as fc from "fast-check" import * as uint8arrays from "uint8arrays" -import * as did from "../src/did" -import * as rsaCrypto from "../src/crypto/rsa" -import RSAKeypair from "../src/keypair/rsa" +import { rsaPlugin } from "../src/rsa/plugin.js" +import * as rsaCrypto from "../src/rsa/crypto.js" +import RSAKeypair from "../src/rsa/keypair.js" describe("rsa", () => { @@ -15,12 +15,15 @@ describe("rsa", () => { keypair = await RSAKeypair.create() }) + it("has the correct JWT alg", async () => { + expect(keypair.jwtAlg).toEqual("RS256") + }) + it("returns a publicKeyStr and did", () => { - const publicKey = keypair.publicKeyStr() - const keyDid = keypair.did() - const transformed = did.didToPublicKey(keyDid) - expect(transformed.publicKey).toEqual(publicKey) - expect(transformed.type).toEqual("rsa") + const did = keypair.did() + const publicKey = rsaPlugin.didToPublicKey(did) + const didAgain = rsaPlugin.publicKeyToDid(publicKey) + expect(did).toEqual(didAgain) }) it("signs data", async () => { @@ -28,8 +31,9 @@ describe("rsa", () => { }) it("can verify signature", async () => { - const isValid = await did.verifySignature(data, signature, keypair.did()) - expect(isValid).toEqual(true) + const publicKey = rsaPlugin.didToPublicKey(keypair.did()) + const isValid = await rsaPlugin.verifySignature(publicKey, data, signature) + expect(isValid).toBeTruthy() }) }) From 93b20ec03ebdda92573ff69bcab1f073ec56ecc1 Mon Sep 17 00:00:00 2001 From: dholms Date: Wed, 29 Jun 2022 10:19:42 -0500 Subject: [PATCH 08/25] cleanup --- .eslintrc.js | 2 +- package.json | 2 +- packages/core/package.json | 9 +- packages/core/src/plugins.ts | 14 +- packages/core/src/token.ts | 2 +- packages/core/tests/fixtures.ts | 2 +- packages/core/tests/setup.ts | 4 +- packages/plugins/.eslintrc.js | 32 -- packages/plugins/package.json | 4 +- packages/plugins/src/default-plugins.ts | 2 +- packages/plugins/src/ed25519/plugin.ts | 4 +- packages/plugins/src/p256/plugin.ts | 4 +- packages/plugins/src/rsa/plugin.ts | 6 +- packages/plugins/src/util.ts | 4 +- packages/ucans/jest.config.js | 21 -- packages/ucans/package.json | 6 +- packages/ucans/tests/attenuation.test.ts | 155 -------- packages/ucans/tests/builder.test.ts | 135 ------- packages/ucans/tests/capability.test.ts | 22 -- packages/ucans/tests/capability/email.ts | 82 ----- packages/ucans/tests/capability/wnfs.test.ts | 238 ------------- packages/ucans/tests/capability/wnfs.ts | 213 ----------- packages/ucans/tests/compatibility.test.ts | 39 --- packages/ucans/tests/did-key.ts | 36 -- packages/ucans/tests/did.test.ts | 78 ----- packages/ucans/tests/ecdsa.test.ts | 34 -- packages/ucans/tests/ed25519.test.ts | 31 -- packages/ucans/tests/fixtures.ts | 19 - packages/ucans/tests/rsa.test.ts | 96 ----- packages/ucans/tests/semver.test.ts | 89 ----- packages/ucans/tests/store.test.ts | 142 -------- packages/ucans/tests/token.test.ts | 175 ---------- packages/ucans/tests/verify.test.ts | 350 ------------------- tsconfig.eslint.json | 5 - 34 files changed, 34 insertions(+), 2023 deletions(-) delete mode 100644 packages/plugins/.eslintrc.js delete mode 100644 packages/ucans/jest.config.js delete mode 100644 packages/ucans/tests/attenuation.test.ts delete mode 100644 packages/ucans/tests/builder.test.ts delete mode 100644 packages/ucans/tests/capability.test.ts delete mode 100644 packages/ucans/tests/capability/email.ts delete mode 100644 packages/ucans/tests/capability/wnfs.test.ts delete mode 100644 packages/ucans/tests/capability/wnfs.ts delete mode 100644 packages/ucans/tests/compatibility.test.ts delete mode 100644 packages/ucans/tests/did-key.ts delete mode 100644 packages/ucans/tests/did.test.ts delete mode 100644 packages/ucans/tests/ecdsa.test.ts delete mode 100644 packages/ucans/tests/ed25519.test.ts delete mode 100644 packages/ucans/tests/fixtures.ts delete mode 100644 packages/ucans/tests/rsa.test.ts delete mode 100644 packages/ucans/tests/semver.test.ts delete mode 100644 packages/ucans/tests/store.test.ts delete mode 100644 packages/ucans/tests/token.test.ts delete mode 100644 packages/ucans/tests/verify.test.ts delete mode 100644 tsconfig.eslint.json diff --git a/.eslintrc.js b/.eslintrc.js index 227ccc1..4bde45a 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -1,4 +1,4 @@ -export default { +module.exports = { root: true, parser: "@typescript-eslint/parser", parserOptions: { diff --git a/package.json b/package.json index d35aca2..5a91d77 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,7 @@ "author": "Daniel Holmgren ", "repository": { "type": "git", - "url": "https://github.com/fission-suite/ucan" + "url": "https://github.com/ucan-wg/ts-ucan" }, "homepage": "https://guide.fission.codes", "license": "Apache-2.0", diff --git a/packages/core/package.json b/packages/core/package.json index 72099d2..d8e319c 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,11 +1,11 @@ { "name": "@ucans/core", "version": "0.9.1", - "description": "Typescript implementation of UCANs", + "description": "Core UCAN implementation", "author": "Daniel Holmgren ", "repository": { "type": "git", - "url": "https://github.com/fission-suite/ucan" + "url": "https://github.com/ucan-wg/ts-ucan" }, "homepage": "https://guide.fission.codes", "license": "Apache-2.0", @@ -20,7 +20,7 @@ "dist:cjs": "tsc --project ./dist/ --module commonjs --outDir ./dist/cjs/ --sourceMap", "dist:esm": "tsc --project ./dist/ --module es2020 --outDir ./dist/esm/ --sourceMap", "dist:pkg": "node ../../scripts/package.js", - "dist:prep": "copyfiles --error tsconfig.json ./dist/", + "dist:prep": "copyfiles --error ../../tsconfig.json ./dist/", "dist:src": "copyfiles --error --up 1 \"./src/**/*\" ./dist/src/", "dist:types": "tsc --project ./dist/ --emitDeclarationOnly --declaration --declarationDir ./dist/types/", "lint": "eslint src/**/*.ts src/*.ts tests/**/*.ts tests/*.ts", @@ -64,6 +64,9 @@ "LICENSE", "README.md" ], + "dependencies": { + "uint8arrays": "^3.0.0" + }, "devDependencies": { "@types/jest": "^27.4.1", "@types/node": "^17.0.23", diff --git a/packages/core/src/plugins.ts b/packages/core/src/plugins.ts index d68264a..0b2f6a4 100644 --- a/packages/core/src/plugins.ts +++ b/packages/core/src/plugins.ts @@ -1,4 +1,4 @@ -import * as uint8arrays from 'uint8arrays' +import * as uint8arrays from "uint8arrays" export type DidKeyPlugin = { prefix: Uint8Array @@ -30,7 +30,7 @@ export const verifyIssuerAlg = (did: string, jwtAlg: string): boolean => { throw new Error("No plugins loaded") } const didMethod = parseDidMethod(did) - if(didMethod === 'key') { + if(didMethod === "key") { const bytes = parsePrefixedBytes(did) for (const keyPlugin of plugins.keys) { if(hasPrefix(bytes, keyPlugin.prefix)) { @@ -52,7 +52,7 @@ export const verifySignature = async (did: string, data: Uint8Array, sig: Uint8A throw new Error("No plugins loaded") } const didMethod = parseDidMethod(did) - if(didMethod === 'key') { + if(didMethod === "key") { const bytes = parsePrefixedBytes(did) for (const keyPlugin of plugins.keys) { if(hasPrefix(bytes, keyPlugin.prefix)) { @@ -70,12 +70,12 @@ export const verifySignature = async (did: string, data: Uint8Array, sig: Uint8A throw new Error(`DID method not supported by plugins: ${did}`) } -export const didToPublicKeyBytes = (did: string): { publicKey: Uint8Array, jwtAlg: string } => { +export const didToPublicKeyBytes = (did: string): { publicKey: Uint8Array; jwtAlg: string } => { if(plugins === null) { throw new Error("No plugins loaded") } const didMethod = parseDidMethod(did) - if(didMethod === 'key') { + if(didMethod === "key") { const bytes = parsePrefixedBytes(did) for (const keyPlugin of plugins.keys) { if(hasPrefix(bytes, keyPlugin.prefix)) { @@ -123,8 +123,8 @@ const parsePrefixedBytes = (did: string): Uint8Array => { } const parseDidMethod = (did: string) => { - const parts = did.split(':') - if(parts[0] !== 'did') { + const parts = did.split(":") + if(parts[0] !== "did") { throw new Error(`Not a DID: ${did}`) } if(parts[1].length < 1) { diff --git a/packages/core/src/token.ts b/packages/core/src/token.ts index 96f9ceb..b390532 100644 --- a/packages/core/src/token.ts +++ b/packages/core/src/token.ts @@ -3,7 +3,7 @@ import * as uint8arrays from "uint8arrays" // @IMPORT import * as semver from "./semver.js" import * as capability from "./capability/index.js" import * as util from "./util.js" -import * as plugins from './plugins.js' +import * as plugins from "./plugins.js" import { Capability, isCapability, isEncodedCapability } from "./capability/index.js" import { Fact, Keypair, Didable } from "./types.js" diff --git a/packages/core/tests/fixtures.ts b/packages/core/tests/fixtures.ts index 83708e1..b34bfcc 100644 --- a/packages/core/tests/fixtures.ts +++ b/packages/core/tests/fixtures.ts @@ -1,4 +1,4 @@ -import { EdKeypair } from '@ucans/plugins' +import { EdKeypair } from "@ucans/plugins" /** did:key:z6Mkk89bC3JrVqKie71YEcc5M1SMVxuCgNx6zLZ8SYJsxALi */ export const alice = EdKeypair.fromSecretKey("U+bzp2GaFQHso587iSFWPSeCzbSfn/CbNHEz7ilKRZ1UQMmMS7qq4UhTzKn3X9Nj/4xgrwa+UqhMOeo4Ki8JUw==") diff --git a/packages/core/tests/setup.ts b/packages/core/tests/setup.ts index 769cd58..937a9eb 100644 --- a/packages/core/tests/setup.ts +++ b/packages/core/tests/setup.ts @@ -1,5 +1,5 @@ -import * as plugins from '@ucans/plugins' -import { loadPlugins } from '../src/plugins' +import * as plugins from "@ucans/plugins" +import { loadPlugins } from "../src/plugins" export const loadTestPlugins = () => { loadPlugins(plugins.defaults) diff --git a/packages/plugins/.eslintrc.js b/packages/plugins/.eslintrc.js deleted file mode 100644 index 227ccc1..0000000 --- a/packages/plugins/.eslintrc.js +++ /dev/null @@ -1,32 +0,0 @@ -export default { - root: true, - parser: "@typescript-eslint/parser", - parserOptions: { - project: `./tsconfig.eslint.json` - }, - plugins: [ - "@typescript-eslint", - ], - extends: [ - "eslint:recommended", - "plugin:@typescript-eslint/eslint-recommended", - "plugin:@typescript-eslint/recommended", - ], - rules: { - "@typescript-eslint/member-delimiter-style": [ "error", { - "multiline": { - "delimiter": "none", - "requireLast": false - }, - } ], - "@typescript-eslint/no-use-before-define": [ "off" ], - "@typescript-eslint/semi": [ "error", "never" ], - "@typescript-eslint/ban-ts-comment": 1, - "@typescript-eslint/quotes": [ "error", "double", { - allowTemplateLiterals: true - } ], - // If you want to *intentionally* run a promise without awaiting, prepend it with "void " instead of "await " - "@typescript-eslint/no-floating-promises": [ "error" ], - "@typescript-eslint/no-inferrable-types": [ "off" ], - } -} diff --git a/packages/plugins/package.json b/packages/plugins/package.json index 98abee9..ddd5fd1 100644 --- a/packages/plugins/package.json +++ b/packages/plugins/package.json @@ -1,11 +1,11 @@ { "name": "@ucans/plugins", "version": "0.9.1", - "description": "Typescript implementation of UCANs", + "description": "Default UCAN plugin set", "author": "Daniel Holmgren ", "repository": { "type": "git", - "url": "https://github.com/fission-suite/ucan" + "url": "https://github.com/ucan-wg/ts-ucan" }, "homepage": "https://guide.fission.codes", "license": "Apache-2.0", diff --git a/packages/plugins/src/default-plugins.ts b/packages/plugins/src/default-plugins.ts index c567371..0809af6 100644 --- a/packages/plugins/src/default-plugins.ts +++ b/packages/plugins/src/default-plugins.ts @@ -1,4 +1,4 @@ -import { Plugins } from '@ucans/core' +import { Plugins } from "@ucans/core" import { ed25519Plugin } from "./ed25519/plugin.js" import { p256Plugin } from "./p256/plugin.js" import { rsaPlugin, rsaOldPlugin } from "./rsa/plugin.js" diff --git a/packages/plugins/src/ed25519/plugin.ts b/packages/plugins/src/ed25519/plugin.ts index 867cfcc..45605e9 100644 --- a/packages/plugins/src/ed25519/plugin.ts +++ b/packages/plugins/src/ed25519/plugin.ts @@ -1,4 +1,4 @@ -import { DidKeyPlugin } from '@ucans/core' +import { DidKeyPlugin } from "@ucans/core" import * as ed25519 from "@stablelib/ed25519" import * as crypto from "./crypto.js" @@ -6,7 +6,7 @@ import { EDWARDS_DID_PREFIX } from "../prefixes.js" export const ed25519Plugin: DidKeyPlugin = { prefix: EDWARDS_DID_PREFIX, - jwtAlg: 'EdDSA', + jwtAlg: "EdDSA", didToPublicKey: crypto.didToPublickey, publicKeyToDid: crypto.publicKeyToDid, verifySignature: async (publicKey: Uint8Array, data: Uint8Array, sig: Uint8Array) => { diff --git a/packages/plugins/src/p256/plugin.ts b/packages/plugins/src/p256/plugin.ts index b8aca14..abba0ae 100644 --- a/packages/plugins/src/p256/plugin.ts +++ b/packages/plugins/src/p256/plugin.ts @@ -1,10 +1,10 @@ -import { DidKeyPlugin } from '@ucans/core' +import { DidKeyPlugin } from "@ucans/core" import * as crypto from "./crypto.js" import { P256_DID_PREFIX } from "../prefixes.js" export const p256Plugin: DidKeyPlugin = { prefix: P256_DID_PREFIX, - jwtAlg: 'ES256', + jwtAlg: "ES256", didToPublicKey: crypto.didToPublicKey, publicKeyToDid: crypto.publicKeyToDid, verifySignature: crypto.verify, diff --git a/packages/plugins/src/rsa/plugin.ts b/packages/plugins/src/rsa/plugin.ts index c2b396d..d56bc7f 100644 --- a/packages/plugins/src/rsa/plugin.ts +++ b/packages/plugins/src/rsa/plugin.ts @@ -1,10 +1,10 @@ -import { DidKeyPlugin } from '@ucans/core' +import { DidKeyPlugin } from "@ucans/core" import * as crypto from "./crypto.js" import { RSA_DID_PREFIX, RSA_DID_PREFIX_OLD } from "../prefixes.js" export const rsaPlugin: DidKeyPlugin = { prefix: RSA_DID_PREFIX, - jwtAlg: 'RS256', + jwtAlg: "RS256", didToPublicKey: crypto.didToPublicKey, publicKeyToDid: crypto.publicKeyToDid, verifySignature: crypto.verify, @@ -12,7 +12,7 @@ export const rsaPlugin: DidKeyPlugin = { export const rsaOldPlugin: DidKeyPlugin = { prefix: RSA_DID_PREFIX_OLD, - jwtAlg: 'RS256', + jwtAlg: "RS256", didToPublicKey: crypto.oldDidToPublicKey, publicKeyToDid: crypto.publicKeyToOldDid, verifySignature: crypto.verify, diff --git a/packages/plugins/src/util.ts b/packages/plugins/src/util.ts index b10f420..03b5f34 100644 --- a/packages/plugins/src/util.ts +++ b/packages/plugins/src/util.ts @@ -1,5 +1,5 @@ -import * as uint8arrays from 'uint8arrays' -import { BASE58_DID_PREFIX } from './prefixes' +import * as uint8arrays from "uint8arrays" +import { BASE58_DID_PREFIX } from "./prefixes.js" export function keyBytesFromDid(did: string, expectedPrefix: Uint8Array): Uint8Array { if (!did.startsWith(BASE58_DID_PREFIX)) { diff --git a/packages/ucans/jest.config.js b/packages/ucans/jest.config.js deleted file mode 100644 index 1a5935b..0000000 --- a/packages/ucans/jest.config.js +++ /dev/null @@ -1,21 +0,0 @@ -module.exports = { // eslint-disable-line - transform: { - ".(ts|tsx)": "ts-jest" - }, - testEnvironment: "node", - testRegex: "(/__tests__/.*|\\.(test|spec))\\.(ts|tsx|js)$", - moduleFileExtensions: [ - "ts", - "tsx", - "js" - ], - preset: "ts-jest/presets/default-esm", - globals: { - "ts-jest": { - useESM: true, - }, - }, - moduleNameMapper: { - "^(\\.{1,2}/.*)\\.js$": "$1", - }, -} diff --git a/packages/ucans/package.json b/packages/ucans/package.json index 9af35eb..298d19e 100644 --- a/packages/ucans/package.json +++ b/packages/ucans/package.json @@ -1,11 +1,11 @@ { "name": "@ucans/ucans", "version": "0.9.1", - "description": "Typescript implementation of UCANs", + "description": "Typescript implementation of UCANs with default plugins", "author": "Daniel Holmgren ", "repository": { "type": "git", - "url": "https://github.com/fission-suite/ucan" + "url": "https://github.com/ucan-wg/ts-ucan" }, "homepage": "https://guide.fission.codes", "license": "Apache-2.0", @@ -23,7 +23,7 @@ "dist:prep": "copyfiles --error tsconfig.json ./dist/", "dist:src": "copyfiles --error --up 1 \"./src/**/*\" ./dist/src/", "dist:types": "tsc --project ./dist/ --emitDeclarationOnly --declaration --declarationDir ./dist/types/", - "lint": "eslint src/**/*.ts src/*.ts tests/**/*.ts tests/*.ts", + "lint": "eslint src/**/*.ts src/*.ts", "prepare": "yarn build", "publish-alpha": "yarn publish --tag alpha", "publish-stable": "yarn publish --tag latest", diff --git a/packages/ucans/tests/attenuation.test.ts b/packages/ucans/tests/attenuation.test.ts deleted file mode 100644 index a798d30..0000000 --- a/packages/ucans/tests/attenuation.test.ts +++ /dev/null @@ -1,155 +0,0 @@ -import * as token from "../src/token" -import { emailCapabilities, emailCapability } from "./capability/email" - -import { alice, bob, mallory } from "./fixtures" -import { all } from "../src/util" - - -describe("attenuation.emailCapabilities", () => { - - it("works with a simple example", async () => { - // alice -> bob, bob -> mallory - // alice delegates access to sending email as her to bob - // and bob delegates it further to mallory - const leafUcan = await token.build({ - issuer: alice, - audience: bob.did(), - capabilities: [ emailCapability("alice@email.com") ] - }) - - const ucan = await token.build({ - issuer: bob, - audience: mallory.did(), - capabilities: [ emailCapability("alice@email.com") ], - proofs: [ token.encode(leafUcan) ] - }) - - expect(await all(emailCapabilities(ucan))).toEqual([ - { - rootIssuer: bob.did(), - capability: emailCapability("alice@email.com") - }, - { - rootIssuer: alice.did(), - capability: emailCapability("alice@email.com") - } - ]) - }) - - it("reports the first issuer in the chain as originator", async () => { - // alice -> bob, bob -> mallory - // alice delegates nothing to bob - // and bob delegates his email to mallory - const leafUcan = await token.build({ - issuer: alice, - audience: bob.did(), - }) - - const ucan = await token.build({ - issuer: bob, - audience: mallory.did(), - capabilities: [ emailCapability("bob@email.com") ], - proofs: [ token.encode(leafUcan) ] - }) - - // we implicitly expect the originator to become bob - expect(await all(emailCapabilities(ucan))).toEqual([ { - rootIssuer: bob.did(), - capability: emailCapability("bob@email.com"), - } ]) - }) - - it("finds the right proof chain for the originator", async () => { - // alice -> mallory, bob -> mallory, mallory -> alice - // both alice and bob delegate their email access to mallory - // mallory then creates a UCAN with capability to send both - const leafUcanAlice = await token.build({ - issuer: alice, - audience: mallory.did(), - capabilities: [ emailCapability("alice@email.com") ] - }) - - const leafUcanBob = await token.build({ - issuer: bob, - audience: mallory.did(), - capabilities: [ emailCapability("bob@email.com") ] - }) - - const ucan = await token.build({ - issuer: mallory, - audience: alice.did(), - capabilities: [ - emailCapability("alice@email.com"), - emailCapability("bob@email.com") - ], - proofs: [ token.encode(leafUcanAlice), token.encode(leafUcanBob) ] - }) - - const chains = await all(emailCapabilities(ucan)) - - expect(chains).toEqual([ - // We expect two capabilities from parenthood: - { - rootIssuer: mallory.did(), - capability: emailCapability("alice@email.com") - }, - { - rootIssuer: mallory.did(), - capability: emailCapability("bob@email.com") - }, - // Then there's also the delegations - { - rootIssuer: alice.did(), - capability: emailCapability("alice@email.com") - }, - { - rootIssuer: bob.did(), - capability: emailCapability("bob@email.com") - } - ]) - }) - - it("reports all chain options", async () => { - // alice -> mallory, bob -> mallory, mallory -> alice - // both alice and bob claim to have access to alice@email.com - // and both grant that capability to mallory - // a verifier needs to know both to verify valid email access - - const aliceEmail = emailCapability("alice@email.com") - - const leafUcanAlice = await token.build({ - issuer: alice, - audience: mallory.did(), - capabilities: [ aliceEmail ] - }) - - const leafUcanBob = await token.build({ - issuer: bob, - audience: mallory.did(), - capabilities: [ aliceEmail ] - }) - - const ucan = await token.build({ - issuer: mallory, - audience: alice.did(), - capabilities: [ aliceEmail ], - proofs: [ token.encode(leafUcanAlice), token.encode(leafUcanBob) ] - }) - - expect(await all(emailCapabilities(ucan))).toEqual([ - { - rootIssuer: mallory.did(), - capability: aliceEmail - }, - { - rootIssuer: alice.did(), - capability: aliceEmail - }, - { - rootIssuer: bob.did(), - capability: aliceEmail - } - ]) - }) - -}) diff --git a/packages/ucans/tests/builder.test.ts b/packages/ucans/tests/builder.test.ts deleted file mode 100644 index 144ec0f..0000000 --- a/packages/ucans/tests/builder.test.ts +++ /dev/null @@ -1,135 +0,0 @@ -import * as token from "../src/token" -import { Builder } from "../src/builder" -import { emailCapability } from "./capability/email" -import { wnfsCapability, wnfsPublicSemantics } from "./capability/wnfs" -import { EMAIL_SEMANTICS } from "./capability/email" -import { alice, bob, mallory } from "./fixtures" -import { delegationChains } from "../src/attenuation" -import { first } from "../src/util" - - -describe("Builder", () => { - - it("builds with a simple example", async () => { - const fact1 = { test: true } - const fact2 = { preimage: "abc", hash: "sth" } - const cap1 = emailCapability("alice@email.com") - const cap2 = wnfsCapability("alice.fission.name/public/", "SUPER_USER") - const expiration = Math.floor(Date.now() / 1000) + 30 - const notBefore = Math.floor(Date.now() / 1000) - 30 - - const ucan = await Builder.create() - .issuedBy(alice) - .toAudience(bob.did()) - .withExpiration(expiration) - .withNotBefore(notBefore) - .withFact(fact1, fact2) - .withNonce() - .claimCapability(cap1, cap2) - .build() - - expect(ucan.payload.iss).toEqual(alice.did()) - expect(ucan.payload.aud).toEqual(bob.did()) - expect(ucan.payload.exp).toEqual(expiration) - expect(ucan.payload.nbf).toEqual(notBefore) - expect(ucan.payload.fct).toEqual([ fact1, fact2 ]) - expect(ucan.payload.att).toEqual([ cap1, cap2 ]) - expect(ucan.payload.nnc).toBeDefined() - }) - - it("builds with lifetimeInSeconds", async () => { - const payload = Builder.create() - .issuedBy(alice) - .toAudience(bob.did()) - .withLifetimeInSeconds(300) - .buildPayload() - - expect(payload.exp).toBeGreaterThan(Date.now() / 1000 + 290) - }) - - it("prevents duplicate proofs", async () => { - const ucan = await Builder.create() - .issuedBy(alice) - .toAudience(bob.did()) - .withLifetimeInSeconds(30) - .claimCapability(wnfsCapability("alice.fission.name/public/", "SUPER_USER")) - .build() - - const publicCapability = await first(delegationChains(wnfsPublicSemantics, ucan)) - - if (publicCapability == null) { - throw "no capabilities" - } - - if (publicCapability instanceof Error) { - throw publicCapability - } - - const payload = Builder.create() - .issuedBy(bob) - .toAudience(mallory.did()) - .withLifetimeInSeconds(30) - .delegateCapability(wnfsCapability("alice.fission.name/public/Apps", "CREATE"), publicCapability, wnfsPublicSemantics) - .delegateCapability(wnfsCapability("alice.fission.name/public/Documents", "OVERWRITE"), publicCapability, wnfsPublicSemantics) - .buildPayload() - - expect(payload.prf).toEqual([ token.encode(ucan) ]) - }) - - it("throws when it's not ready to be built", () => { - expect(() => { - Builder.create() - .buildPayload() - }).toThrow() - // issuer missing - expect(() => { - Builder.create() - .toAudience(bob.did()) - .withLifetimeInSeconds(1) - .buildPayload() - }).toThrow() - // audience missing - expect(() => { - Builder.create() - .issuedBy(alice) - .withLifetimeInSeconds(1) - .buildPayload() - }).toThrow() - // expiration missing - expect(() => { - Builder.create() - .issuedBy(alice) - .toAudience(bob.did()) - .buildPayload() - }).toThrow() - }) - - it("throws when trying to delegate unproven capabilities", async () => { - const ucan = await Builder.create() - .issuedBy(alice) - .toAudience(bob.did()) - .withLifetimeInSeconds(30) - .claimCapability(emailCapability("alice@email.com")) - .build() - - const delegationChain = await first(delegationChains(EMAIL_SEMANTICS, ucan)) - - if (delegationChain == null) { - throw "no capabilities" - } - - if (delegationChain instanceof Error) { - throw delegationChain - } - - expect(() => { - Builder.create() - .issuedBy(bob) - .toAudience(mallory.did()) - .withLifetimeInSeconds(30) - .delegateCapability(emailCapability("bob@email.com"), delegationChain, EMAIL_SEMANTICS) - .buildPayload() - }).toThrow() - }) - -}) diff --git a/packages/ucans/tests/capability.test.ts b/packages/ucans/tests/capability.test.ts deleted file mode 100644 index 671469c..0000000 --- a/packages/ucans/tests/capability.test.ts +++ /dev/null @@ -1,22 +0,0 @@ -import * as capability from "../src/capability" - - -describe("capability.isEqual", () => { - - it("is able to compare two equal capabilities", () => { - const a = { - with: { scheme: "scheme", hierPart: "hierPart" }, - can: { namespace: "namespace", segments: [ "a", "B" ] } - } - - const b = { - with: { scheme: "SCHEME", hierPart: "hierPart" }, - can: { namespace: "NAMESPACE", segments: [ "A", "b" ] } - } - - expect(capability.isEqual(a, b)).toBe(true) - expect(capability.resourcePointer.isEqual(a.with, b.with)).toBe(true) - expect(capability.ability.isEqual(a.can, b.can)).toBe(true) - }) - -}) \ No newline at end of file diff --git a/packages/ucans/tests/capability/email.ts b/packages/ucans/tests/capability/email.ts deleted file mode 100644 index 63ad868..0000000 --- a/packages/ucans/tests/capability/email.ts +++ /dev/null @@ -1,82 +0,0 @@ -import { Ucan } from "../../src/types" -import { DelegationSemantics, delegationChains, rootIssuer } from "../../src/attenuation" -import { Ability } from "../../src/capability/ability" -import { Capability } from "../../src/capability" -import { SUPERUSER } from "../../src/capability/super-user" -import { ResourcePointer } from "../../src/capability/resource-pointer" - - -// 🌸 - - -export interface EmailCapability { - with: ResourcePointer - can: Ability -} - - - -// 🏔 - - -export const SEND_ABILITY: Ability = { namespace: "msg", segments: [ "SEND" ] } - - -export const EMAIL_SEMANTICS: DelegationSemantics = { - - canDelegateResource(parentResource, resource) { - if (parentResource.scheme !== "email") { - return false - } - if (resource.scheme !== "email") { - return false - } - return parentResource.hierPart === resource.hierPart - }, - - canDelegateAbility(parentAbility, ability) { - if (parentAbility === SUPERUSER) { - return true - } - if (ability === SUPERUSER) { - return false - } - return parentAbility.namespace === "msg" - && parentAbility.segments.length === 1 - && parentAbility.segments[0] === "SEND" - && ability.namespace === "msg" - && ability.segments.length === 1 - && ability.segments[0] === "SEND" - } - -} - - - -// 🛠 - - -export function emailResourcePointer(emailAddress: string): ResourcePointer { - return { scheme: "email", hierPart: emailAddress } -} - - -export function emailCapability(emailAddress: string): Capability { - return { - with: emailResourcePointer(emailAddress), - can: SEND_ABILITY - } -} - - -export async function * emailCapabilities(ucan: Ucan): AsyncIterable<{ capability: EmailCapability; rootIssuer: string }> { - for await (const delegationChain of delegationChains(EMAIL_SEMANTICS, ucan)) { - if (delegationChain instanceof Error || "ownershipDID" in delegationChain) { - continue - } - yield { - rootIssuer: rootIssuer(delegationChain), - capability: delegationChain.capability - } - } -} diff --git a/packages/ucans/tests/capability/wnfs.test.ts b/packages/ucans/tests/capability/wnfs.test.ts deleted file mode 100644 index 58f9bdb..0000000 --- a/packages/ucans/tests/capability/wnfs.test.ts +++ /dev/null @@ -1,238 +0,0 @@ -import * as token from "../../src/token" -import { Capability } from "../../src/capability" -import { wnfsCapability, wnfsPrivateCapabilities, wnfsPublicCapabilities } from "./wnfs" - -import { alice, bob, mallory } from "../fixtures" -import { all } from "../../src/util" - - - -describe("wnfs public capability", () => { - - it("works with a simple example", async () => { - const { ucan } = await makeSimpleDelegation( - [ wnfsCapability("//boris.fission.name/public/Apps/", "OVERWRITE") ], - [ wnfsCapability("//boris.fission.name/public/Apps/appinator/", "REVISE") ] - ) - - expect(await all(wnfsPublicCapabilities(ucan))).toEqual([ - { - rootIssuer: bob.did(), - capability: wnfsCapability("//boris.fission.name/public/Apps/appinator/", "REVISE") - }, - { - rootIssuer: alice.did(), - capability: wnfsCapability("//boris.fission.name/public/Apps/appinator/", "REVISE") - } - ]) - }) - - it("detects capability escalations", async () => { - const { ucan } = await makeSimpleDelegation( - [ wnfsCapability("//boris.fission.name/public/Apps/", "CREATE") ], - [ wnfsCapability("//boris.fission.name/public/Apps/appinator/", "OVERWRITE") ] - ) - - expect(await all(wnfsPublicCapabilities(ucan))).toEqual([ - { - rootIssuer: bob.did(), - capability: wnfsCapability("//boris.fission.name/public/Apps/appinator/", "OVERWRITE") - } - ]) - }) - - it("detects capability escalations, even if there's valid capabilities", async () => { - const { ucan } = await makeSimpleDelegation( - [ - wnfsCapability("//boris.fission.name/public/Apps/", "CREATE"), - wnfsCapability("//boris.fission.name/public/Apps/", "SUPER_USER") - ], - [ wnfsCapability("//boris.fission.name/public/Apps/appinator/", "OVERWRITE") - ] - ) - - expect(await all(wnfsPublicCapabilities(ucan))).toEqual([ - { - rootIssuer: bob.did(), - capability: wnfsCapability("//boris.fission.name/public/Apps/appinator/", "OVERWRITE") - }, - { - rootIssuer: alice.did(), - capability: wnfsCapability("//boris.fission.name/public/Apps/appinator/", "OVERWRITE") - } - ]) - }) - -}) - -describe("wnfs private capability", () => { - - it("works with a simple example", async () => { - const { ucan } = await makeSimpleDelegation( - [ wnfsCapability("//boris.fission.name/private/abc", "OVERWRITE") ], - [ wnfsCapability("//boris.fission.name/private/def", "REVISE") ] - ) - - expect(await all(wnfsPrivateCapabilities(ucan))).toEqual([ - { - rootIssuer: bob.did(), - capability: wnfsCapability("//boris.fission.name/private/def", "REVISE"), - requiredINumbers: new Set(["def"]) - }, - { - rootIssuer: alice.did(), - capability: wnfsCapability("//boris.fission.name/private/def", "REVISE"), - requiredINumbers: new Set(["abc", "def"]) - } - ]) - }) - - it("detects capability escalations", async () => { - const { ucan } = await makeSimpleDelegation( - [ wnfsCapability("//boris.fission.name/private/abc", "OVERWRITE") ], - [ wnfsCapability("//boris.fission.name/private/def", "SUPER_USER") ] - ) - - expect(await all(wnfsPrivateCapabilities(ucan))).toEqual([ - { - rootIssuer: bob.did(), - capability: wnfsCapability("//boris.fission.name/private/def", "SUPER_USER"), - requiredINumbers: new Set(["def"]) - }, - ]) - }) - - it("detects capability escalations, but still returns valid delegations", async () => { - const { ucan } = await makeSimpleDelegation( - [ wnfsCapability("//boris.fission.name/private/abc", "OVERWRITE") ], - [ - wnfsCapability("//boris.fission.name/private/def", "SUPER_USER"), - wnfsCapability("//boris.fission.name/private/ghi", "CREATE") - ] - ) - - expect(await all(wnfsPrivateCapabilities(ucan))).toEqual([ - { - rootIssuer: bob.did(), - capability: wnfsCapability("//boris.fission.name/private/def", "SUPER_USER"), - requiredINumbers: new Set(["def"]) - }, - { - rootIssuer: bob.did(), - capability: wnfsCapability("//boris.fission.name/private/ghi", "CREATE"), - requiredINumbers: new Set(["ghi"]) - }, - { - rootIssuer: alice.did(), - capability: wnfsCapability("//boris.fission.name/private/ghi", "CREATE"), - requiredINumbers: new Set(["ghi", "abc"]) - } - ]) - }) - - it("lists all possible inumber combinations", async () => { - const { ucan } = await makeComplexDelegation( - { - alice: [ wnfsCapability("//boris.fission.name/private/inumalice", "OVERWRITE") ], - bob: [ wnfsCapability("//boris.fission.name/private/inumbob", "OVERWRITE") ] - }, - [ wnfsCapability("//boris.fission.name/private/subinum", "OVERWRITE") ] - ) - - expect(await all(wnfsPrivateCapabilities(ucan))).toEqual([ - { - rootIssuer: mallory.did(), - capability: wnfsCapability("//boris.fission.name/private/subinum", "OVERWRITE"), - requiredINumbers: new Set(["subinum"]) - }, - { - rootIssuer: alice.did(), - capability: wnfsCapability("//boris.fission.name/private/subinum", "OVERWRITE"), - requiredINumbers: new Set(["subinum", "inumalice"]) - }, - { - rootIssuer: bob.did(), - capability: wnfsCapability("//boris.fission.name/private/subinum", "OVERWRITE"), - requiredINumbers: new Set(["subinum", "inumbob"]) - } - ]) - }) - - it("lists all possible inumber combinations except escalations", async () => { - const { ucan } = await makeComplexDelegation( - { - alice: [ wnfsCapability("//boris.fission.name/private/inumalice", "CREATE") ], - bob: [ wnfsCapability("//boris.fission.name/private/inumbob", "OVERWRITE") ] - }, - [ wnfsCapability("//boris.fission.name/private/subinum", "OVERWRITE") ] - ) - - expect(await all(wnfsPrivateCapabilities(ucan))).toEqual([ - { - rootIssuer: mallory.did(), - capability: wnfsCapability("//boris.fission.name/private/subinum", "OVERWRITE"), - requiredINumbers: new Set(["subinum"]) - }, - { - rootIssuer: bob.did(), - capability: wnfsCapability("//boris.fission.name/private/subinum", "OVERWRITE"), - requiredINumbers: new Set(["subinum", "inumbob"]) - } - ]) - }) - -}) - -/** - * A linear delegation chain: - * alice -> bob -> mallory - * - * The arguments are the capabilities delegated in the first and second arrow, respectively. - */ -async function makeSimpleDelegation(aliceCapabilities: Capability[], bobCapabilities: Capability[]) { - const leaf = await token.build({ - issuer: alice, - audience: bob.did(), - capabilities: aliceCapabilities - }) - - const ucan = await token.build({ - issuer: bob, - audience: mallory.did(), - capabilities: bobCapabilities, - proofs: [ token.encode(leaf) ] - }) - - return { leaf, ucan } -} - - -/** - * A tree-like delegation ucan: - * alice & bob => mallory -> alice - * - * The first argument are the capabilities delegated in the first two arrows, - * the second argument are the capabilities delegated in the last arrow. - */ -async function makeComplexDelegation(proofs: { alice: Capability[]; bob: Capability[] }, final: Capability[]) { - const leafAlice = await token.build({ - issuer: alice, - audience: mallory.did(), - capabilities: proofs.alice, - }) - - const leafBob = await token.build({ - issuer: bob, - audience: mallory.did(), - capabilities: proofs.bob, - }) - - const ucan = await token.build({ - issuer: mallory, - audience: alice.did(), - capabilities: final, - proofs: [ token.encode(leafAlice), token.encode(leafBob) ], - }) - - return { leafAlice, leafBob, ucan } -} diff --git a/packages/ucans/tests/capability/wnfs.ts b/packages/ucans/tests/capability/wnfs.ts deleted file mode 100644 index 68b189f..0000000 --- a/packages/ucans/tests/capability/wnfs.ts +++ /dev/null @@ -1,213 +0,0 @@ -import { Ability, isAbility } from "../../src/capability/ability" -import { Capability } from "../../src/capability" -import { DelegationSemantics, DelegatedCapability, DelegatedOwnership, delegationChains, rootIssuer } from "../../src/attenuation" -import { SUPERUSER } from "../../src/capability/super-user" -import { Ucan } from "../../src/types" -import { ResourcePointer } from "../../src/capability/resource-pointer" - - -export const WNFS_ABILITY_LEVELS = { - "SUPER_USER": 0, - "OVERWRITE": -1, - "SOFT_DELETE": -2, - "REVISE": -3, - "CREATE": -4, -} - -export const WNFS_ABILITIES: string[] = Object.keys(WNFS_ABILITY_LEVELS) - -export type WnfsAbility = keyof typeof WNFS_ABILITY_LEVELS - -export function isWnfsCap(cap: Capability): boolean { - return cap.with.scheme === "wnfs" && isWnfsAbility(cap.can) -} - -export function isWnfsAbility(ability: unknown): ability is WnfsAbility { - if (!isAbility(ability)) return false - if (ability === SUPERUSER) return true - const abilitySegment = ability.segments[ 0 ] - const isWnfsAbilitySegment = !!abilitySegment && WNFS_ABILITIES.includes(abilitySegment) - return isWnfsAbilitySegment && ability.namespace.toLowerCase() === "wnfs" -} - -export function wnfsAbilityFromAbility(ability: Ability): WnfsAbility | null { - if (ability === SUPERUSER) return "SUPER_USER" - if (isWnfsAbility(ability)) return ability.segments[ 0 ] as WnfsAbility - return null -} - -export function wnfsCapability(path: string, ability: WnfsAbility): Capability { - return { - with: { scheme: "wnfs", hierPart: path }, - can: { namespace: "wnfs", segments: [ ability ] } - } -} - - - -////////////////////////////// -// Public WNFS Capabilities // -////////////////////////////// - - -interface WnfsPublicResourcePointer { - user: string // e.g. matheus23.fission.name - publicPath: string[] -} - -function tryParseWnfsPublicResource(pointer: ResourcePointer): WnfsPublicResourcePointer | null { - if (pointer.scheme !== "wnfs") return null - - // remove trailing slash - const path = pointer.hierPart.replace(/^\/\//, "") - const trimmed = path.endsWith("/") ? path.slice(0, -1) : path - const split = trimmed.split("/") - const user = split[ 0 ] - const publicPath = split.slice(2) // drop first two: matheus23.fission.name/public/keep/this - if (user == null || split[ 1 ] !== "public") return null - - return { - user, - publicPath, - } -} - -export const wnfsPublicSemantics: DelegationSemantics = { - - canDelegateResource(parentResource, childResource) { - const parent = tryParseWnfsPublicResource(parentResource) - const child = tryParseWnfsPublicResource(childResource) - - if (parent == null || child == null) { - return false - } - - if (parent.user !== child.user) { - return false - } - - // parentCap path must be a prefix of childCap path - if (child.publicPath.length < parent.publicPath.length) { - return false - } - - for (let i = 0; i < parent.publicPath.length; i++) { - if (child.publicPath[ i ] !== parent.publicPath[ i ]) { - return false - } - } - - return true - }, - - canDelegateAbility(parentAbility, childAbility) { - const parent = wnfsAbilityFromAbility(parentAbility) - const child = wnfsAbilityFromAbility(childAbility) - - if (parent == null || child == null) { - return false - } - - if (WNFS_ABILITY_LEVELS[ child ] > WNFS_ABILITY_LEVELS[ parent ]) { - return false - } - - return true - } - -} - -export async function * wnfsPublicCapabilities(ucan: Ucan) { - for await (const delegationChain of delegationChains(wnfsPublicSemantics, ucan)) { - if (delegationChain instanceof Error || "ownershipDID" in delegationChain) { - continue - } - yield { - capability: delegationChain.capability, - rootIssuer: rootIssuer(delegationChain), - } - } -} - - - -/////////////////////////////// -// Private WNFS Capabilities // -/////////////////////////////// - - -interface WnfsPrivateResourcePointer { - user: string - requiredINumbers: Set -} - -function tryParseWnfsPrivateResource(pointer: ResourcePointer): WnfsPrivateResourcePointer | null { - if (pointer.scheme !== "wnfs") return null - - // split up "boris.fission.name/private/fccXmZ8HYmpwxkvPSjwW9A" into "/private/" - const split = pointer.hierPart.replace(/^\/\//, "").split("/") - const user = split[ 0 ] - const inumberBase64url = split[ 2 ] - - if (user == null || split[ 1 ] !== "private" || inumberBase64url == null) return null - - return { - user, - requiredINumbers: new Set([ inumberBase64url ]), - } -} - -const wnfsPrivateSemantics: DelegationSemantics = { - - canDelegateResource(parentResource, childResource) { - const parent = tryParseWnfsPrivateResource(parentResource) - const child = tryParseWnfsPrivateResource(childResource) - - if (parent == null || child == null) { - return false - } - - // There's more tests that need to be run on the resulting delegation chain. - return true - }, - - canDelegateAbility(parentAbility, childAbility) { - const parent = wnfsAbilityFromAbility(parentAbility) - const child = wnfsAbilityFromAbility(childAbility) - - if (parent == null || child == null) { - return false - } - - if (WNFS_ABILITY_LEVELS[ child ] > WNFS_ABILITY_LEVELS[ parent ]) { - return false - } - - return true - } - -} - -export async function * wnfsPrivateCapabilities(ucan: Ucan) { - for await (const delegationChain of delegationChains(wnfsPrivateSemantics, ucan)) { - if (delegationChain instanceof Error || "ownershipDID" in delegationChain) { - continue - } - - const requiredINumbers = new Set() - let chainStep: DelegatedCapability | DelegatedOwnership | undefined = delegationChain - - while (chainStep != null && "capability" in chainStep) { - const hierSplit = chainStep.capability.with.hierPart.split("/") - const inumber = hierSplit[hierSplit.length - 1] - requiredINumbers.add(inumber) - chainStep = chainStep.chainStep - } - - yield { - capability: delegationChain.capability, - requiredINumbers, - rootIssuer: rootIssuer(delegationChain), - } - } -} diff --git a/packages/ucans/tests/compatibility.test.ts b/packages/ucans/tests/compatibility.test.ts deleted file mode 100644 index fca9af9..0000000 --- a/packages/ucans/tests/compatibility.test.ts +++ /dev/null @@ -1,39 +0,0 @@ -import * as uint8arrays from "uint8arrays" -import * as token from "../src/token" - - -const oldUcan = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsInVhdiI6IjEuMC4wIn0.eyJhdWQiOiJkaWQ6a2V5OnoxM1YzU29nMllhVUtoZEdDbWd4OVVadVcxbzFTaEZKWWM2RHZHWWU3TlR0Njg5Tm9MMXRrZUd3NGMydGFQa2dBdWloUjh0cmg2azg2VHRVaTNIR2ZrNEh1NDg3czNiTWY4V1MzWjJoU3VwRktiNmhnV3VwajFIRzhheUxRdDFmeWJSdThjTGdBMkNKanFRYm16YzRFOEFKU0tKeDNndVFYa2F4c3R2Um5RRGN1eDFkZzhVR1BRS3haN2lLeUFKWkFuQlcyWXJUM2o0TVQxdTJNcWZQWG9RYU01WFZQMk04clBFN0FCSEREOXdMbWlKdjkzUUFDRFR5MllnZkVSS3JualNWaTdFb3RNOFR3NHg3M1pNUXJEQnZRRW01Zm9tTWZVaTZVSmJUTmVaaldDTUJQYllNbXRKUDZQZlRpaWZYZG0zdXprVFg5NnExUkVFOExodkU2Rzg2cUR0Wjg5MzdFYUdXdXFpNkRHVDFvc2FRMUVnR3NFN3Jac2JSdDFLNnRXeTZpYktlNTlKZWtnTWFlNW9XNER2IiwiZXhwIjozMjc0NDE5MTQyMywiZmN0IjpbXSwiaXNzIjoiZGlkOmtleTp6MTNWM1NvZzJZYVVLaGRHQ21neDlVWnVXMW8xU2hGSlljNkR2R1llN05UdDY4OU5vTDJWanZBR2JXdTFrdmZWUWFyVTVWMXBTUnNjOWFwR2h2dDdaODJmUWg1QWE1NW41Zm0zZGs2SnFuTXczZGU4WG91dWZUV2Z1eHpEVkhrSFNGV0sxOW1SWWI4d205d1VwZkxtUWl4QVdtMndFWVZqU2dENEd6YzhVUDlDSjFxMkY4ZXlpVXViMThGbld4Y2djUWhqdXB3OTNxUlMzWDlXUDViemlSYjE4TTZ0Vm8zaUJ4ZUozb2lrRTNaa3RScEtTZDlkcHU5WWNXZFhoeDZDQmY5NTZ1UXhkTDZoTkppNmVMbmZ1eFY2NEhpZU1rZFVoTTJSeThRd3lqZjQ4ZnZWMVhFVU1zeEM5YWFjNEtCcGJONDJHR3U4UmFkRDU3cjZuMWFOc2IyTjU3RkNOYnFIMXVLdHhNTmVHZHJ2QWlUUGRzVjJBRmppczJvN243ajhMNW41YmJ4TFl4VThNVHB3QVphdFpkSiIsIm5iZiI6MTY0MDE5MTQ1NywicHJmIjoiZXlKaGJHY2lPaUpTVXpJMU5pSXNJblI1Y0NJNklrcFhWQ0lzSW5WaGRpSTZJakV1TUM0d0luMC5leUpoZFdRaU9pSmthV1E2YTJWNU9ub3hNMVl6VTI5bk1sbGhWVXRvWkVkRGJXZDRPVlZhZFZjeGJ6RlRhRVpLV1dNMlJIWkhXV1UzVGxSME5qZzVUbTlNTWxacWRrRkhZbGQxTVd0MlpsWlJZWEpWTlZZeGNGTlNjMk01WVhCSGFIWjBOMW80TW1aUmFEVkJZVFUxYmpWbWJUTmthelpLY1c1TmR6TmtaVGhZYjNWMVpsUlhablY0ZWtSV1NHdElVMFpYU3pFNWJWSlpZamgzYlRsM1ZYQm1URzFSYVhoQlYyMHlkMFZaVm1wVFowUTBSM3BqT0ZWUU9VTktNWEV5UmpobGVXbFZkV0l4T0VadVYzaGpaMk5SYUdwMWNIYzVNM0ZTVXpOWU9WZFFOV0o2YVZKaU1UaE5OblJXYnpOcFFuaGxTak52YVd0Rk0xcHJkRkp3UzFOa09XUndkVGxaWTFka1dHaDROa05DWmprMU5uVlJlR1JNTm1oT1NtazJaVXh1Wm5WNFZqWTBTR2xsVFd0a1ZXaE5NbEo1T0ZGM2VXcG1ORGhtZGxZeFdFVlZUWE40UXpsaFlXTTBTMEp3WWs0ME1rZEhkVGhTWVdSRU5UZHlObTR4WVU1ellqSk9OVGRHUTA1aWNVZ3hkVXQwZUUxT1pVZGtjblpCYVZSUVpITldNa0ZHYW1sek1tODNiamRxT0V3MWJqVmlZbmhNV1hoVk9FMVVjSGRCV21GMFdtUktJaXdpWlhod0lqb3pNamMwTkRFNU1UUXlNeXdpWm1OMElqcGJYU3dpYVhOeklqb2laR2xrT210bGVUcDZNVE5XTTFOdlp6SlpZVlZMYUdSSFEyMW5lRGxWV25WWE1XOHhVMmhHU2xsak5rUjJSMWxsTjA1VWREWTRPVTV2VERKaE5VcE9hMlI0VmpabWJYVm9WbU5SWkRkSVIycHhkRXBRYVc1WlZWQTRRMUp4Y21veVkyVm5hVTFyT1RKUlNIazJRbWRXT1hveVVGQnJWMkZZU0RkUlRsQmlRekphZEUxNWFXbGFjWGRLUkVOd05sZG9VbkZVUzJodVFtaENUbWQ1WkRkTFJuUTNjRkkyTkhCa1ZIQjZUbXRNUlZKNGFHNTNUVUZqZURKcVJGZFlOelpDVG5SS04xUTFWVXQ0TTIxcWRHWTBaak0wWjJwVGRUaHJkME5UY0V0alFuQTRWV2RwU0hkdllVSkhkREUxVkZjNVUzQlNXVkoxYUZKdk1tdEljVFZ5Y0ROTmRFSnFSa2QyVUdZeVRsTlpZbUUzTmxoSGJYcFhlVEZyZUZOelEySTVUSGhqTW5welEwdG1lSEF5ZUd0VVFqWmtPVVJDUlVwVE5sUnhXbFo1WkhKU05GWmFNVkE1ZFhJeGRGcHBlbk5qYWtWd1kzVlViV1EzV0VRemRYSjZVelpqY0RSdU1sZHdSbFZNYjNsMk5tOW5ibWxaZEVOSGFUVlVlbWxEY2pKT1FWRjNWMEZYY25CMldVMWllbVEyVmt0a2RUVmpaekZZUWxoTVZFNWhUQ0lzSW01aVppSTZNVFkwTURFNU1UTTJNeXdpY0hSaklqb2lVMVZRUlZKZlZWTkZVaUlzSW5Kell5STZJaW9pZlEuQ0k5SjlOLVhUZUxQNEM5WTktUl9TcEE1aE80dHdpNUQxNFpTR2lwUzdjNS1jTlJWTVItc285Z0JZMlQzSFNaTHFmQ2xyMEtlQVJicFk2TFBwSm1NRGQ1ODdvck1TVVRnMndqN043eUNVeksxSWhOazhQMkQ3RGVlSHNxQ1lsTVotdXpjMHBSbnFJb3dPTWl6MVFkbHZXaTZ0UHNxZkZVYnl4bEx1bXRHdjV1a1hqc1FZcmYzdko3aU5DMkJibWotMGhTV25wNTNBN01TQTllLWFXVGpLUWEwSkpXVVVhWG5XS19CNjRaa3NyTWRXdW5mVFNuSE9lR2o3MFRuSXhieVcxbFhodk5pcnhIUV90ZVlKZ2xIZTRBbldEQXdUa2dnaVotdkp0WUhsYnVwQkt4S1YtNm9OMTlXS3dUT3U3QnpPX2QyUHAtWVVyY1RSSS1KZ0F2NUpnIiwicHRjIjoiU1VQRVJfVVNFUiIsInJzYyI6IioifQ.CRLB4gBBHhnsbfUhLALiCfo6mHnHSlEUczyZsWhh9TNjv9UxdgvsSWQsehGIT4XR0jQeYZo2OhasEVaF-Gtt_qqtUQIrducKngd0qzmpfjVbicsQPKVdJjlcTwm9dqhLSEtL195El0oucLzYdqMEZMf-txEmyhCd_Q8CaNExhAnwN32v1salnO6vrAw33ZJID7ZaFmBleoGUXBHQwnkv9_m_P6Fh-UGIKjaOuNmBkGXGn-4irm-eXrne2OPZCoPjhiaf0xTONu4ROrQQYykG8CppvsSXeiylOFY11Ot0sdAlHGSlyZk1_chJ3ud17K9S-CKWK9NtqiMNcUdQGFnNQQ" -const [ encodedHeader, encodedPayload, encodedSignature ] = oldUcan.split(".") -const signature = encodedSignature -const header = JSON.parse(uint8arrays.toString(uint8arrays.fromString(encodedHeader, "base64url"))) -const payload = JSON.parse(uint8arrays.toString(uint8arrays.fromString(encodedPayload, "base64url"))) - -describe("compatibility", () => { - - it("allows parsing UCANs with 'uav: 1.0.0' into 'ucv: 0.3.0'", async () => { - const ucan = await token.validate(oldUcan, { checkIsExpired: false, checkIsTooEarly: false, checkSignature: false }) - expect(ucan).toEqual({ - header: { - alg: header.alg, // "RS256", - typ: header.typ, // "JWT", - ucv: { major: 0, minor: 3, patch: 0 } // we translate uav: 1.0.0 to ucv: 0.3.0 - }, - payload: { - iss: payload.iss, // "did:key:z13V3Sog2YaUKhdGCmgx9UZuW1o1ShFJYc6DvGYe7NTt689NoL2VjvAGbWu1kvfVQarU5V1pSRsc9apGhvt7Z82fQh5Aa55n5fm3dk6JqnMw3de8XouufTWfuxzDVHkHSFWK19mRYb8wm9wUpfLmQixAWm2wEYVjSgD4Gzc8UP9CJ1q2F8eyiUub18FnWxcgcQhjupw93qRS3X9WP5bziRb18M6tVo3iBxeJ3oikE3ZktRpKSd9dpu9YcWdXhx6CBf956uQxdL6hNJi6eLnfuxV64HieMkdUhM2Ry8Qwyjf48fvV1XEUMsxC9aac4KBpbN42GGu8RadD57r6n1aNsb2N57FCNbqH1uKtxMNeGdrvAiTPdsV2AFjis2o7n7j8L5n5bbxLYxU8MTpwAZatZdJ", - aud: payload.aud, // "did:key:z13V3Sog2YaUKhdGCmgx9UZuW1o1ShFJYc6DvGYe7NTt689NoL1tkeGw4c2taPkgAuihR8trh6k86TtUi3HGfk4Hu487s3bMf8WS3Z2hSupFKb6hgWupj1HG8ayLQt1fybRu8cLgA2CJjqQbmzc4E8AJSKJx3guQXkaxstvRnQDcux1dg8UGPQKxZ7iKyAJZAnBW2YrT3j4MT1u2MqfPXoQaM5XVP2M8rPE7ABHDD9wLmiJv93QACDTy2YgfERKrnjSVi7EotM8Tw4x73ZMQrDBvQEm5fomMfUi6UJbTNeZjWCMBPbYMmtJP6PfTiifXdm3uzkTX96q1REE8LhvE6G86qDtZ8937EaGWuqi6DGT1osaQ1EgGsE7rZsbRt1K6tWy6ibKe59JekgMae5oW4Dv", - nbf: payload.nbf, // 1640191457, - exp: payload.exp, // 32744191423, - att: [ { - can: payload.rsc, // "*", - with: { scheme: "my", hierPart: payload.rsc } - } ], - prf: [ - payload.prf, // "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsInVhdiI6IjEuMC4wIn0.eyJhdWQiOiJkaWQ6a2V5OnoxM1YzU29nMllhVUtoZEdDbWd4OVVadVcxbzFTaEZKWWM2RHZHWWU3TlR0Njg5Tm9MMlZqdkFHYld1MWt2ZlZRYXJVNVYxcFNSc2M5YXBHaHZ0N1o4MmZRaDVBYTU1bjVmbTNkazZKcW5NdzNkZThYb3V1ZlRXZnV4ekRWSGtIU0ZXSzE5bVJZYjh3bTl3VXBmTG1RaXhBV20yd0VZVmpTZ0Q0R3pjOFVQOUNKMXEyRjhleWlVdWIxOEZuV3hjZ2NRaGp1cHc5M3FSUzNYOVdQNWJ6aVJiMThNNnRWbzNpQnhlSjNvaWtFM1prdFJwS1NkOWRwdTlZY1dkWGh4NkNCZjk1NnVReGRMNmhOSmk2ZUxuZnV4VjY0SGllTWtkVWhNMlJ5OFF3eWpmNDhmdlYxWEVVTXN4QzlhYWM0S0JwYk40MkdHdThSYWRENTdyNm4xYU5zYjJONTdGQ05icUgxdUt0eE1OZUdkcnZBaVRQZHNWMkFGamlzMm83bjdqOEw1bjViYnhMWXhVOE1UcHdBWmF0WmRKIiwiZXhwIjozMjc0NDE5MTQyMywiZmN0IjpbXSwiaXNzIjoiZGlkOmtleTp6MTNWM1NvZzJZYVVLaGRHQ21neDlVWnVXMW8xU2hGSlljNkR2R1llN05UdDY4OU5vTDJhNUpOa2R4VjZmbXVoVmNRZDdIR2pxdEpQaW5ZVVA4Q1JxcmoyY2VnaU1rOTJRSHk2QmdWOXoyUFBrV2FYSDdRTlBiQzJadE15aWlacXdKRENwNldoUnFUS2huQmhCTmd5ZDdLRnQ3cFI2NHBkVHB6TmtMRVJ4aG53TUFjeDJqRFdYNzZCTnRKN1Q1VUt4M21qdGY0ZjM0Z2pTdThrd0NTcEtjQnA4VWdpSHdvYUJHdDE1VFc5U3BSWVJ1aFJvMmtIcTVycDNNdEJqRkd2UGYyTlNZYmE3NlhHbXpXeTFreFNzQ2I5THhjMnpzQ0tmeHAyeGtUQjZkOURCRUpTNlRxWlZ5ZHJSNFZaMVA5dXIxdFppenNjakVwY3VUbWQ3WEQzdXJ6UzZjcDRuMldwRlVMb3l2Nm9nbmlZdENHaTVUemlDcjJOQVF3V0FXcnB2WU1iemQ2VktkdTVjZzFYQlhMVE5hTCIsIm5iZiI6MTY0MDE5MTM2MywicHRjIjoiU1VQRVJfVVNFUiIsInJzYyI6IioifQ.CI9J9N-XTeLP4C9Y9-R_SpA5hO4twi5D14ZSGipS7c5-cNRVMR-so9gBY2T3HSZLqfClr0KeARbpY6LPpJmMDd587orMSUTg2wj7N7yCUzK1IhNk8P2D7DeeHsqCYlMZ-uzc0pRnqIowOMiz1QdlvWi6tPsqfFUbyxlLumtGv5ukXjsQYrf3vJ7iNC2Bbmj-0hSWnp53A7MSA9e-aWTjKQa0JJWUUaXnWK_B64ZksrMdWunfTSnHOeGj70TnIxbyW1lXhvNirxHQ_teYJglHe4AnWDAwTkggiZ-vJtYHlbupBKxKV-6oN19WKwTOu7BzO_d2Pp-YUrcTRI-JgAv5Jg", - ], - }, - signedData: `${encodedHeader}.${encodedPayload}`, - signature // "CRLB4gBBHhnsbfUhLALiCfo6mHnHSlEUczyZsWhh9TNjv9UxdgvsSWQsehGIT4XR0jQeYZo2OhasEVaF-Gtt_qqtUQIrducKngd0qzmpfjVbicsQPKVdJjlcTwm9dqhLSEtL195El0oucLzYdqMEZMf-txEmyhCd_Q8CaNExhAnwN32v1salnO6vrAw33ZJID7ZaFmBleoGUXBHQwnkv9_m_P6Fh-UGIKjaOuNmBkGXGn-4irm-eXrne2OPZCoPjhiaf0xTONu4ROrQQYykG8CppvsSXeiylOFY11Ot0sdAlHGSlyZk1_chJ3ud17K9S-CKWK9NtqiMNcUdQGFnNQQ" - }) - }) - -}) diff --git a/packages/ucans/tests/did-key.ts b/packages/ucans/tests/did-key.ts deleted file mode 100644 index 9ad2824..0000000 --- a/packages/ucans/tests/did-key.ts +++ /dev/null @@ -1,36 +0,0 @@ -import EcdsaKeypair from "../src/keypair/ecdsa.js" - -// did:key test vectors from W3C -// https://github.com/w3c-ccg/did-method-key/blob/main/test-vectors/nist-curves.json -const testVectors = [ - { - id: "did:key:zDnaerx9CtbPJ1q36T5Ln5wYt3MQYeGRG5ehnPAmxcf5mDZpv", - jwk: { - kty: "EC", - crv: "P-256", - x: "igrFmi0whuihKnj9R3Om1SoMph72wUGeFaBbzG2vzns", - y: "efsX5b10x8yjyrj4ny3pGfLcY7Xby1KzgqOdqnsrJIM", - d: "gPh-VvVS8MbvKQ9LSVVmfnxnKjHn4Tqj0bmbpehRlpc" - } - }, - { - id: "did:key:zDnaerDaTF5BXEavCrfRZEk316dpbLsfPDZ3WJ5hRTPFU2169", - jwk: { - kty: "EC", - crv: "P-256", - x: "fyNYMN0976ci7xqiSdag3buk-ZCwgXU4kz9XNkBlNUI", - y: "hW2ojTNfH7Jbi8--CJUo3OCbH3y5n91g-IMA9MLMbTU", - d: "YjRs6vNvw4sYrzVVY8ipkEpDAD9PFqw1sUnvPRMA-WI" - } - } -] - -describe("did:key ECDSA P-256", () => { - it("derives the correct DID from the JWK", async () => { - for(const vector of testVectors) { - const keypair = await EcdsaKeypair.importFromJwk(vector.jwk) - const did = keypair.did() - expect(did).toEqual(vector.id) - } - }) -}) \ No newline at end of file diff --git a/packages/ucans/tests/did.test.ts b/packages/ucans/tests/did.test.ts deleted file mode 100644 index 02abca7..0000000 --- a/packages/ucans/tests/did.test.ts +++ /dev/null @@ -1,78 +0,0 @@ -import * as did from "../src/did" - -describe("publicKeyToDid", () => { - - it("handles RSA Keys", async () => { - const pubkey = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAnzyis1ZjfNB0bBgKFMSvvkTtwlvBsaJq7S5wA+kzeVOVpVWwkWdVha4s38XM/pa/yr47av7+z3VTmvDRyAHcaT92whREFpLv9cj5lTeJSibyr/Mrm/YtjCZVWgaOYIhwrXwKLqPr/11inWsAkfIytvHWTxZYEcXLgAXFuUuaS3uF9gEiNQwzGTU1v0FqkqTBr4B8nW3HCN47XUu0t8Y0e+lf4s4OxQawWD79J9/5d3Ry0vbV3Am1FtGJiJvOwRsIfVChDpYStTcHTCMqtvWbV6L11BWkpzGXSW4Hv43qa+GSYOD2QU68Mb59oSk2OB+BtOLpJofmbGEGgvmwyCI9MwIDAQAB" - // old: const expectedDid = "did:key:z13V3Sog2YaUKhdGCmgx9UZuW1o1ShFJYc6DvGYe7NTt689NoL2RtpVs65Zw899YrTN9WuxdEEDm54YxWuQHQvcKfkZwa8HTgokHxGDPEmNLhvh69zUMEP4zjuARQ3T8bMUumkSLGpxNe1bfQX624ef45GhWb3S9HM3gvAJ7Qftm8iqnDQVcxwKHjmkV4hveKMTix4bTRhieVHi1oqU4QCVy4QPWpAAympuCP9dAoJFxSP6TNBLY9vPKLazsg7XcFov6UuLWsEaxJ5SomCpDx181mEgW2qTug5oQbrJwExbD9CMgXHLVDE2QgLoQMmgsrPevX57dH715NXC2uY6vo2mYCzRY4KuDRUsrkuYCkewL8q2oK1BEDVvi3Sg8pbC9QYQ5mMiHf8uxiHxTAmPedv8" - const expectedDid = "did:key:z4MXj1wBzi9jUstyNvmiK5WLRRL4rr9UvzPxhry1CudCLKWLyMbP1WoTwDfttBTpxDKf5hAJEjqNbeYx2EEvrJmSWHAu7TJRPTrE3QodbMfRvRNRDyYvaN1FSQus2ziS1rWXwAi5Gpc16bY3JwjyLCPJLfdRWHZhRXiay5FWEkfoSKy6aftnzAvqNkKBg2AxgzGMinR6d1WiH4w5mEXFtUeZkeo4uwtRTd8rD9BoVaHVkGwJkksDybE23CsBNXiNfbweFVRcwfTMhcQsTsYhUWDcSC6QE3zt9h4Rsrj7XRYdwYSK5bc1qFRsg5HULKBp2uZ1gcayiW2FqHFcMRjBieC4LnSMSD1AZB1WUncVRbPpVkn1UGhCU" - const result = did.publicKeyToDid(pubkey, "rsa") - expect(result).toEqual(expectedDid) - }) - - it("handles Ed25519 Keys", async () => { - const pubkey = "Hv+AVRD2WUjUFOsSNbsmrp9fokuwrUnjBcr92f0kxw4=" - const expectedDid = "did:key:z6MkgYGF3thn8k1Fv4p4dWXKtsXCnLH7q9yw4QgNPULDmDKB" - const result = did.publicKeyToDid(pubkey, "ed25519") - expect(result).toEqual(expectedDid) - }) - - it("handles BLS12-381 Keys", async () => { - const pubkey = "Hv+AVRD2WUjUFOsSNbsmrp9fokuwrUnjBcr92f0kxw4=" - const expectedDid = "did:key:z6HpYD1br5P4QVh5rjRGAkBfKMWes44uhKmKdJ6dN2Nm9gHK" - const result = did.publicKeyToDid(pubkey, "bls12-381") - expect(result).toEqual(expectedDid) - }) - - it("handles NIST P-256 Keys", async () => { - const pubkey = "BEgg6smRlRQXKqzJHu0w5/nHcy7zYNxFXwTKUyyCOoDMztUvQn5VZg668v4MecHGUZTeHkaZhSraK7RtJuXfWp4=" - const expectedDid = "did:key:zDnaeVHXWgKUpJFc8AUfRDuRWfsAQejmZu5HrERh41iGumZVu" - const result = did.publicKeyToDid(pubkey, "p256") - expect(result).toEqual(expectedDid) - }) - -}) - -describe("didToPublicKey", () => { - - it("handles old RSA Keys", async () => { - const toDecode = "did:key:z13V3Sog2YaUKhdGCmgx9UZuW1o1ShFJYc6DvGYe7NTt689NoL2RtpVs65Zw899YrTN9WuxdEEDm54YxWuQHQvcKfkZwa8HTgokHxGDPEmNLhvh69zUMEP4zjuARQ3T8bMUumkSLGpxNe1bfQX624ef45GhWb3S9HM3gvAJ7Qftm8iqnDQVcxwKHjmkV4hveKMTix4bTRhieVHi1oqU4QCVy4QPWpAAympuCP9dAoJFxSP6TNBLY9vPKLazsg7XcFov6UuLWsEaxJ5SomCpDx181mEgW2qTug5oQbrJwExbD9CMgXHLVDE2QgLoQMmgsrPevX57dH715NXC2uY6vo2mYCzRY4KuDRUsrkuYCkewL8q2oK1BEDVvi3Sg8pbC9QYQ5mMiHf8uxiHxTAmPedv8" - const expectedKey = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAnzyis1ZjfNB0bBgKFMSvvkTtwlvBsaJq7S5wA+kzeVOVpVWwkWdVha4s38XM/pa/yr47av7+z3VTmvDRyAHcaT92whREFpLv9cj5lTeJSibyr/Mrm/YtjCZVWgaOYIhwrXwKLqPr/11inWsAkfIytvHWTxZYEcXLgAXFuUuaS3uF9gEiNQwzGTU1v0FqkqTBr4B8nW3HCN47XUu0t8Y0e+lf4s4OxQawWD79J9/5d3Ry0vbV3Am1FtGJiJvOwRsIfVChDpYStTcHTCMqtvWbV6L11BWkpzGXSW4Hv43qa+GSYOD2QU68Mb59oSk2OB+BtOLpJofmbGEGgvmwyCI9MwIDAQAB" - const { publicKey, type } = did.didToPublicKey(toDecode) - expect(publicKey).toEqual(expectedKey) - expect(type).toEqual("rsa") - }) - - it("handles standardized RSA Keys", async () => { - const toDecode = "did:key:z4MXj1wBzi9jUstyNvmiK5WLRRL4rr9UvzPxhry1CudCLKWLyMbP1WoTwDfttBTpxDKf5hAJEjqNbeYx2EEvrJmSWHAu7TJRPTrE3QodbMfRvRNRDyYvaN1FSQus2ziS1rWXwAi5Gpc16bY3JwjyLCPJLfdRWHZhRXiay5FWEkfoSKy6aftnzAvqNkKBg2AxgzGMinR6d1WiH4w5mEXFtUeZkeo4uwtRTd8rD9BoVaHVkGwJkksDybE23CsBNXiNfbweFVRcwfTMhcQsTsYhUWDcSC6QE3zt9h4Rsrj7XRYdwYSK5bc1qFRsg5HULKBp2uZ1gcayiW2FqHFcMRjBieC4LnSMSD1AZB1WUncVRbPpVkn1UGhCU" - const expectedKey = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAnzyis1ZjfNB0bBgKFMSvvkTtwlvBsaJq7S5wA+kzeVOVpVWwkWdVha4s38XM/pa/yr47av7+z3VTmvDRyAHcaT92whREFpLv9cj5lTeJSibyr/Mrm/YtjCZVWgaOYIhwrXwKLqPr/11inWsAkfIytvHWTxZYEcXLgAXFuUuaS3uF9gEiNQwzGTU1v0FqkqTBr4B8nW3HCN47XUu0t8Y0e+lf4s4OxQawWD79J9/5d3Ry0vbV3Am1FtGJiJvOwRsIfVChDpYStTcHTCMqtvWbV6L11BWkpzGXSW4Hv43qa+GSYOD2QU68Mb59oSk2OB+BtOLpJofmbGEGgvmwyCI9MwIDAQAB" - const { publicKey, type } = did.didToPublicKey(toDecode) - expect(publicKey).toEqual(expectedKey) - expect(type).toEqual("rsa") - }) - - it("handles Ed25519 Keys", async () => { - const toDecode = "did:key:z6MkgYGF3thn8k1Fv4p4dWXKtsXCnLH7q9yw4QgNPULDmDKB" - const expectedKey = "Hv+AVRD2WUjUFOsSNbsmrp9fokuwrUnjBcr92f0kxw4=" - const { publicKey, type } = did.didToPublicKey(toDecode) - expect(publicKey).toEqual(expectedKey) - expect(type).toEqual("ed25519") - }) - - it("handles BLS12-381 Keys", async () => { - const toDecode = "did:key:z6HpYD1br5P4QVh5rjRGAkBfKMWes44uhKmKdJ6dN2Nm9gHK" - const expectedKey = "Hv+AVRD2WUjUFOsSNbsmrp9fokuwrUnjBcr92f0kxw4=" - const { publicKey, type } = did.didToPublicKey(toDecode) - expect(publicKey).toEqual(expectedKey) - expect(type).toEqual("bls12-381") - }) - - it("handles NIST P-256 Keys", async () => { - const toDecode = "did:key:zDnaeVHXWgKUpJFc8AUfRDuRWfsAQejmZu5HrERh41iGumZVu" - const expectedKey = "BEgg6smRlRQXKqzJHu0w5/nHcy7zYNxFXwTKUyyCOoDMztUvQn5VZg668v4MecHGUZTeHkaZhSraK7RtJuXfWp4=" - const { publicKey, type } = did.didToPublicKey(toDecode) - expect(publicKey).toEqual(expectedKey) - expect(type).toEqual("p256") - }) - -}) diff --git a/packages/ucans/tests/ecdsa.test.ts b/packages/ucans/tests/ecdsa.test.ts deleted file mode 100644 index 3d74917..0000000 --- a/packages/ucans/tests/ecdsa.test.ts +++ /dev/null @@ -1,34 +0,0 @@ -import * as did from "../src/did" -import ECDSAKeyPair from "../src/keypair/ecdsa" - - -describe("ecdsa", () => { - - let p256Keypair: ECDSAKeyPair - - let p256Signature: Uint8Array - - const data = new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8, 9]) - - it("creates an ecdsa keypairs with different curves", async () => { - p256Keypair = await ECDSAKeyPair.create() - }) - - it("returns a publicKeyStr and did: curve P-256", () => { - const keyDid = p256Keypair.did() - const publicKey = p256Keypair.publicKeyStr() - const transformed = did.didToPublicKey(keyDid) - expect(transformed.publicKey).toEqual(publicKey) - expect(transformed.type).toEqual("p256") - }) - - it("signs data: curve P-256", async () => { - p256Signature = await p256Keypair.sign(data) - }) - - it("can verify signature: P-256", async () => { - const isValid = await did.verifySignature(data, p256Signature, p256Keypair.did()) - expect(isValid).toBeTruthy() - }) - -}) diff --git a/packages/ucans/tests/ed25519.test.ts b/packages/ucans/tests/ed25519.test.ts deleted file mode 100644 index 65769d9..0000000 --- a/packages/ucans/tests/ed25519.test.ts +++ /dev/null @@ -1,31 +0,0 @@ -import * as did from "../src/did" -import EdwardsKey from "../src/keypair/ed25519" - -describe("ed25519", () => { - - let keypair: EdwardsKey - let signature: Uint8Array - const data = new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8, 9]) - - it("creates an edwards curve keypair", async () => { - keypair = await EdwardsKey.create() - }) - - it("returns a publicKeyStr and did", () => { - const publicKey = keypair.publicKeyStr() - const keyDid = keypair.did() - const transformed = did.didToPublicKey(keyDid) - expect(transformed.publicKey).toEqual(publicKey) - expect(transformed.type).toEqual("ed25519") - }) - - it("signs data", async () => { - signature = await keypair.sign(data) - }) - - it("can verify signature", async () => { - const isValid = await did.verifySignature(data, signature, keypair.did()) - expect(isValid).toEqual(true) - }) - -}) diff --git a/packages/ucans/tests/fixtures.ts b/packages/ucans/tests/fixtures.ts deleted file mode 100644 index fbdeeb1..0000000 --- a/packages/ucans/tests/fixtures.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { EdKeypair } from "../src/keypair/ed25519" - - -/** did:key:z6Mkk89bC3JrVqKie71YEcc5M1SMVxuCgNx6zLZ8SYJsxALi */ -export const alice = EdKeypair.fromSecretKey("U+bzp2GaFQHso587iSFWPSeCzbSfn/CbNHEz7ilKRZ1UQMmMS7qq4UhTzKn3X9Nj/4xgrwa+UqhMOeo4Ki8JUw==") - -/** did:key:z6MkffDZCkCTWreg8868fG1FGFogcJj5X6PY93pPcWDn9bob */ -export const bob = EdKeypair.fromSecretKey("G4+QCX1b3a45IzQsQd4gFMMe0UB1UOx9bCsh8uOiKLER69eAvVXvc8P2yc4Iig42Bv7JD2zJxhyFALyTKBHipg==") - -/** did:key:z6MktafZTREjJkvV5mfJxcLpNBoVPwDLhTuMg9ng7dY4zMAL */ -export const mallory = EdKeypair.fromSecretKey("LR9AL2MYkMARuvmV3MJV8sKvbSOdBtpggFCW8K62oZDR6UViSXdSV/dDcD8S9xVjS61vh62JITx7qmLgfQUSZQ==") - - -export function didToName(did: string) { - if (did === alice.did()) return "alice" - if (did === bob.did()) return "bob" - if (did === mallory.did()) return "mallory" - return did -} diff --git a/packages/ucans/tests/rsa.test.ts b/packages/ucans/tests/rsa.test.ts deleted file mode 100644 index 70950ce..0000000 --- a/packages/ucans/tests/rsa.test.ts +++ /dev/null @@ -1,96 +0,0 @@ -import * as fc from "fast-check" -import * as uint8arrays from "uint8arrays" -import * as did from "../src/did" -import * as rsaCrypto from "../src/crypto/rsa" -import RSAKeypair from "../src/keypair/rsa" - - -describe("rsa", () => { - - let keypair: RSAKeypair - let signature: Uint8Array - const data = new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8, 9]) - - it("creates an rsa keypair", async () => { - keypair = await RSAKeypair.create() - }) - - it("returns a publicKeyStr and did", () => { - const publicKey = keypair.publicKeyStr() - const keyDid = keypair.did() - const transformed = did.didToPublicKey(keyDid) - expect(transformed.publicKey).toEqual(publicKey) - expect(transformed.type).toEqual("rsa") - }) - - it("signs data", async () => { - signature = await keypair.sign(data) - }) - - it("can verify signature", async () => { - const isValid = await did.verifySignature(data, signature, keypair.did()) - expect(isValid).toEqual(true) - }) - -}) - -describe("ASN", () => { - - describe("asn1DERLengthEncode/Decode", () => { - - it("works with simple examples", () => { - // 82 - bigger than 127 & 2 length octets - // 01 - 1 * 256^1 + - // b3 - 179 * 256^0 - // = 435 - // Example from https://en.wikipedia.org/wiki/X.690#Length_octets - expect(uint8arrays.toString(rsaCrypto.asn1DERLengthEncode(435), "hex")).toEqual("8201b3") - }) - - it("round-trips", () => { - fc.assert(fc.property(fc.nat(), n => { - expect(rsaCrypto.asn1DERLengthDecode(rsaCrypto.asn1DERLengthEncode(n))).toEqual(n) - })) - }) - - it("encodes in a simple way until 127", () => { - for (let i = 0; i < 128; i++) { - expect(`Encoded ${i}: ${uint8arrays.toString(rsaCrypto.asn1DERLengthEncode(i), "hex")}`) - .toEqual(`Encoded ${i}: ${uint8arrays.toString(new Uint8Array([i]), "hex")}`) - } - }) - }) - - describe("SPKI/PKCS1 conversion", () => { - - it("round trips with webcrypto-generated spki keys", async () => { - await fc.assert( - fc.asyncProperty( - fc.constantFrom(1024, 2048, 3072, 4096), - async size => { - const key = await rsaCrypto.generateKeypair(size) - if (key.publicKey == null) { - expect(key.publicKey).toBeDefined() - throw "public key is undefined" - } - const spki = await rsaCrypto.exportKey(key.publicKey) - const converted = - rsaCrypto.convertRSAPublicKeyToSubjectPublicKeyInfo( - rsaCrypto.convertSubjectPublicKeyInfoToRSAPublicKey( - spki - ) - ) - - // I find hex dumps the most readable when it comes to ASN1 - expect(uint8arrays.toString(converted, "hex")).toEqual(uint8arrays.toString(spki, "hex")) - } - ), - { - numRuns: 5, // unfortunately, generating rsa keys is quite slow. Let's try to reliably keep below the 5s timeout - examples: [[1024], [2048], [3072], [4096]], // ensure we're testing each variant at least once - } - ) - }) - - }) -}) diff --git a/packages/ucans/tests/semver.test.ts b/packages/ucans/tests/semver.test.ts deleted file mode 100644 index 70667a8..0000000 --- a/packages/ucans/tests/semver.test.ts +++ /dev/null @@ -1,89 +0,0 @@ -import * as fc from "fast-check" - -import { SemVer, parse, format, compare, GT, EQ, LT } from "../src/semver" - -const arbitrarySemVer = fc.record({ - major: fc.nat(100), - minor: fc.nat(100), - patch: fc.nat(100) -}) - -const toNum = (semVer: SemVer) => - 100 * 100 * semVer.major - + 100 * semVer.minor - + semVer.patch - -describe("SemVer", () => { - - it("has the property parse(format(x)) == x", () => { - fc.property(arbitrarySemVer, semVer => { - expect(parse(format(semVer))).toEqual(semVer) - }) - }) - - it("has the property compare(x, y) == flip(compare(y, x))", () => { - fc.property(arbitrarySemVer, arbitrarySemVer, (l, r) => { - expect(compare(l, r)).toEqual(flip(compare(r, l))) - }) - }) - - it("has the property compare(x, x) == EQ", () => { - fc.property(arbitrarySemVer, semVer => { - expect(compare(semVer, semVer)).toEqual(EQ) - }) - }) - - it("has the property compare(x, y) == compareNum(toNum(x), toNum(y))", () => { - fc.property(arbitrarySemVer, arbitrarySemVer, (x, y) => { - expect(compare(x, y)).toEqual(compareNum(toNum(x), toNum(y))) - }) - }) - - describe("parse", () => { - - it("parses 0.1.2", () => { - expect(parse("0.1.2")).toEqual({ - major: 0, - minor: 1, - patch: 2, - }) - }) - - it("parses 11.22.33", () => { - expect(parse("11.22.33")).toEqual({ - major: 11, - minor: 22, - patch: 33, - }) - }) - - it("doesn't parse negative integers", () => { - expect(parse("0.-1.1")).toEqual(null) - - }) - - it("doesn't parse octal integers", () => { - expect(parse("0.010.0")).toEqual(null) - }) - - it("doesn't parse scientific integers", () => { - expect(parse("1e5.0.0")).toEqual(null) - }) - - }) - -}) - -function flip(sign: GT | EQ | LT): GT | EQ | LT { - switch (sign) { - case GT: return LT - case LT: return GT - default: return EQ - } -} - -function compareNum(l: number, r: number): LT | EQ | GT { - if (l < r) return LT - if (l > r) return GT - return EQ -} diff --git a/packages/ucans/tests/store.test.ts b/packages/ucans/tests/store.test.ts deleted file mode 100644 index da7864f..0000000 --- a/packages/ucans/tests/store.test.ts +++ /dev/null @@ -1,142 +0,0 @@ -import * as token from "../src/token" -import { Store } from "../src/store" -import { Builder } from "../src/builder" -import { alice, bob, mallory } from "./fixtures" -import { wnfsCapability, wnfsPublicSemantics } from "./capability/wnfs" -import { Ucan } from "../src/types" -import { equalCanDelegate } from "../src/attenuation" -import { all } from "../src/util" - - -describe("Store.add", () => { - - it("makes added items retrievable with findByAudience", async () => { - const ucan = await Builder.create() - .issuedBy(alice) - .toAudience(bob.did()) - .withLifetimeInSeconds(30) - .build() - - const encoded = token.encode(ucan) - - const store = await Store.fromTokens(equalCanDelegate, []) - await store.add(ucan) - expect(encodeOrNull(store.findByAudience(ucan.payload.aud, find => token.encode(find) === encoded))).toEqual(encoded) - }) - - it("makes added items retrievable with findByAudience among multiple others", async () => { - const ucan = await Builder.create() - .issuedBy(alice) - .toAudience(bob.did()) - .withLifetimeInSeconds(30) - .build() - - const ucan2 = await Builder.create() - .issuedBy(alice) - .toAudience(mallory.did()) - .withLifetimeInSeconds(30) - .build() - - const encoded = token.encode(ucan) - const store = await Store.fromTokens(equalCanDelegate, []) - await store.add(ucan2) - await store.add(ucan) - expect(encodeOrNull(store.findByAudience(ucan.payload.aud, find => token.encode(find) === encoded))).toEqual(encoded) - }) - - it("doesn't add items twice", async () => { - const ucan = await Builder.create() - .issuedBy(alice) - .toAudience(bob.did()) - .withLifetimeInSeconds(30) - .build() - - const store = await Store.fromTokens(equalCanDelegate, []) - await store.add(ucan) - await store.add(ucan) - expect(store.getByAudience(ucan.payload.aud)).toEqual([ ucan ]) - }) - -}) - -describe("Store.findByAudience", () => { - - it("only returns ucans with given audience", async () => { - const ucanBob = await Builder.create() - .issuedBy(alice) - .toAudience(bob.did()) - .withLifetimeInSeconds(30) - .build() - - const ucanAlice = await Builder.create() - .issuedBy(bob) - .toAudience(alice.did()) - .withLifetimeInSeconds(30) - .build() - - const store = await Store.fromTokens(equalCanDelegate, [ ucanBob, ucanAlice ].map(ucan => token.encode(ucan))) - expect(store.findByAudience(mallory.did(), () => true)).toEqual(null) - expect(encodeOrNull(store.findByAudience(bob.did(), () => true))).toEqual(token.encode(ucanBob)) - expect(encodeOrNull(store.findByAudience(alice.did(), () => true))).toEqual(token.encode(ucanAlice)) - }) - -}) - -describe("Store.findWithCapability", () => { - - it("finds ucans with more capabilities than the given", async () => { - const ucan = await Builder.create() - .issuedBy(alice) - .toAudience(bob.did()) - .withLifetimeInSeconds(30) - .claimCapability(wnfsCapability("alice.fission.name/public/", "SUPER_USER")) - .build() - - const store = await Store.fromTokens(wnfsPublicSemantics, [ token.encode(ucan) ]) - - const results = all(store.findWithCapability( - bob.did(), - wnfsCapability("alice.fission.name/public/Apps", "OVERWRITE"), - alice.did() - )) - - if (!("capability" in results[0])) { - throw "no capability" - } - - expect(encodeOrNull(results[0]?.ucan)).toEqual(token.encode(ucan)) - }) - - it("reports an error if the capability can't be found with given audience", async () => { - const ucanBob = await Builder.create() - .issuedBy(alice) - .toAudience(bob.did()) - .withLifetimeInSeconds(30) - .claimCapability(wnfsCapability("alice.fission.name/public/", "SUPER_USER")) - .build() - - const ucanAlice = await Builder.create() - .issuedBy(alice) - .toAudience(bob.did()) - .withLifetimeInSeconds(30) - .build() - - const store = await Store.fromTokens(wnfsPublicSemantics, [ token.encode(ucanAlice), token.encode(ucanBob) ]) - - const results = all(store.findWithCapability( - alice.did(), - wnfsCapability("alice.fission.name/public/Apps", "OVERWRITE"), - alice.did() - )) - - expect(results).toEqual([]) - }) - -}) - -function encodeOrNull(ucan: Ucan | null): string { - if (ucan == null) { - return "null" - } - return token.encode(ucan) -} diff --git a/packages/ucans/tests/token.test.ts b/packages/ucans/tests/token.test.ts deleted file mode 100644 index b625a78..0000000 --- a/packages/ucans/tests/token.test.ts +++ /dev/null @@ -1,175 +0,0 @@ -import * as uint8arrays from "uint8arrays" - -import * as capability from "../src/capability" -import * as token from "../src/token" -import { verifySignatureUtf8 } from "../src/did" -import { alice, bob } from "./fixtures" - - -// COMPOSING - - -describe("token.build", () => { - - it("can build payloads without nbf", () => { - const payload = token.buildPayload({ - issuer: alice.did(), - audience: bob.did(), - }) - expect(payload.nbf).not.toBeDefined() - }) - - it("builds payloads that expire in the future", () => { - const payload = token.buildPayload({ - issuer: alice.did(), - audience: bob.did(), - - lifetimeInSeconds: 30, - }) - expect(payload.exp).toBeGreaterThan(Date.now() / 1000) - }) - - it("throws when enclosing tokens with an invalid key type", async () => { - await expect(() => { - const payload = token.buildPayload({ - issuer: alice.did(), - audience: bob.did(), - }) - - return token.sign( - payload, - "rsa", - data => alice.sign(data) - ) - }).rejects.toBeDefined() - }) - -}) - - - -// ENCODING - - -describe("token.encodePayload", () => { - - it("encodes capabilities", () => { - const encodedCaps = { - with: "wnfs://boris.fission.name/public/photos/", - can: "crud/DELETE" - } - - const payload = token.buildPayload({ - issuer: alice.did(), - audience: bob.did(), - capabilities: [ capability.parse(encodedCaps) ] - }) - - const encoded = token.encodePayload(payload) - const decodedString = uint8arrays.toString( - uint8arrays.fromString(encoded, "base64url"), - "utf8" - ) - - const decoded = JSON.parse(decodedString) - - expect( - JSON.stringify(decoded.att) - ).toEqual( - JSON.stringify([ encodedCaps ]) - ) - }) - -}) - - - -// VALIDATION - - -describe("token.validate", () => { - async function makeUcan() { - return await token.build({ - audience: bob.did(), - issuer: alice, - capabilities: [ - { - "with": { scheme: "wnfs", hierPart: "//boris.fission.name/public/photos/" }, - "can": { namespace: "crud", segments: [ "DELETE" ] } - }, - { - "with": { scheme: "wnfs", hierPart: "//boris.fission.name/private/84MZ7aqwKn7sNiMGsSbaxsEa6EPnQLoKYbXByxNBrCEr" }, - "can": { namespace: "wnfs", segments: [ "APPEND" ] } - }, - { - "with": { scheme: "mailto", hierPart: "boris@fission.codes" }, - "can": { namespace: "msg", segments: [ "SEND" ] } - } - ] - }) - } - - it("round-trips with token.build", async () => { - const ucan = await makeUcan() - const parsedUcan = await token.validate(token.encode(ucan)) - expect(parsedUcan).toBeDefined() - }) - - it("throws with a bad audience", async () => { - const ucan = await makeUcan() - const badPayload = { - ...ucan.payload, - aud: "fakeaudience" - } - const badUcan = `${token.encodeHeader(ucan.header)}.${token.encodePayload(badPayload)}.${ucan.signature}` - await expect(() => token.validate(badUcan)).rejects.toBeDefined() - }) - - it("throws with a bad issuer", async () => { - const ucan = await makeUcan() - const badHeader = { - ...ucan.header, - alg: "RS256" - } - const badUcan = `${token.encodeHeader(badHeader)}.${token.encodePayload(ucan.payload)}.${ucan.signature}` - await expect(() => token.validate(badUcan)).rejects.toBeDefined() - }) - - it("identifies a ucan that is not active yet", async () => { - const ucan = await makeUcan() - const badUcan = { - ...ucan, - payload: { - ...ucan.payload, - nbf: 2637252774, - exp: 2637352774 - } - } - expect(token.isTooEarly(badUcan)).toBe(true) - }) - - it("identifies a ucan that has become active", async () => { - const ucan = await makeUcan() - const activeUcan = { - ...ucan, - payload: { - ...ucan.payload, - nbf: Math.floor(Date.now() / 1000), - lifetimeInSeonds: 30 - } - } - expect(token.isTooEarly(activeUcan)).toBe(false) - }) -}) - -describe("verifySignatureUtf8", () => { - - it("works with an example", async () => { - const [ header, payload, signature ] = token.encode(await token.build({ - issuer: alice, - audience: bob.did(), - })).split(".") - expect(await verifySignatureUtf8(`${header}.${payload}`, signature, alice.did())).toEqual(true) - }) - -}) diff --git a/packages/ucans/tests/verify.test.ts b/packages/ucans/tests/verify.test.ts deleted file mode 100644 index ba91658..0000000 --- a/packages/ucans/tests/verify.test.ts +++ /dev/null @@ -1,350 +0,0 @@ -import * as token from "../src/token" -import * as capability from "../src/capability" -import { SUPERUSER } from "../src/capability/super-user" -import { verify } from "../src/verify" -import { emailCapability } from "./capability/email" -import { alice, bob, mallory } from "./fixtures" -import { REDELEGATE } from "../src/capability/ability" - - -describe("verify", () => { - - async function aliceEmailDelegationExample(expiration?: number) { - // alice -> bob, bob -> mallory - // alice delegates access to sending email as her to bob - // and bob delegates it further to mallory - const leafUcan = await token.build({ - issuer: alice, - audience: bob.did(), - expiration, - capabilities: [ emailCapability("alice@email.com") ] - }) - - const ucan = await token.build({ - issuer: bob, - audience: mallory.did(), - expiration, - capabilities: [ emailCapability("alice@email.com") ], - proofs: [ token.encode(leafUcan) ] - }) - - return token.encode(ucan) - } - - const alicesEmail = { - capability: emailCapability("alice@email.com"), - rootIssuer: alice.did(), - } - - it("verifies a delegation chain", async () => { - const ucan = await aliceEmailDelegationExample() - - const result = await verify(ucan, { - audience: mallory.did(), - requiredCapabilities: [ alicesEmail ] - }) - - if (result.ok === false) { - console.log(result.error) - } - - expect(result.ok).toEqual(true) - - if (!result.ok) return - - expect(result.value[ 0 ]?.rootIssuer).toEqual(alice.did()) - expect(result.value[ 0 ]?.capability).toEqual(emailCapability("alice@email.com")) - }) - - it("rejects an invalid escalation", async () => { - const ucan = await aliceEmailDelegationExample() - - const result = await verify(ucan, { - audience: mallory.did(), - requiredCapabilities: [ { - capability: { - ...emailCapability("alice@email.com"), - can: SUPERUSER, - }, - rootIssuer: alice.did() - } ] - }) - - expect(result.ok).toEqual(false) - }) - - it("rejects for an invalid audience", async () => { - const ucan = await aliceEmailDelegationExample() - - const result = await verify(ucan, { - audience: bob.did(), - requiredCapabilities: [ alicesEmail ] - }) - - expect(result.ok).toEqual(false) - }) - - it("rejects for an invalid rootIssuer", async () => { - const ucan = await aliceEmailDelegationExample() - - const result = await verify(ucan, { - audience: mallory.did(), - requiredCapabilities: [ { - capability: emailCapability("alice@email.com"), - // an invalid rootIssuer - rootIssuer: "did:someone-else", - } ] - }) - - expect(result.ok).toEqual(false) - }) - - it("rejects for an expired capability", async () => { - // unix timestamp in seconds. Will be after - const nowInSeconds = Math.floor(Date.now() / 1000) - // expiry is in the past - const ucan = await aliceEmailDelegationExample(nowInSeconds - 60 * 60 * 24) - - const result = await verify(ucan, { - audience: mallory.did(), - requiredCapabilities: [ alicesEmail ] - }) - - expect(result.ok).toEqual(false) - }) - - it("supports redelegation with a `prf:*` capability", async () => { - // alice -> bob - // alice delegates access to sending email as her to bob - // and bob delegates it further to mallory - const leafUcan = await token.build({ - issuer: alice, - audience: bob.did(), - capabilities: [ emailCapability("alice@email.com") ] - }) - - const ucan = await token.build({ - issuer: bob, - audience: mallory.did(), - capabilities: [ capability.prf(capability.superUser.SUPERUSER, REDELEGATE) ], - proofs: [ token.encode(leafUcan) ] - }) - - const result = await verify(token.encode(ucan), { - audience: mallory.did(), - requiredCapabilities: [ alicesEmail ] - }) - - expect(result.ok).toEqual(true) - - if (!result.ok) return - - expect(result.value[ 0 ]?.rootIssuer).toEqual(alice.did()) - expect(result.value[ 0 ]?.capability).toEqual(emailCapability("alice@email.com")) - }) - - it("supports redelegation with a `prf:1` capability", async () => { - // alice -> bob - // alice delegates access to sending email as her to bob - // and bob delegates it further to mallory - const leafUcanA = await token.build({ - issuer: alice, - audience: bob.did(), - capabilities: [ emailCapability("ignore-me@email.com") ] - }) - - const leafUcanB = await token.build({ - issuer: alice, - audience: bob.did(), - capabilities: [ emailCapability("alice@email.com") ] - }) - - const ucan = await token.build({ - issuer: bob, - audience: mallory.did(), - capabilities: [ capability.prf(1, REDELEGATE) ], - proofs: [ token.encode(leafUcanA), token.encode(leafUcanB) ] - }) - - const result = await verify(token.encode(ucan), { - audience: mallory.did(), - requiredCapabilities: [ alicesEmail ] - }) - - expect(result.ok).toEqual(true) - - if (!result.ok) return - - expect(result.value[ 0 ]?.rootIssuer).toEqual(alice.did()) - expect(result.value[ 0 ]?.capability).toEqual(emailCapability("alice@email.com")) - }) - - it("ignores other proofs not referred to by `prf:0`", async () => { - const leafUcanA = await token.build({ - issuer: alice, - audience: bob.did(), - capabilities: [ emailCapability("ignore-me@email.com") ] - }) - - const leafUcanB = await token.build({ - issuer: alice, - audience: bob.did(), - capabilities: [ emailCapability("alice@email.com") ] - }) - - const faultyUcan = await token.build({ - issuer: bob, - audience: mallory.did(), - capabilities: [ capability.prf(0, REDELEGATE) ], - proofs: [ token.encode(leafUcanA), token.encode(leafUcanB) ] - }) - - const result = await verify(token.encode(faultyUcan), { - audience: mallory.did(), - requiredCapabilities: [ alicesEmail ] - }) - - expect(result.ok).toEqual(false) - }) - - it("rejects an improper `prf` redelegation", async () => { - // alice -> bob - // alice delegates access to sending email as her to bob - // and bob delegates it further to mallory - const leafUcan = await token.build({ - issuer: alice, - audience: bob.did(), - capabilities: [ emailCapability("invalid@email.com") ] - }) - - const ucan = await token.build({ - issuer: bob, - audience: mallory.did(), - capabilities: [ capability.prf(capability.superUser.SUPERUSER, REDELEGATE) ], - proofs: [ token.encode(leafUcan) ] - }) - - const result = await verify(token.encode(ucan), { - audience: mallory.did(), - requiredCapabilities: [ alicesEmail ] - }) - - expect(result.ok).toEqual(false) - }) - - it("supports redelegation with a `my` capability", async () => { - // alice -> bob - // alice delegates access to sending email as her to bob - // and bob delegates it further to mallory - const leafUcan = await token.build({ - issuer: alice, - audience: bob.did(), - capabilities: [ capability.my(capability.superUser.SUPERUSER) ] - }) - - const ucan = await token.build({ - issuer: bob, - audience: mallory.did(), - capabilities: [ emailCapability("alice@email.com") ], - proofs: [ token.encode(leafUcan) ] - }) - - const result = await verify(token.encode(ucan), { - audience: mallory.did(), - requiredCapabilities: [ alicesEmail ], - }) - - expect(result.ok).toEqual(true) - - if (!result.ok) return - - expect(result.value[ 0 ]?.rootIssuer).toEqual(alice.did()) - }) - - it("supports redelegation with a `my` & `as` capability", async () => { - // alice -> bob, bob -> mallory, mallory -> "someone" - // alice delegates access to sending email as her to bob - // and bob delegates it further to mallory - const leafUcan = await token.build({ - issuer: alice, - audience: bob.did(), - capabilities: [ capability.my(capability.superUser.SUPERUSER) ] - }) - - const middleUcan = await token.build({ - issuer: bob, - audience: mallory.did(), - capabilities: [ capability.as(alice.did(), SUPERUSER) ], - proofs: [ token.encode(leafUcan) ] - }) - - const ucan = await token.build({ - issuer: mallory, - audience: "did:key:someone", - capabilities: [ emailCapability("alice@email.com") ], - proofs: [ token.encode(middleUcan) ] - }) - - const result = await verify(token.encode(ucan), { - audience: "did:key:someone", - requiredCapabilities: [ alicesEmail ] - }) - - expect(result.ok).toEqual(true) - - if (!result.ok) return - - expect(result.value[ 0 ]?.rootIssuer).toEqual(alice.did()) - }) - - it("rejects an improper `my` redelegation", async () => { - // alice -> bob - // alice delegates access to sending email as her to bob - // and bob delegates it further to mallory - const leafUcan = await token.build({ - issuer: alice, - audience: bob.did(), - capabilities: [] - }) - - const ucan = await token.build({ - issuer: bob, - audience: mallory.did(), - capabilities: [ emailCapability("alice@email.com") ], - proofs: [ token.encode(leafUcan) ] - }) - - const result = await verify(token.encode(ucan), { - audience: mallory.did(), - requiredCapabilities: [ alicesEmail ] - }) - - expect(result.ok).toEqual(false) - }) - - it("rejects an improper `as` redelegation - no `my`", async () => { - // alice -> bob - // alice delegates access to sending email as her to bob - // and bob delegates it further to mallory - const leafUcan = await token.build({ - issuer: alice, - audience: bob.did(), - capabilities: [ capability.as(bob.did(), SUPERUSER) ] - }) - - const ucan = await token.build({ - issuer: bob, - audience: mallory.did(), - capabilities: [ emailCapability("alice@email.com") ], - proofs: [ token.encode(leafUcan) ] - }) - - const result = await verify(token.encode(ucan), { - audience: mallory.did(), - requiredCapabilities: [ alicesEmail ] - }) - - expect(result.ok).toEqual(false) - }) - -}) diff --git a/tsconfig.eslint.json b/tsconfig.eslint.json deleted file mode 100644 index e662081..0000000 --- a/tsconfig.eslint.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "extends": "./tsconfig.json", - "include": ["**/*.ts"], - "exclude": [] -} From 11108c5a45d02724796761937e5762824a960223 Mon Sep 17 00:00:00 2001 From: dholms Date: Wed, 29 Jun 2022 10:24:25 -0500 Subject: [PATCH 09/25] Cleanup root package.json --- package.json | 23 +---------------------- 1 file changed, 1 insertion(+), 22 deletions(-) diff --git a/package.json b/package.json index 5a91d77..2139bad 100644 --- a/package.json +++ b/package.json @@ -15,26 +15,5 @@ "private": true, "workspaces": [ "packages/*" - ], - "scripts": { }, - "dependencies": { - "@stablelib/ed25519": "^1.0.2", - "big-integer": "^1.6.51", - "one-webcrypto": "^1.0.3", - "uint8arrays": "^3.0.0" - }, - "devDependencies": { - "@types/jest": "^27.4.1", - "@types/node": "^17.0.23", - "@typescript-eslint/eslint-plugin": "^5.18.0", - "@typescript-eslint/parser": "^5.18.0", - "copyfiles": "^2.4.1", - "eslint": "^8.12.0", - "fast-check": "^2.24.0", - "jest": "^27.5.1", - "rimraf": "^3.0.2", - "ts-jest": "^27.1.4", - "typescript": "^4.6.3", - "yarn": "^1.22.18" - } + ] } From b9c11183b9a6e45fd2b58f9a81a0d2ed59b55845 Mon Sep 17 00:00:00 2001 From: dholms Date: Tue, 5 Jul 2022 09:46:45 -0500 Subject: [PATCH 10/25] tsconfigs in root --- packages/core/package.json | 2 +- tsconfig.eslint.json | 5 +++++ tsconfig.json | 13 +++++++++++++ 3 files changed, 19 insertions(+), 1 deletion(-) create mode 100644 tsconfig.eslint.json create mode 100644 tsconfig.json diff --git a/packages/core/package.json b/packages/core/package.json index d8e319c..795dca7 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -20,7 +20,7 @@ "dist:cjs": "tsc --project ./dist/ --module commonjs --outDir ./dist/cjs/ --sourceMap", "dist:esm": "tsc --project ./dist/ --module es2020 --outDir ./dist/esm/ --sourceMap", "dist:pkg": "node ../../scripts/package.js", - "dist:prep": "copyfiles --error ../../tsconfig.json ./dist/", + "dist:prep": "copyfiles --error tsconfig.json ./dist/", "dist:src": "copyfiles --error --up 1 \"./src/**/*\" ./dist/src/", "dist:types": "tsc --project ./dist/ --emitDeclarationOnly --declaration --declarationDir ./dist/types/", "lint": "eslint src/**/*.ts src/*.ts tests/**/*.ts tests/*.ts", diff --git a/tsconfig.eslint.json b/tsconfig.eslint.json new file mode 100644 index 0000000..e662081 --- /dev/null +++ b/tsconfig.eslint.json @@ -0,0 +1,5 @@ +{ + "extends": "./tsconfig.json", + "include": ["**/*.ts"], + "exclude": [] +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..c60fdc6 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,13 @@ +{ + "compilerOptions": { + "target": "ES2015", + "strict": true, + "moduleResolution": "node", + "esModuleInterop": true, + "noFallthroughCasesInSwitch": true, + "allowJs": true + }, + "include": [ + "src/**/*" + ] +} \ No newline at end of file From 7697e2140589571389c2e8c1571bc61f4bcb3758 Mon Sep 17 00:00:00 2001 From: dholms Date: Tue, 5 Jul 2022 10:00:28 -0500 Subject: [PATCH 11/25] PR fixups --- packages/core/src/did.ts | 64 ----------------------- packages/core/src/plugins.ts | 55 ++++---------------- packages/core/src/types.ts | 3 -- packages/core/tests/did.test.ts | 68 ------------------------- packages/plugins/src/default-plugins.ts | 2 +- packages/plugins/src/ed25519/crypto.ts | 2 +- packages/plugins/src/ed25519/plugin.ts | 5 +- packages/plugins/src/p256/plugin.ts | 7 +-- packages/plugins/src/rsa/plugin.ts | 14 ++--- packages/plugins/tests/ecdsa.test.ts | 10 +--- packages/plugins/tests/ed25519.test.ts | 10 +--- packages/plugins/tests/rsa.test.ts | 10 +--- 12 files changed, 28 insertions(+), 222 deletions(-) delete mode 100644 packages/core/src/did.ts delete mode 100644 packages/core/tests/did.test.ts diff --git a/packages/core/src/did.ts b/packages/core/src/did.ts deleted file mode 100644 index 5065f3e..0000000 --- a/packages/core/src/did.ts +++ /dev/null @@ -1,64 +0,0 @@ -import * as uint8arrays from "uint8arrays" - -import * as plugins from "./plugins.js" -// import * as compression from "./pubkey-compress.js" -// import * as rsa from "../crypto/rsa.js" -// import { BASE58_DID_PREFIX, RSA_DID_PREFIX_OLD, magicBytes, parseMagicBytes, hasPrefix } from "./prefix.js" -import { Encodings } from "./types.js" - - -// DID → PUBLIC KEY - - -/** - * Convert a DID (did:key) to a base64 public key. - */ -export function didToPublicKey(did: string, encoding: Encodings = "base64pad"): { - publicKey: string - jwtAlg: string -} { - const { publicKey, jwtAlg } = didToPublicKeyBytes(did) - return { - publicKey: uint8arrays.toString(publicKey, encoding), - jwtAlg - } -} - -/** - * Convert a DID (did:key) to the public key into bytes in SubjectPublicKeyInfo (spki) format. - * - * For consumption e.g. in the WebCrypto API. - */ -export function didToPublicKeyBytes(did: string): { - publicKey: Uint8Array - jwtAlg: string -} { - return plugins.didToPublicKeyBytes(did) -} - - - -// PUBLIC KEY → DID - - -/** - * Convert a base64 public key to a DID (did:key). - */ -export function publicKeyToDid( - publicKey: string, - jwtAlg: string, - encoding: Encodings = "base64pad" -): string { - const pubKeyBytes = uint8arrays.fromString(publicKey, encoding) - return publicKeyBytesToDid(pubKeyBytes, jwtAlg) -} - -/** - * Convert a public key in bytes to a DID (did:key). - */ -export function publicKeyBytesToDid( - publicKeyBytes: Uint8Array, - jwtAlg: string, -): string { - return plugins.publicKeyBytesToDid(publicKeyBytes, jwtAlg) -} \ No newline at end of file diff --git a/packages/core/src/plugins.ts b/packages/core/src/plugins.ts index 0b2f6a4..f04b8e4 100644 --- a/packages/core/src/plugins.ts +++ b/packages/core/src/plugins.ts @@ -3,20 +3,17 @@ import * as uint8arrays from "uint8arrays" export type DidKeyPlugin = { prefix: Uint8Array jwtAlg: string - didToPublicKey: (did: string) => Uint8Array - publicKeyToDid: (pubkey: Uint8Array) => string - verifySignature: (publicKey: Uint8Array, data: Uint8Array, sig: Uint8Array) => Promise + verifySignature: (did: string, data: Uint8Array, sig: Uint8Array) => Promise } export type DidMethodPlugin = { - isMatch: (method: string, did: string) => boolean checkJwtAlg: (did: string, jwtAlg: string) => boolean verifySignature: (did: string, data: Uint8Array, sig: Uint8Array) => Promise } export type Plugins = { keys: DidKeyPlugin[] - methods: DidMethodPlugin[] + methods: Record } let plugins: Plugins | null = null @@ -38,10 +35,9 @@ export const verifyIssuerAlg = (did: string, jwtAlg: string): boolean => { } } } else { - for (const didPlugin of plugins.methods) { - if(didPlugin.isMatch(didMethod, did)) { - return didPlugin.checkJwtAlg(did, jwtAlg) - } + const maybePlugin = plugins.methods[didMethod] + if(maybePlugin) { + return maybePlugin.checkJwtAlg(did, jwtAlg) } } throw new Error(`DID method not supported by plugins: ${did}`) @@ -56,51 +52,18 @@ export const verifySignature = async (did: string, data: Uint8Array, sig: Uint8A const bytes = parsePrefixedBytes(did) for (const keyPlugin of plugins.keys) { if(hasPrefix(bytes, keyPlugin.prefix)) { - const publicKey = keyPlugin.didToPublicKey(did) - return keyPlugin.verifySignature(publicKey, data, sig) + return keyPlugin.verifySignature(did, data, sig) } } } else { - for (const didPlugin of plugins.methods) { - if(didPlugin.isMatch(didMethod, did)) { - return didPlugin.verifySignature(did, data, sig) - } - } - } - throw new Error(`DID method not supported by plugins: ${did}`) -} - -export const didToPublicKeyBytes = (did: string): { publicKey: Uint8Array; jwtAlg: string } => { - if(plugins === null) { - throw new Error("No plugins loaded") - } - const didMethod = parseDidMethod(did) - if(didMethod === "key") { - const bytes = parsePrefixedBytes(did) - for (const keyPlugin of plugins.keys) { - if(hasPrefix(bytes, keyPlugin.prefix)) { - return { - publicKey: keyPlugin.didToPublicKey(did), - jwtAlg: keyPlugin.jwtAlg - } - } + const maybePlugin = plugins.methods[didMethod] + if (maybePlugin) { + return maybePlugin.verifySignature(did, data, sig) } } throw new Error(`DID method not supported by plugins: ${did}`) } -export const publicKeyBytesToDid = (publicKey: Uint8Array, jwtAlg: string): string => { - if(plugins === null) { - throw new Error("No plugins loaded") - } - for(const keyPlugin of plugins.keys) { - if(keyPlugin.jwtAlg === jwtAlg) { - return keyPlugin.publicKeyToDid(publicKey) - } - } - throw new Error(`JWT alg not supported by plugins: ${jwtAlg}`) -} - export const hasPrefix = ( prefixedKey: Uint8Array, prefix: Uint8Array diff --git a/packages/core/src/types.ts b/packages/core/src/types.ts index 4da97c4..d7f1cec 100644 --- a/packages/core/src/types.ts +++ b/packages/core/src/types.ts @@ -54,9 +54,6 @@ export type Fact = Record // CRYPTOGRAPHY - -/** Unlike tslib's CryptoKeyPair, this requires the `privateKey` and `publicKey` fields */ - export interface Didable { did: () => string } diff --git a/packages/core/tests/did.test.ts b/packages/core/tests/did.test.ts deleted file mode 100644 index fee0b4f..0000000 --- a/packages/core/tests/did.test.ts +++ /dev/null @@ -1,68 +0,0 @@ -import * as did from "../src/did" -import { loadTestPlugins } from "./setup.js" - -describe("publicKeyToDid", () => { - - beforeAll(loadTestPlugins) - - it("handles RSA Keys", async () => { - const pubkey = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAnzyis1ZjfNB0bBgKFMSvvkTtwlvBsaJq7S5wA+kzeVOVpVWwkWdVha4s38XM/pa/yr47av7+z3VTmvDRyAHcaT92whREFpLv9cj5lTeJSibyr/Mrm/YtjCZVWgaOYIhwrXwKLqPr/11inWsAkfIytvHWTxZYEcXLgAXFuUuaS3uF9gEiNQwzGTU1v0FqkqTBr4B8nW3HCN47XUu0t8Y0e+lf4s4OxQawWD79J9/5d3Ry0vbV3Am1FtGJiJvOwRsIfVChDpYStTcHTCMqtvWbV6L11BWkpzGXSW4Hv43qa+GSYOD2QU68Mb59oSk2OB+BtOLpJofmbGEGgvmwyCI9MwIDAQAB" - // old: const expectedDid = "did:key:z13V3Sog2YaUKhdGCmgx9UZuW1o1ShFJYc6DvGYe7NTt689NoL2RtpVs65Zw899YrTN9WuxdEEDm54YxWuQHQvcKfkZwa8HTgokHxGDPEmNLhvh69zUMEP4zjuARQ3T8bMUumkSLGpxNe1bfQX624ef45GhWb3S9HM3gvAJ7Qftm8iqnDQVcxwKHjmkV4hveKMTix4bTRhieVHi1oqU4QCVy4QPWpAAympuCP9dAoJFxSP6TNBLY9vPKLazsg7XcFov6UuLWsEaxJ5SomCpDx181mEgW2qTug5oQbrJwExbD9CMgXHLVDE2QgLoQMmgsrPevX57dH715NXC2uY6vo2mYCzRY4KuDRUsrkuYCkewL8q2oK1BEDVvi3Sg8pbC9QYQ5mMiHf8uxiHxTAmPedv8" - const expectedDid = "did:key:z4MXj1wBzi9jUstyNvmiK5WLRRL4rr9UvzPxhry1CudCLKWLyMbP1WoTwDfttBTpxDKf5hAJEjqNbeYx2EEvrJmSWHAu7TJRPTrE3QodbMfRvRNRDyYvaN1FSQus2ziS1rWXwAi5Gpc16bY3JwjyLCPJLfdRWHZhRXiay5FWEkfoSKy6aftnzAvqNkKBg2AxgzGMinR6d1WiH4w5mEXFtUeZkeo4uwtRTd8rD9BoVaHVkGwJkksDybE23CsBNXiNfbweFVRcwfTMhcQsTsYhUWDcSC6QE3zt9h4Rsrj7XRYdwYSK5bc1qFRsg5HULKBp2uZ1gcayiW2FqHFcMRjBieC4LnSMSD1AZB1WUncVRbPpVkn1UGhCU" - const result = did.publicKeyToDid(pubkey, "RS256") - expect(result).toEqual(expectedDid) - }) - - it("handles Ed25519 Keys", async () => { - const pubkey = "Hv+AVRD2WUjUFOsSNbsmrp9fokuwrUnjBcr92f0kxw4=" - const expectedDid = "did:key:z6MkgYGF3thn8k1Fv4p4dWXKtsXCnLH7q9yw4QgNPULDmDKB" - const result = did.publicKeyToDid(pubkey, "EdDSA") - expect(result).toEqual(expectedDid) - }) - - it("handles NIST P-256 Keys", async () => { - const pubkey = "BEgg6smRlRQXKqzJHu0w5/nHcy7zYNxFXwTKUyyCOoDMztUvQn5VZg668v4MecHGUZTeHkaZhSraK7RtJuXfWp4=" - const expectedDid = "did:key:zDnaeVHXWgKUpJFc8AUfRDuRWfsAQejmZu5HrERh41iGumZVu" - const result = did.publicKeyToDid(pubkey, "ES256") - expect(result).toEqual(expectedDid) - }) - -}) - -describe("didToPublicKey", () => { - - beforeAll(loadTestPlugins) - - it("handles old RSA Keys", async () => { - const toDecode = "did:key:z13V3Sog2YaUKhdGCmgx9UZuW1o1ShFJYc6DvGYe7NTt689NoL2RtpVs65Zw899YrTN9WuxdEEDm54YxWuQHQvcKfkZwa8HTgokHxGDPEmNLhvh69zUMEP4zjuARQ3T8bMUumkSLGpxNe1bfQX624ef45GhWb3S9HM3gvAJ7Qftm8iqnDQVcxwKHjmkV4hveKMTix4bTRhieVHi1oqU4QCVy4QPWpAAympuCP9dAoJFxSP6TNBLY9vPKLazsg7XcFov6UuLWsEaxJ5SomCpDx181mEgW2qTug5oQbrJwExbD9CMgXHLVDE2QgLoQMmgsrPevX57dH715NXC2uY6vo2mYCzRY4KuDRUsrkuYCkewL8q2oK1BEDVvi3Sg8pbC9QYQ5mMiHf8uxiHxTAmPedv8" - const expectedKey = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAnzyis1ZjfNB0bBgKFMSvvkTtwlvBsaJq7S5wA+kzeVOVpVWwkWdVha4s38XM/pa/yr47av7+z3VTmvDRyAHcaT92whREFpLv9cj5lTeJSibyr/Mrm/YtjCZVWgaOYIhwrXwKLqPr/11inWsAkfIytvHWTxZYEcXLgAXFuUuaS3uF9gEiNQwzGTU1v0FqkqTBr4B8nW3HCN47XUu0t8Y0e+lf4s4OxQawWD79J9/5d3Ry0vbV3Am1FtGJiJvOwRsIfVChDpYStTcHTCMqtvWbV6L11BWkpzGXSW4Hv43qa+GSYOD2QU68Mb59oSk2OB+BtOLpJofmbGEGgvmwyCI9MwIDAQAB" - const { publicKey, jwtAlg } = did.didToPublicKey(toDecode) - expect(publicKey).toEqual(expectedKey) - expect(jwtAlg).toEqual("RS256") - }) - - it("handles standardized RSA Keys", async () => { - const toDecode = "did:key:z4MXj1wBzi9jUstyNvmiK5WLRRL4rr9UvzPxhry1CudCLKWLyMbP1WoTwDfttBTpxDKf5hAJEjqNbeYx2EEvrJmSWHAu7TJRPTrE3QodbMfRvRNRDyYvaN1FSQus2ziS1rWXwAi5Gpc16bY3JwjyLCPJLfdRWHZhRXiay5FWEkfoSKy6aftnzAvqNkKBg2AxgzGMinR6d1WiH4w5mEXFtUeZkeo4uwtRTd8rD9BoVaHVkGwJkksDybE23CsBNXiNfbweFVRcwfTMhcQsTsYhUWDcSC6QE3zt9h4Rsrj7XRYdwYSK5bc1qFRsg5HULKBp2uZ1gcayiW2FqHFcMRjBieC4LnSMSD1AZB1WUncVRbPpVkn1UGhCU" - const expectedKey = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAnzyis1ZjfNB0bBgKFMSvvkTtwlvBsaJq7S5wA+kzeVOVpVWwkWdVha4s38XM/pa/yr47av7+z3VTmvDRyAHcaT92whREFpLv9cj5lTeJSibyr/Mrm/YtjCZVWgaOYIhwrXwKLqPr/11inWsAkfIytvHWTxZYEcXLgAXFuUuaS3uF9gEiNQwzGTU1v0FqkqTBr4B8nW3HCN47XUu0t8Y0e+lf4s4OxQawWD79J9/5d3Ry0vbV3Am1FtGJiJvOwRsIfVChDpYStTcHTCMqtvWbV6L11BWkpzGXSW4Hv43qa+GSYOD2QU68Mb59oSk2OB+BtOLpJofmbGEGgvmwyCI9MwIDAQAB" - const { publicKey, jwtAlg } = did.didToPublicKey(toDecode) - expect(publicKey).toEqual(expectedKey) - expect(jwtAlg).toEqual("RS256") - }) - - it("handles Ed25519 Keys", async () => { - const toDecode = "did:key:z6MkgYGF3thn8k1Fv4p4dWXKtsXCnLH7q9yw4QgNPULDmDKB" - const expectedKey = "Hv+AVRD2WUjUFOsSNbsmrp9fokuwrUnjBcr92f0kxw4=" - const { publicKey, jwtAlg } = did.didToPublicKey(toDecode) - expect(publicKey).toEqual(expectedKey) - expect(jwtAlg).toEqual("EdDSA") - }) - - it("handles NIST P-256 Keys", async () => { - const toDecode = "did:key:zDnaeVHXWgKUpJFc8AUfRDuRWfsAQejmZu5HrERh41iGumZVu" - const expectedKey = "BEgg6smRlRQXKqzJHu0w5/nHcy7zYNxFXwTKUyyCOoDMztUvQn5VZg668v4MecHGUZTeHkaZhSraK7RtJuXfWp4=" - const { publicKey, jwtAlg } = did.didToPublicKey(toDecode) - expect(publicKey).toEqual(expectedKey) - expect(jwtAlg).toEqual("ES256") - }) - -}) diff --git a/packages/plugins/src/default-plugins.ts b/packages/plugins/src/default-plugins.ts index 0809af6..c5a6000 100644 --- a/packages/plugins/src/default-plugins.ts +++ b/packages/plugins/src/default-plugins.ts @@ -5,5 +5,5 @@ import { rsaPlugin, rsaOldPlugin } from "./rsa/plugin.js" export const defaults: Plugins = { keys: [ed25519Plugin, p256Plugin, rsaPlugin, rsaOldPlugin], - methods: [], + methods: {}, } \ No newline at end of file diff --git a/packages/plugins/src/ed25519/crypto.ts b/packages/plugins/src/ed25519/crypto.ts index 3dc2002..925dc87 100644 --- a/packages/plugins/src/ed25519/crypto.ts +++ b/packages/plugins/src/ed25519/crypto.ts @@ -1,7 +1,7 @@ import { EDWARDS_DID_PREFIX } from "../prefixes" import { didFromKeyBytes, keyBytesFromDid } from "../util" -export const didToPublickey = (did: string): Uint8Array => { +export const didToPublicKey = (did: string): Uint8Array => { return keyBytesFromDid(did, EDWARDS_DID_PREFIX) } diff --git a/packages/plugins/src/ed25519/plugin.ts b/packages/plugins/src/ed25519/plugin.ts index 45605e9..a42b842 100644 --- a/packages/plugins/src/ed25519/plugin.ts +++ b/packages/plugins/src/ed25519/plugin.ts @@ -7,9 +7,8 @@ import { EDWARDS_DID_PREFIX } from "../prefixes.js" export const ed25519Plugin: DidKeyPlugin = { prefix: EDWARDS_DID_PREFIX, jwtAlg: "EdDSA", - didToPublicKey: crypto.didToPublickey, - publicKeyToDid: crypto.publicKeyToDid, - verifySignature: async (publicKey: Uint8Array, data: Uint8Array, sig: Uint8Array) => { + verifySignature: async (did: string, data: Uint8Array, sig: Uint8Array) => { + const publicKey = crypto.didToPublicKey(did) return ed25519.verify(publicKey, data, sig) } } \ No newline at end of file diff --git a/packages/plugins/src/p256/plugin.ts b/packages/plugins/src/p256/plugin.ts index abba0ae..bbf449f 100644 --- a/packages/plugins/src/p256/plugin.ts +++ b/packages/plugins/src/p256/plugin.ts @@ -5,7 +5,8 @@ import { P256_DID_PREFIX } from "../prefixes.js" export const p256Plugin: DidKeyPlugin = { prefix: P256_DID_PREFIX, jwtAlg: "ES256", - didToPublicKey: crypto.didToPublicKey, - publicKeyToDid: crypto.publicKeyToDid, - verifySignature: crypto.verify, + verifySignature: async (did: string, data: Uint8Array, sig: Uint8Array) => { + const publicKey = crypto.didToPublicKey(did) + return crypto.verify(publicKey, data, sig) + } } \ No newline at end of file diff --git a/packages/plugins/src/rsa/plugin.ts b/packages/plugins/src/rsa/plugin.ts index d56bc7f..f358de0 100644 --- a/packages/plugins/src/rsa/plugin.ts +++ b/packages/plugins/src/rsa/plugin.ts @@ -5,16 +5,18 @@ import { RSA_DID_PREFIX, RSA_DID_PREFIX_OLD } from "../prefixes.js" export const rsaPlugin: DidKeyPlugin = { prefix: RSA_DID_PREFIX, jwtAlg: "RS256", - didToPublicKey: crypto.didToPublicKey, - publicKeyToDid: crypto.publicKeyToDid, - verifySignature: crypto.verify, + verifySignature: async (did: string, data: Uint8Array, sig: Uint8Array) => { + const publicKey = crypto.didToPublicKey(did) + return crypto.verify(publicKey, data, sig) + } } export const rsaOldPlugin: DidKeyPlugin = { prefix: RSA_DID_PREFIX_OLD, jwtAlg: "RS256", - didToPublicKey: crypto.oldDidToPublicKey, - publicKeyToDid: crypto.publicKeyToOldDid, - verifySignature: crypto.verify, + verifySignature: async (did: string, data: Uint8Array, sig: Uint8Array) => { + const publicKey = crypto.oldDidToPublicKey(did) + return crypto.verify(publicKey, data, sig) + } } diff --git a/packages/plugins/tests/ecdsa.test.ts b/packages/plugins/tests/ecdsa.test.ts index c7642e1..214c4c3 100644 --- a/packages/plugins/tests/ecdsa.test.ts +++ b/packages/plugins/tests/ecdsa.test.ts @@ -16,20 +16,12 @@ describe("ecdsa", () => { expect(keypair.jwtAlg).toEqual("ES256") }) - it("can transform between DID & public key", () => { - const did = keypair.did() - const publicKey = p256Plugin.didToPublicKey(did) - const didAgain = p256Plugin.publicKeyToDid(publicKey) - expect(did).toEqual(didAgain) - }) - it("signs data", async () => { signature = await keypair.sign(data) }) it("can verify signature", async () => { - const publicKey = p256Plugin.didToPublicKey(keypair.did()) - const isValid = await p256Plugin.verifySignature(publicKey, data, signature) + const isValid = await p256Plugin.verifySignature(keypair.did(), data, signature) expect(isValid).toBeTruthy() }) diff --git a/packages/plugins/tests/ed25519.test.ts b/packages/plugins/tests/ed25519.test.ts index 338dbe2..72ee25c 100644 --- a/packages/plugins/tests/ed25519.test.ts +++ b/packages/plugins/tests/ed25519.test.ts @@ -15,20 +15,12 @@ describe("ed25519", () => { expect(keypair.jwtAlg).toEqual("EdDSA") }) - it("can transform between DID & public key", () => { - const did = keypair.did() - const publicKey = ed25519Plugin.didToPublicKey(did) - const didAgain = ed25519Plugin.publicKeyToDid(publicKey) - expect(did).toEqual(didAgain) - }) - it("signs data", async () => { signature = await keypair.sign(data) }) it("can verify signature", async () => { - const publicKey = ed25519Plugin.didToPublicKey(keypair.did()) - const isValid = await ed25519Plugin.verifySignature(publicKey, data, signature) + const isValid = await ed25519Plugin.verifySignature(keypair.did(), data, signature) expect(isValid).toBeTruthy() }) diff --git a/packages/plugins/tests/rsa.test.ts b/packages/plugins/tests/rsa.test.ts index ab47538..1b4a486 100644 --- a/packages/plugins/tests/rsa.test.ts +++ b/packages/plugins/tests/rsa.test.ts @@ -19,20 +19,12 @@ describe("rsa", () => { expect(keypair.jwtAlg).toEqual("RS256") }) - it("returns a publicKeyStr and did", () => { - const did = keypair.did() - const publicKey = rsaPlugin.didToPublicKey(did) - const didAgain = rsaPlugin.publicKeyToDid(publicKey) - expect(did).toEqual(didAgain) - }) - it("signs data", async () => { signature = await keypair.sign(data) }) it("can verify signature", async () => { - const publicKey = rsaPlugin.didToPublicKey(keypair.did()) - const isValid = await rsaPlugin.verifySignature(publicKey, data, signature) + const isValid = await rsaPlugin.verifySignature(keypair.did(), data, signature) expect(isValid).toBeTruthy() }) From ef01a24e2e7fa17d8a134926eee2b676833d9e35 Mon Sep 17 00:00:00 2001 From: dholms Date: Tue, 5 Jul 2022 10:31:24 -0500 Subject: [PATCH 12/25] DidableKey --- packages/core/src/builder.ts | 6 +++--- packages/core/src/token.ts | 4 ++-- packages/core/src/types.ts | 2 ++ packages/plugins/src/ed25519/keypair.ts | 4 ++-- packages/plugins/src/p256/keypair.ts | 4 ++-- packages/plugins/src/rsa/index.ts | 0 packages/plugins/src/rsa/keypair.ts | 4 ++-- 7 files changed, 13 insertions(+), 11 deletions(-) delete mode 100644 packages/plugins/src/rsa/index.ts diff --git a/packages/core/src/builder.ts b/packages/core/src/builder.ts index f3cfeb5..a582869 100644 --- a/packages/core/src/builder.ts +++ b/packages/core/src/builder.ts @@ -1,14 +1,14 @@ import * as token from "./token.js" import * as util from "./util.js" -import { Keypair, Fact, UcanPayload, isKeypair, Ucan, Didable } from "./types.js" +import { Keypair, Fact, UcanPayload, isKeypair, Ucan, DidableKey } from "./types.js" import { Capability, isCapability } from "./capability/index.js" import { capabilityCanBeDelegated, DelegationSemantics, DelegationChain } from "./attenuation.js" import { Store } from "./store.js" export interface BuildableState { - issuer: Keypair & Didable + issuer: DidableKey audience: string expiration: number } @@ -84,7 +84,7 @@ export class Builder> { * * The UCAN must be signed with the private key of the issuer to be valid. */ - issuedBy(issuer: Keypair & Didable): Builder { + issuedBy(issuer: DidableKey): Builder { if (!isKeypair(issuer)) { throw new TypeError(`Expected a Keypair, but got ${issuer}`) } diff --git a/packages/core/src/token.ts b/packages/core/src/token.ts index b390532..9bad659 100644 --- a/packages/core/src/token.ts +++ b/packages/core/src/token.ts @@ -6,7 +6,7 @@ import * as util from "./util.js" import * as plugins from "./plugins.js" import { Capability, isCapability, isEncodedCapability } from "./capability/index.js" -import { Fact, Keypair, Didable } from "./types.js" +import { Fact, Keypair, DidableKey } from "./types.js" import { Ucan, UcanHeader, UcanParts, UcanPayload } from "./types.js" import { handleCompatibility } from "./compatibility.js" @@ -45,7 +45,7 @@ const VERSION = { major: 0, minor: 8, patch: 1 } */ export async function build(params: { // from/to - issuer: Keypair & Didable + issuer: DidableKey audience: string // capabilities diff --git a/packages/core/src/types.ts b/packages/core/src/types.ts index d7f1cec..f0f85dd 100644 --- a/packages/core/src/types.ts +++ b/packages/core/src/types.ts @@ -67,6 +67,8 @@ export interface Keypair { sign: (msg: Uint8Array) => Promise } +export interface DidableKey extends Didable, Keypair {} + // MISC diff --git a/packages/plugins/src/ed25519/keypair.ts b/packages/plugins/src/ed25519/keypair.ts index 51a6113..d217546 100644 --- a/packages/plugins/src/ed25519/keypair.ts +++ b/packages/plugins/src/ed25519/keypair.ts @@ -2,10 +2,10 @@ import * as uint8arrays from "uint8arrays" import * as ed25519 from "@stablelib/ed25519" import * as crypto from "./crypto.js" -import { Didable, Encodings, ExportableKey, Keypair } from "@ucans/core" +import { DidableKey, Encodings, ExportableKey } from "@ucans/core" -export class EdKeypair implements Keypair, Didable, ExportableKey { +export class EdKeypair implements DidableKey, ExportableKey { public jwtAlg = "EdDSA" diff --git a/packages/plugins/src/p256/keypair.ts b/packages/plugins/src/p256/keypair.ts index b831ce5..76b8e2f 100644 --- a/packages/plugins/src/p256/keypair.ts +++ b/packages/plugins/src/p256/keypair.ts @@ -1,6 +1,6 @@ import { webcrypto } from "one-webcrypto" import * as uint8arrays from "uint8arrays" -import { Didable, Encodings, ExportableKey, Keypair } from "@ucans/core" +import { DidableKey, Encodings, ExportableKey } from "@ucans/core" import * as crypto from "./crypto.js" import { @@ -10,7 +10,7 @@ import { } from "../types.js" -export class EcdsaKeypair implements Keypair, Didable, ExportableKey { +export class EcdsaKeypair implements DidableKey, ExportableKey { public jwtAlg = "ES256" diff --git a/packages/plugins/src/rsa/index.ts b/packages/plugins/src/rsa/index.ts deleted file mode 100644 index e69de29..0000000 diff --git a/packages/plugins/src/rsa/keypair.ts b/packages/plugins/src/rsa/keypair.ts index c303b38..0c5dff1 100644 --- a/packages/plugins/src/rsa/keypair.ts +++ b/packages/plugins/src/rsa/keypair.ts @@ -3,10 +3,10 @@ import * as uint8arrays from "uint8arrays" import * as crypto from "./crypto.js" import { AvailableCryptoKeyPair, isAvailableCryptoKeyPair } from "../types.js" -import { Didable, Encodings, ExportableKey, Keypair } from "@ucans/core" +import { DidableKey, Encodings, ExportableKey } from "@ucans/core" -export class RsaKeypair implements Keypair, Didable, ExportableKey { +export class RsaKeypair implements DidableKey, ExportableKey { public jwtAlg = "RS256" From 61ed38e4ca8b21a23a9e1b313aa125acc4520b64 Mon Sep 17 00:00:00 2001 From: dholms Date: Tue, 5 Jul 2022 14:02:52 -0500 Subject: [PATCH 13/25] Inject plugins by passing through fns instead of global state var --- packages/core/src/attenuation.ts | 40 ++++--- packages/core/src/builder.ts | 52 +++++---- packages/core/src/capability/ability.ts | 5 + packages/core/src/capability/index.ts | 9 +- packages/core/src/index.ts | 36 +++++- packages/core/src/plugins.ts | 79 ++++++------- packages/core/src/store.ts | 34 ++++-- packages/core/src/token.ts | 76 +++++++------ packages/core/src/verify.ts | 8 +- packages/core/tests/attenuation.test.ts | 33 +++--- packages/core/tests/builder.test.ts | 33 +++--- packages/core/tests/capability/email.ts | 8 +- packages/core/tests/capability/wnfs.test.ts | 22 ++-- packages/core/tests/capability/wnfs.ts | 7 +- packages/core/tests/compatibility.test.ts | 8 +- packages/core/tests/setup.ts | 19 +++- packages/core/tests/store.test.ts | 55 ++++----- packages/core/tests/token.test.ts | 42 +++---- packages/core/tests/verify.test.ts | 118 +++++++++----------- packages/plugins/src/default-plugins.ts | 8 +- packages/ucans/src/index.ts | 19 +++- 21 files changed, 376 insertions(+), 335 deletions(-) diff --git a/packages/core/src/attenuation.ts b/packages/core/src/attenuation.ts index 26ee68b..85c570b 100644 --- a/packages/core/src/attenuation.ts +++ b/packages/core/src/attenuation.ts @@ -1,4 +1,5 @@ import * as token from "./token.js" +import Plugins from "./plugins.js" import { Capability } from "./capability/index.js" import { Ucan } from "./types.js" import { ResourcePointer } from "./capability/resource-pointer.js" @@ -119,11 +120,12 @@ export type OwnershipScope * out different ways of delegating a capability from the attenuations. * It also makes it possible to return early if a valid delegation chain has been found. */ -export async function* delegationChains( - semantics: DelegationSemantics, - ucan: Ucan, - isRevoked: (ucan: Ucan) => Promise = async () => false -): AsyncIterable { +export const delegationChains = (plugins: Plugins) => + async function* ( + semantics: DelegationSemantics, + ucan: Ucan, + isRevoked: (ucan: Ucan) => Promise = async () => false, + ): AsyncIterable { if (await isRevoked(ucan)) { yield new Error(`UCAN Revoked: ${token.encode(ucan)}`) @@ -131,7 +133,7 @@ export async function* delegationChains( } yield* capabilitiesFromParenthood(ucan) - yield* capabilitiesFromDelegation(semantics, ucan, isRevoked) + yield* capabilitiesFromDelegation(plugins, semantics, ucan, isRevoked) } @@ -265,14 +267,15 @@ function* capabilitiesFromParenthood(ucan: Ucan): Iterable { async function* capabilitiesFromDelegation( + plugins: Plugins, semantics: DelegationSemantics, ucan: Ucan, - isRevoked: (ucan: Ucan) => Promise + isRevoked: (ucan: Ucan) => Promise, ): AsyncIterable { let proofIndex = 0 - for await (const proof of token.validateProofs(ucan)) { + for await (const proof of token.validateProofs(plugins)(ucan)) { if (proof instanceof Error) { yield proof continue @@ -283,15 +286,15 @@ async function* capabilitiesFromDelegation( switch (capability.with.scheme.toLowerCase()) { case "my": continue // cannot be delegated, only introduced by parenthood. case "as": { - yield* handleAsDelegation(semantics, capability, ucan, proof, isRevoked) + yield* handleAsDelegation(plugins, semantics, capability, ucan, proof, isRevoked) break } case "prf": { - yield* handlePrfDelegation(semantics, capability, ucan, proof, proofIndex, isRevoked) + yield* handlePrfDelegation(plugins, semantics, capability, ucan, proof, proofIndex, isRevoked) break } default: { - yield* handleNormalDelegation(semantics, capability, ucan, proof, isRevoked) + yield* handleNormalDelegation(plugins, semantics, capability, ucan, proof, isRevoked) } } } catch (e) { @@ -313,11 +316,12 @@ async function* capabilitiesFromDelegation( async function* handleAsDelegation( + plugins: Plugins, semantics: DelegationSemantics, capability: Capability, ucan: Ucan, proof: Ucan, - isRevoked: (ucan: Ucan) => Promise + isRevoked: (ucan: Ucan) => Promise, ): AsyncIterable { const split = capability.with.hierPart.split(":") const scheme = split[ split.length - 1 ] @@ -326,7 +330,7 @@ async function* handleAsDelegation( ? SUPERUSER : { scheme, ability: capability.can } - for await (const delegationChain of delegationChains(semantics, proof, isRevoked)) { + for await (const delegationChain of delegationChains(plugins)(semantics, proof, isRevoked)) { if (delegationChain instanceof Error) { yield delegationChain continue @@ -352,12 +356,13 @@ async function* handleAsDelegation( async function* handlePrfDelegation( + plugins: Plugins, semantics: DelegationSemantics, capability: Capability, ucan: Ucan, proof: Ucan, proofIndex: number, - isRevoked: (ucan: Ucan) => Promise + isRevoked: (ucan: Ucan) => Promise, ): AsyncIterable { if ( capability.with.hierPart !== SUPERUSER @@ -367,7 +372,7 @@ async function* handlePrfDelegation( // we only process the delegation if proofIndex === 2 return } - for await (const delegationChain of delegationChains(semantics, proof, isRevoked)) { + for await (const delegationChain of delegationChains(plugins)(semantics, proof, isRevoked)) { if (delegationChain instanceof Error) { yield delegationChain continue @@ -385,13 +390,14 @@ async function* handlePrfDelegation( async function* handleNormalDelegation( + plugins: Plugins, semantics: DelegationSemantics, capability: Capability, ucan: Ucan, proof: Ucan, - isRevoked: (ucan: Ucan) => Promise + isRevoked: (ucan: Ucan) => Promise, ): AsyncIterable { - for await (const delegationChain of delegationChains(semantics, proof, isRevoked)) { + for await (const delegationChain of delegationChains(plugins)(semantics, proof, isRevoked)) { if (delegationChain instanceof Error) { yield delegationChain continue diff --git a/packages/core/src/builder.ts b/packages/core/src/builder.ts index a582869..bc5e0ae 100644 --- a/packages/core/src/builder.ts +++ b/packages/core/src/builder.ts @@ -1,5 +1,6 @@ import * as token from "./token.js" import * as util from "./util.js" +import Plugins from "./plugins.js" import { Keypair, Fact, UcanPayload, isKeypair, Ucan, DidableKey } from "./types.js" import { Capability, isCapability } from "./capability/index.js" @@ -40,6 +41,21 @@ function isCapabilityLookupCapableState(obj: unknown): obj is CapabilityLookupCa && util.hasProp(obj, "expiration") && typeof obj.expiration === "number" } + + /** + * Create an empty builder. + * Before finalising the builder, you need to at least call + * - `issuedBy` + * - `toAudience` and + * - `withLifetimeInSeconds` or `withExpiration`. + * To finalise the builder, call its `build` or `buildPayload` method. + */ + +export const createBuilder = (plugins: Plugins) => + (): Builder> => { + return new Builder(plugins, {}, { capabilities: [], facts: [], proofs: [], addNonce: false }) +} + /** * A builder API for UCANs. * @@ -59,26 +75,16 @@ function isCapabilityLookupCapableState(obj: unknown): obj is CapabilityLookupCa */ export class Builder> { + private plugins: Plugins private state: State // portion of the state that's required to be set before building private defaultable: DefaultableState // portion of the state that has sensible defaults - private constructor(state: State, defaultable: DefaultableState) { + constructor(plugins: Plugins, state: State, defaultable: DefaultableState) { + this.plugins = plugins this.state = state this.defaultable = defaultable } - /** - * Create an empty builder. - * Before finalising the builder, you need to at least call - * - `issuedBy` - * - `toAudience` and - * - `withLifetimeInSeconds` or `withExpiration`. - * To finalise the builder, call its `build` or `buildPayload` method. - */ - static create(): Builder> { - return new Builder({}, { capabilities: [], facts: [], proofs: [], addNonce: false }) - } - /** * @param issuer The issuer as a DID string ("did:key:..."). * @@ -88,7 +94,7 @@ export class Builder> { if (!isKeypair(issuer)) { throw new TypeError(`Expected a Keypair, but got ${issuer}`) } - return new Builder({ ...this.state, issuer }, this.defaultable) + return new Builder(this.plugins, { ...this.state, issuer }, this.defaultable) } /** @@ -103,7 +109,7 @@ export class Builder> { if (typeof audience !== "string") { throw new TypeError(`Expected audience DID as string, but got ${audience}`) } - return new Builder({ ...this.state, audience }, this.defaultable) + return new Builder(this.plugins, { ...this.state, audience }, this.defaultable) } /** @@ -130,7 +136,7 @@ export class Builder> { if (this.defaultable.notBefore != null && expiration < this.defaultable.notBefore) { throw new Error(`Can't set expiration to ${expiration} which is before 'notBefore': ${this.defaultable.notBefore}`) } - return new Builder({ ...this.state, expiration }, this.defaultable) + return new Builder(this.plugins, { ...this.state, expiration }, this.defaultable) } /** @@ -143,7 +149,7 @@ export class Builder> { if (util.hasProp(this.state, "expiration") && typeof this.state.expiration === "number" && this.state.expiration < notBeforeTimestamp) { throw new Error(`Can't set 'notBefore' to ${notBeforeTimestamp} which is after expiration: ${this.state.expiration}`) } - return new Builder(this.state, { ...this.defaultable, notBefore: notBeforeTimestamp }) + return new Builder(this.plugins, this.state, { ...this.defaultable, notBefore: notBeforeTimestamp }) } /** @@ -156,7 +162,7 @@ export class Builder> { if (!util.isRecord(fact) || facts.some(fct => !util.isRecord(fct))) { throw new TypeError(`Expected fact(s) to be a record, but got ${fact}`) } - return new Builder(this.state, { + return new Builder(this.plugins, this.state, { ...this.defaultable, facts: [ ...this.defaultable.facts, fact, ...facts ] }) @@ -166,7 +172,7 @@ export class Builder> { * Will ensure that the built UCAN includes a number used once. */ withNonce(): Builder { - return new Builder(this.state, { ...this.defaultable, addNonce: true }) + return new Builder(this.plugins, this.state, { ...this.defaultable, addNonce: true }) } /** @@ -178,7 +184,7 @@ export class Builder> { if (!isCapability(capability)) { throw new TypeError(`Expected capability, but got ${JSON.stringify(capability, null, " ")}`) } - return new Builder(this.state, { + return new Builder(this.plugins, this.state, { ...this.defaultable, capabilities: [ ...this.defaultable.capabilities, capability, ...capabilities ] }) @@ -221,7 +227,7 @@ export class Builder> { if (!capabilityCanBeDelegated(semantics, requiredCapability, proof)) { throw new Error(`Can't add capability to UCAN: Given proof doesn't give required rights to delegate.`) } - return new Builder(this.state, { + return new Builder(this.plugins, this.state, { ...this.defaultable, capabilities: [ ...this.defaultable.capabilities, requiredCapability ], proofs: this.defaultable.proofs.find(p => token.encode(p) === token.encode(ucan)) == null @@ -236,7 +242,7 @@ export class Builder> { if (result != null) { const ucan = result.ucan const ucanEncoded = token.encode(ucan) - return new Builder(this.state, { + return new Builder(this.plugins, this.state, { ...this.defaultable, capabilities: [ ...this.defaultable.capabilities, requiredCapability ], proofs: this.defaultable.proofs.find(proof => token.encode(proof) === ucanEncoded) == null @@ -282,7 +288,7 @@ export class Builder> { throw new Error(`Builder is missing one of the required properties before it can be built: issuer, audience and expiration.`) } const payload = this.buildPayload() - return await token.signWithKeypair(payload, this.state.issuer) + return await token.signWithKeypair(this.plugins)(payload, this.state.issuer) } } diff --git a/packages/core/src/capability/ability.ts b/packages/core/src/capability/ability.ts index 873c5b4..b3c8b70 100644 --- a/packages/core/src/capability/ability.ts +++ b/packages/core/src/capability/ability.ts @@ -1,6 +1,11 @@ import { Superuser, SUPERUSER } from "./super-user.js" import * as util from "../util.js" +// RE-EXPORTS + + +export { Superuser, SUPERUSER } + // 💎 diff --git a/packages/core/src/capability/index.ts b/packages/core/src/capability/index.ts index 148306d..b35504d 100644 --- a/packages/core/src/capability/index.ts +++ b/packages/core/src/capability/index.ts @@ -1,17 +1,16 @@ import * as ability from "./ability.js" import * as resourcePointer from "./resource-pointer.js" -import * as superUser from "./super-user.js" import * as util from "../util.js" import { Ability, isAbility } from "./ability.js" import { ResourcePointer, isResourcePointer } from "./resource-pointer.js" -import { Superuser, SUPERUSER } from "./super-user.js" +import { Superuser } from "./super-user.js" // RE-EXPORTS -export { ability, resourcePointer, superUser } +export { ability, resourcePointer } @@ -53,7 +52,7 @@ export function isEncodedCapability(obj: unknown): obj is EncodedCapability { export function as(did: string, resource: Superuser | string): Capability { return { with: resourcePointer.as(did, resource), - can: SUPERUSER + can: ability.SUPERUSER } } @@ -61,7 +60,7 @@ export function as(did: string, resource: Superuser | string): Capability { export function my(resource: Superuser | string): Capability { return { with: resourcePointer.my(resource), - can: SUPERUSER + can: ability.SUPERUSER } } diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index db0ee1e..429bae9 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -1,3 +1,10 @@ +import Plugins from "./plugins.js" +import * as token from "./token.js" +import * as verifyLib from "./verify.js" +import * as attenuation from "./attenuation.js" +import * as builder from "./builder.js" +import * as store from "./store.js" + export * from "./attenuation.js" export * from "./builder.js" export * from "./store.js" @@ -7,5 +14,32 @@ export * from "./verify.js" export * from "./plugins.js" export * as capability from "./capability/index.js" +export * as ability from "./capability/ability.js" + +export { Capability, EncodedCapability, isCapability } from "./capability/index.js" + +export const injectPlugins = (plugins: Plugins) => { + const build = token.build(plugins) + const sign = token.sign(plugins) + const signWithKeypair = token.signWithKeypair(plugins) + const validate = token.validate(plugins) + const validateProofs = token.validateProofs(plugins) + const verify = verifyLib.verify(plugins) + const createBuilder = builder.createBuilder(plugins) + const storeFromTokens = store.storeFromTokens(plugins) + const emptyStore = store.emptyStore(plugins) + const delegationChains = attenuation.delegationChains(plugins) -export { Capability, EncodedCapability, isCapability } from "./capability/index.js" \ No newline at end of file + return { + build, + sign, + signWithKeypair, + validate, + validateProofs, + verify, + createBuilder, + storeFromTokens, + emptyStore, + delegationChains, + } +} \ No newline at end of file diff --git a/packages/core/src/plugins.ts b/packages/core/src/plugins.ts index f04b8e4..e8de469 100644 --- a/packages/core/src/plugins.ts +++ b/packages/core/src/plugins.ts @@ -11,60 +11,53 @@ export type DidMethodPlugin = { verifySignature: (did: string, data: Uint8Array, sig: Uint8Array) => Promise } -export type Plugins = { - keys: DidKeyPlugin[] - methods: Record -} - -let plugins: Plugins | null = null +export class Plugins { -export const loadPlugins = (toLoad: Plugins): void => { - plugins = toLoad -} + constructor( + public keys: DidKeyPlugin[], + public methods: Record + ) {} -export const verifyIssuerAlg = (did: string, jwtAlg: string): boolean => { - if(plugins === null) { - throw new Error("No plugins loaded") - } - const didMethod = parseDidMethod(did) - if(didMethod === "key") { - const bytes = parsePrefixedBytes(did) - for (const keyPlugin of plugins.keys) { - if(hasPrefix(bytes, keyPlugin.prefix)) { - return jwtAlg === keyPlugin.jwtAlg + verifyIssuerAlg(did: string, jwtAlg: string): boolean { + const didMethod = parseDidMethod(did) + if(didMethod === "key") { + const bytes = parsePrefixedBytes(did) + for (const keyPlugin of this.keys) { + if(hasPrefix(bytes, keyPlugin.prefix)) { + return jwtAlg === keyPlugin.jwtAlg + } + } + } else { + const maybePlugin = this.methods[didMethod] + if(maybePlugin) { + return maybePlugin.checkJwtAlg(did, jwtAlg) } } - } else { - const maybePlugin = plugins.methods[didMethod] - if(maybePlugin) { - return maybePlugin.checkJwtAlg(did, jwtAlg) - } + throw new Error(`DID method not supported by plugins: ${did}`) } - throw new Error(`DID method not supported by plugins: ${did}`) -} -export const verifySignature = async (did: string, data: Uint8Array, sig: Uint8Array): Promise => { - if(plugins === null) { - throw new Error("No plugins loaded") - } - const didMethod = parseDidMethod(did) - if(didMethod === "key") { - const bytes = parsePrefixedBytes(did) - for (const keyPlugin of plugins.keys) { - if(hasPrefix(bytes, keyPlugin.prefix)) { - return keyPlugin.verifySignature(did, data, sig) + async verifySignature(did: string, data: Uint8Array, sig: Uint8Array): Promise { + const didMethod = parseDidMethod(did) + if(didMethod === "key") { + const bytes = parsePrefixedBytes(did) + for (const keyPlugin of this.keys) { + if(hasPrefix(bytes, keyPlugin.prefix)) { + return keyPlugin.verifySignature(did, data, sig) + } + } + } else { + const maybePlugin = this.methods[didMethod] + if (maybePlugin) { + return maybePlugin.verifySignature(did, data, sig) } } - } else { - const maybePlugin = plugins.methods[didMethod] - if (maybePlugin) { - return maybePlugin.verifySignature(did, data, sig) - } + throw new Error(`DID method not supported by plugins: ${did}`) } - throw new Error(`DID method not supported by plugins: ${did}`) } -export const hasPrefix = ( +export default Plugins + +const hasPrefix = ( prefixedKey: Uint8Array, prefix: Uint8Array ): boolean => { diff --git a/packages/core/src/store.ts b/packages/core/src/store.ts index aa9560d..509fa0e 100644 --- a/packages/core/src/store.ts +++ b/packages/core/src/store.ts @@ -1,4 +1,5 @@ import * as token from "./token.js" +import Plugins from "./plugins.js" import { capabilityCanBeDelegated, DelegationSemantics, DelegationChain, delegationChains, rootIssuer } from "./attenuation.js" import { Ucan } from "./types.js" import { Capability } from "./capability/index.js" @@ -11,25 +12,36 @@ export interface IndexByAudience { }> } +export const storeFromTokens = (plugins: Plugins) => + async ( + knownSemantics: DelegationSemantics, + tokens: Iterable | AsyncIterable, + ): Promise => { + const store = new Store(plugins, knownSemantics, {}) + for await (const encodedUcan of tokens) { + const ucan = await token.validate(plugins)(encodedUcan) + await store.add(ucan) + } + return store +} + +export const emptyStore = (plugins: Plugins) => + (knownSemantics: DelegationSemantics) => { + return new Store(plugins, knownSemantics, {}) +} + export class Store { + private plugins: Plugins private index: IndexByAudience private knownSemantics: DelegationSemantics - constructor(knownSemantics: DelegationSemantics, index: IndexByAudience) { + constructor(plugins: Plugins, knownSemantics: DelegationSemantics, index: IndexByAudience) { + this.plugins = plugins this.index = index this.knownSemantics = knownSemantics } - static async fromTokens(knownSemantics: DelegationSemantics, tokens: Iterable | AsyncIterable): Promise { - const store = new Store(knownSemantics, {}) - for await (const encodedUcan of tokens) { - const ucan = await token.validate(encodedUcan) - await store.add(ucan) - } - return store - } - async add(ucan: Ucan): Promise { const audience = ucan.payload.aud const byAudience = this.index[ audience ] ?? [] @@ -40,7 +52,7 @@ export class Store { } const chains = [] - for await (const delegationChain of delegationChains(this.knownSemantics, ucan)) { + for await (const delegationChain of delegationChains(this.plugins)(this.knownSemantics, ucan)) { if (delegationChain instanceof Error) { console.warn(`Delegation chain error while storing UCAN:`, delegationChain) continue diff --git a/packages/core/src/token.ts b/packages/core/src/token.ts index 9bad659..b5420ca 100644 --- a/packages/core/src/token.ts +++ b/packages/core/src/token.ts @@ -3,7 +3,7 @@ import * as uint8arrays from "uint8arrays" // @IMPORT import * as semver from "./semver.js" import * as capability from "./capability/index.js" import * as util from "./util.js" -import * as plugins from "./plugins.js" +import Plugins from "./plugins.js" import { Capability, isCapability, isEncodedCapability } from "./capability/index.js" import { Fact, Keypair, DidableKey } from "./types.js" @@ -43,28 +43,28 @@ const VERSION = { major: 0, minor: 8, patch: 1 } * `prf`, Proofs, nested tokens with equal or greater privileges. * */ -export async function build(params: { - // from/to - issuer: DidableKey - audience: string - - // capabilities - capabilities?: Array - - // time bounds - lifetimeInSeconds?: number // expiration overrides lifetimeInSeconds - expiration?: number - notBefore?: number - - // proofs / other info - facts?: Array - proofs?: Array - addNonce?: boolean -} -): Promise { +export const build = (plugins: Plugins) => + ( params: { + // from/to + issuer: DidableKey + audience: string + + // capabilities + capabilities?: Array + + // time bounds + lifetimeInSeconds?: number // expiration overrides lifetimeInSeconds + expiration?: number + notBefore?: number + + // proofs / other info + facts?: Array + proofs?: Array + addNonce?: boolean + }): Promise => { const keypair = params.issuer const payload = buildPayload({ ...params, issuer: keypair.did() }) - return signWithKeypair(payload, keypair) + return signWithKeypair(plugins)(payload, keypair) } /** @@ -124,11 +124,11 @@ export function buildPayload(params: { /** * Encloses a UCAN payload as to form a finalised UCAN. */ -export async function sign( - payload: UcanPayload, - jwtAlg: string, - signFn: (data: Uint8Array) => Promise -): Promise { +export const sign = (plugins: Plugins) => + async (payload: UcanPayload, + jwtAlg: string, + signFn: (data: Uint8Array) => Promise, + ): Promise => { const header: UcanHeader = { alg: jwtAlg, typ: TYPE, @@ -163,11 +163,11 @@ export async function sign( /** * `sign` with a `Keypair`. */ -export async function signWithKeypair( - payload: UcanPayload, - keypair: Keypair, -): Promise { - return sign( +export const signWithKeypair = (plugins: Plugins) => + ( payload: UcanPayload, + keypair: Keypair, + ): Promise => { + return sign(plugins)( payload, keypair.jwtAlg, data => keypair.sign(data), @@ -318,7 +318,8 @@ export interface ValidateOptions { * @returns the parsed & validated UCAN (one layer) * @throws Error if the UCAN is invalid */ -export async function validate(encodedUcan: string, opts?: Partial): Promise { +export const validate = (plugins: Plugins) => + async (encodedUcan: string, opts?: Partial): Promise => { const { checkIssuer = true, checkSignature = true, checkIsExpired = true, checkIsTooEarly = true } = opts ?? {} const { header, payload } = parse(encodedUcan) @@ -383,15 +384,16 @@ export interface ValidateProofsOptions extends ValidateOptions { * @return an async iterator of the given ucan's proofs parsed & validated, or an `Error` * for each proof that couldn't be validated or parsed. */ -export async function* validateProofs( - ucan: Ucan, - opts?: Partial -): AsyncIterable { + +export const validateProofs = (plugins: Plugins) => + async function* ( ucan: Ucan, + opts?: Partial + ): AsyncIterable { const { checkAddressing = true, checkTimeBoundsSubset = true, checkVersionMonotonic = true } = opts || {} for (const prf of ucan.payload.prf) { try { - const proof = await validate(prf, opts) + const proof = await validate(plugins)(prf, opts) if (checkAddressing && ucan.payload.iss !== proof.payload.aud) { throw new Error(`Invalid Proof: Issuer ${ucan.payload.iss} doesn't match parent's audience ${proof.payload.aud}`) diff --git a/packages/core/src/verify.ts b/packages/core/src/verify.ts index e6a5c1e..8797dfc 100644 --- a/packages/core/src/verify.ts +++ b/packages/core/src/verify.ts @@ -1,4 +1,5 @@ import * as token from "./token.js" +import Plugins from "./plugins.js" import { capabilityCanBeDelegated, DelegationSemantics, DelegationChain, delegationChains, equalCanDelegate, rootIssuer } from "./attenuation.js" import { Capability, isCapability } from "./capability/index.js" import { Fact, Ucan } from "./types.js" @@ -53,7 +54,8 @@ export interface VerifyOptions { * * @throws TypeError if the passed arguments don't match what is expected */ -export async function verify(ucan: string, options: VerifyOptions): Promise> { +export const verify = (plugins: Plugins) => + async (ucan: string, options: VerifyOptions): Promise> => { const { audience, requiredCapabilities } = options const semantics = options.semantics ?? equalCanDelegate const isRevoked = options.isRevoked ?? (async () => false) @@ -86,7 +88,7 @@ export async function verify(ucan: string, options: VerifyOptions): Promise { - beforeAll(loadTestPlugins) - it("works with a simple example", async () => { // alice -> bob, bob -> mallory // alice delegates access to sending email as her to bob // and bob delegates it further to mallory - const leafUcan = await token.build({ + const leafUcan = await ucans.build({ issuer: alice, audience: bob.did(), capabilities: [ emailCapability("alice@email.com") ] }) - const ucan = await token.build({ + const ucan = await ucans.build({ issuer: bob, audience: mallory.did(), capabilities: [ emailCapability("alice@email.com") ], - proofs: [ token.encode(leafUcan) ] + proofs: [ ucans.encode(leafUcan) ] }) expect(await all(emailCapabilities(ucan))).toEqual([ @@ -43,16 +40,16 @@ describe("attenuation.emailCapabilities", () => { // alice -> bob, bob -> mallory // alice delegates nothing to bob // and bob delegates his email to mallory - const leafUcan = await token.build({ + const leafUcan = await ucans.build({ issuer: alice, audience: bob.did(), }) - const ucan = await token.build({ + const ucan = await ucans.build({ issuer: bob, audience: mallory.did(), capabilities: [ emailCapability("bob@email.com") ], - proofs: [ token.encode(leafUcan) ] + proofs: [ ucans.encode(leafUcan) ] }) // we implicitly expect the originator to become bob @@ -66,26 +63,26 @@ describe("attenuation.emailCapabilities", () => { // alice -> mallory, bob -> mallory, mallory -> alice // both alice and bob delegate their email access to mallory // mallory then creates a UCAN with capability to send both - const leafUcanAlice = await token.build({ + const leafUcanAlice = await ucans.build({ issuer: alice, audience: mallory.did(), capabilities: [ emailCapability("alice@email.com") ] }) - const leafUcanBob = await token.build({ + const leafUcanBob = await ucans.build({ issuer: bob, audience: mallory.did(), capabilities: [ emailCapability("bob@email.com") ] }) - const ucan = await token.build({ + const ucan = await ucans.build({ issuer: mallory, audience: alice.did(), capabilities: [ emailCapability("alice@email.com"), emailCapability("bob@email.com") ], - proofs: [ token.encode(leafUcanAlice), token.encode(leafUcanBob) ] + proofs: [ ucans.encode(leafUcanAlice), ucans.encode(leafUcanBob) ] }) const chains = await all(emailCapabilities(ucan)) @@ -120,23 +117,23 @@ describe("attenuation.emailCapabilities", () => { const aliceEmail = emailCapability("alice@email.com") - const leafUcanAlice = await token.build({ + const leafUcanAlice = await ucans.build({ issuer: alice, audience: mallory.did(), capabilities: [ aliceEmail ] }) - const leafUcanBob = await token.build({ + const leafUcanBob = await ucans.build({ issuer: bob, audience: mallory.did(), capabilities: [ aliceEmail ] }) - const ucan = await token.build({ + const ucan = await ucans.build({ issuer: mallory, audience: alice.did(), capabilities: [ aliceEmail ], - proofs: [ token.encode(leafUcanAlice), token.encode(leafUcanBob) ] + proofs: [ ucans.encode(leafUcanAlice), ucans.encode(leafUcanBob) ] }) expect(await all(emailCapabilities(ucan))).toEqual([ diff --git a/packages/core/tests/builder.test.ts b/packages/core/tests/builder.test.ts index b4bec59..5205750 100644 --- a/packages/core/tests/builder.test.ts +++ b/packages/core/tests/builder.test.ts @@ -1,17 +1,12 @@ -import * as token from "../src/token" -import { Builder } from "../src/builder" import { emailCapability } from "./capability/email" import { wnfsCapability, wnfsPublicSemantics } from "./capability/wnfs" import { EMAIL_SEMANTICS } from "./capability/email" import { alice, bob, mallory } from "./fixtures" -import { delegationChains } from "../src/attenuation" import { first } from "../src/util" -import { loadTestPlugins } from "./setup.js" +import * as ucans from "./setup" describe("Builder", () => { - - beforeAll(loadTestPlugins) it("builds with a simple example", async () => { const fact1 = { test: true } @@ -21,7 +16,7 @@ describe("Builder", () => { const expiration = Math.floor(Date.now() / 1000) + 30 const notBefore = Math.floor(Date.now() / 1000) - 30 - const ucan = await Builder.create() + const ucan = await ucans.createBuilder() .issuedBy(alice) .toAudience(bob.did()) .withExpiration(expiration) @@ -41,7 +36,7 @@ describe("Builder", () => { }) it("builds with lifetimeInSeconds", async () => { - const payload = Builder.create() + const payload = ucans.createBuilder() .issuedBy(alice) .toAudience(bob.did()) .withLifetimeInSeconds(300) @@ -51,14 +46,14 @@ describe("Builder", () => { }) it("prevents duplicate proofs", async () => { - const ucan = await Builder.create() + const ucan = await ucans.createBuilder() .issuedBy(alice) .toAudience(bob.did()) .withLifetimeInSeconds(30) .claimCapability(wnfsCapability("alice.fission.name/public/", "SUPER_USER")) .build() - const publicCapability = await first(delegationChains(wnfsPublicSemantics, ucan)) + const publicCapability = await first(ucans.delegationChains(wnfsPublicSemantics, ucan)) if (publicCapability == null) { throw "no capabilities" @@ -68,7 +63,7 @@ describe("Builder", () => { throw publicCapability } - const payload = Builder.create() + const payload = ucans.createBuilder() .issuedBy(bob) .toAudience(mallory.did()) .withLifetimeInSeconds(30) @@ -76,31 +71,31 @@ describe("Builder", () => { .delegateCapability(wnfsCapability("alice.fission.name/public/Documents", "OVERWRITE"), publicCapability, wnfsPublicSemantics) .buildPayload() - expect(payload.prf).toEqual([ token.encode(ucan) ]) + expect(payload.prf).toEqual([ ucans.encode(ucan) ]) }) it("throws when it's not ready to be built", () => { expect(() => { - Builder.create() + ucans.createBuilder() .buildPayload() }).toThrow() // issuer missing expect(() => { - Builder.create() + ucans.createBuilder() .toAudience(bob.did()) .withLifetimeInSeconds(1) .buildPayload() }).toThrow() // audience missing expect(() => { - Builder.create() + ucans.createBuilder() .issuedBy(alice) .withLifetimeInSeconds(1) .buildPayload() }).toThrow() // expiration missing expect(() => { - Builder.create() + ucans.createBuilder() .issuedBy(alice) .toAudience(bob.did()) .buildPayload() @@ -108,14 +103,14 @@ describe("Builder", () => { }) it("throws when trying to delegate unproven capabilities", async () => { - const ucan = await Builder.create() + const ucan = await ucans.createBuilder() .issuedBy(alice) .toAudience(bob.did()) .withLifetimeInSeconds(30) .claimCapability(emailCapability("alice@email.com")) .build() - const delegationChain = await first(delegationChains(EMAIL_SEMANTICS, ucan)) + const delegationChain = await first(ucans.delegationChains(EMAIL_SEMANTICS, ucan)) if (delegationChain == null) { throw "no capabilities" @@ -126,7 +121,7 @@ describe("Builder", () => { } expect(() => { - Builder.create() + ucans.createBuilder() .issuedBy(bob) .toAudience(mallory.did()) .withLifetimeInSeconds(30) diff --git a/packages/core/tests/capability/email.ts b/packages/core/tests/capability/email.ts index 63ad868..eb09d94 100644 --- a/packages/core/tests/capability/email.ts +++ b/packages/core/tests/capability/email.ts @@ -1,10 +1,10 @@ import { Ucan } from "../../src/types" -import { DelegationSemantics, delegationChains, rootIssuer } from "../../src/attenuation" +import { DelegationSemantics } from "../../src/attenuation" import { Ability } from "../../src/capability/ability" import { Capability } from "../../src/capability" import { SUPERUSER } from "../../src/capability/super-user" import { ResourcePointer } from "../../src/capability/resource-pointer" - +import * as ucans from "../setup" // 🌸 @@ -70,12 +70,12 @@ export function emailCapability(emailAddress: string): Capability { export async function * emailCapabilities(ucan: Ucan): AsyncIterable<{ capability: EmailCapability; rootIssuer: string }> { - for await (const delegationChain of delegationChains(EMAIL_SEMANTICS, ucan)) { + for await (const delegationChain of ucans.delegationChains(EMAIL_SEMANTICS, ucan)) { if (delegationChain instanceof Error || "ownershipDID" in delegationChain) { continue } yield { - rootIssuer: rootIssuer(delegationChain), + rootIssuer: ucans.rootIssuer(delegationChain), capability: delegationChain.capability } } diff --git a/packages/core/tests/capability/wnfs.test.ts b/packages/core/tests/capability/wnfs.test.ts index 01888b6..c00fbb8 100644 --- a/packages/core/tests/capability/wnfs.test.ts +++ b/packages/core/tests/capability/wnfs.test.ts @@ -1,17 +1,13 @@ -import * as token from "../../src/token" import { Capability } from "../../src/capability" import { wnfsCapability, wnfsPrivateCapabilities, wnfsPublicCapabilities } from "./wnfs" import { alice, bob, mallory } from "../fixtures" -import { loadTestPlugins } from "../setup.js" import { all } from "../../src/util" - +import * as ucans from "../setup" describe("wnfs public capability", () => { - beforeAll(loadTestPlugins) - it("works with a simple example", async () => { const { ucan } = await makeSimpleDelegation( [ wnfsCapability("//boris.fission.name/public/Apps/", "OVERWRITE") ], @@ -70,8 +66,6 @@ describe("wnfs public capability", () => { describe("wnfs private capability", () => { - beforeAll(loadTestPlugins) - it("works with a simple example", async () => { const { ucan } = await makeSimpleDelegation( [ wnfsCapability("//boris.fission.name/private/abc", "OVERWRITE") ], @@ -195,17 +189,17 @@ describe("wnfs private capability", () => { * The arguments are the capabilities delegated in the first and second arrow, respectively. */ async function makeSimpleDelegation(aliceCapabilities: Capability[], bobCapabilities: Capability[]) { - const leaf = await token.build({ + const leaf = await ucans.build({ issuer: alice, audience: bob.did(), capabilities: aliceCapabilities }) - const ucan = await token.build({ + const ucan = await ucans.build({ issuer: bob, audience: mallory.did(), capabilities: bobCapabilities, - proofs: [ token.encode(leaf) ] + proofs: [ ucans.encode(leaf) ] }) return { leaf, ucan } @@ -220,23 +214,23 @@ async function makeSimpleDelegation(aliceCapabilities: Capability[], bobCapabili * the second argument are the capabilities delegated in the last arrow. */ async function makeComplexDelegation(proofs: { alice: Capability[]; bob: Capability[] }, final: Capability[]) { - const leafAlice = await token.build({ + const leafAlice = await ucans.build({ issuer: alice, audience: mallory.did(), capabilities: proofs.alice, }) - const leafBob = await token.build({ + const leafBob = await ucans.build({ issuer: bob, audience: mallory.did(), capabilities: proofs.bob, }) - const ucan = await token.build({ + const ucan = await ucans.build({ issuer: mallory, audience: alice.did(), capabilities: final, - proofs: [ token.encode(leafAlice), token.encode(leafBob) ], + proofs: [ ucans.encode(leafAlice), ucans.encode(leafBob) ], }) return { leafAlice, leafBob, ucan } diff --git a/packages/core/tests/capability/wnfs.ts b/packages/core/tests/capability/wnfs.ts index 68b189f..94f9b00 100644 --- a/packages/core/tests/capability/wnfs.ts +++ b/packages/core/tests/capability/wnfs.ts @@ -1,9 +1,10 @@ import { Ability, isAbility } from "../../src/capability/ability" import { Capability } from "../../src/capability" -import { DelegationSemantics, DelegatedCapability, DelegatedOwnership, delegationChains, rootIssuer } from "../../src/attenuation" +import { DelegationSemantics, DelegatedCapability, DelegatedOwnership, rootIssuer } from "../../src/attenuation" import { SUPERUSER } from "../../src/capability/super-user" import { Ucan } from "../../src/types" import { ResourcePointer } from "../../src/capability/resource-pointer" +import * as ucans from "../setup" export const WNFS_ABILITY_LEVELS = { @@ -118,7 +119,7 @@ export const wnfsPublicSemantics: DelegationSemantics = { } export async function * wnfsPublicCapabilities(ucan: Ucan) { - for await (const delegationChain of delegationChains(wnfsPublicSemantics, ucan)) { + for await (const delegationChain of ucans.delegationChains(wnfsPublicSemantics, ucan)) { if (delegationChain instanceof Error || "ownershipDID" in delegationChain) { continue } @@ -189,7 +190,7 @@ const wnfsPrivateSemantics: DelegationSemantics = { } export async function * wnfsPrivateCapabilities(ucan: Ucan) { - for await (const delegationChain of delegationChains(wnfsPrivateSemantics, ucan)) { + for await (const delegationChain of ucans.delegationChains(wnfsPrivateSemantics, ucan)) { if (delegationChain instanceof Error || "ownershipDID" in delegationChain) { continue } diff --git a/packages/core/tests/compatibility.test.ts b/packages/core/tests/compatibility.test.ts index 7030df4..4b23022 100644 --- a/packages/core/tests/compatibility.test.ts +++ b/packages/core/tests/compatibility.test.ts @@ -1,6 +1,5 @@ import * as uint8arrays from "uint8arrays" -import * as token from "../src/token" -import { loadTestPlugins } from "./setup.js" +import * as ucans from "./setup" const oldUcan = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsInVhdiI6IjEuMC4wIn0.eyJhdWQiOiJkaWQ6a2V5OnoxM1YzU29nMllhVUtoZEdDbWd4OVVadVcxbzFTaEZKWWM2RHZHWWU3TlR0Njg5Tm9MMXRrZUd3NGMydGFQa2dBdWloUjh0cmg2azg2VHRVaTNIR2ZrNEh1NDg3czNiTWY4V1MzWjJoU3VwRktiNmhnV3VwajFIRzhheUxRdDFmeWJSdThjTGdBMkNKanFRYm16YzRFOEFKU0tKeDNndVFYa2F4c3R2Um5RRGN1eDFkZzhVR1BRS3haN2lLeUFKWkFuQlcyWXJUM2o0TVQxdTJNcWZQWG9RYU01WFZQMk04clBFN0FCSEREOXdMbWlKdjkzUUFDRFR5MllnZkVSS3JualNWaTdFb3RNOFR3NHg3M1pNUXJEQnZRRW01Zm9tTWZVaTZVSmJUTmVaaldDTUJQYllNbXRKUDZQZlRpaWZYZG0zdXprVFg5NnExUkVFOExodkU2Rzg2cUR0Wjg5MzdFYUdXdXFpNkRHVDFvc2FRMUVnR3NFN3Jac2JSdDFLNnRXeTZpYktlNTlKZWtnTWFlNW9XNER2IiwiZXhwIjozMjc0NDE5MTQyMywiZmN0IjpbXSwiaXNzIjoiZGlkOmtleTp6MTNWM1NvZzJZYVVLaGRHQ21neDlVWnVXMW8xU2hGSlljNkR2R1llN05UdDY4OU5vTDJWanZBR2JXdTFrdmZWUWFyVTVWMXBTUnNjOWFwR2h2dDdaODJmUWg1QWE1NW41Zm0zZGs2SnFuTXczZGU4WG91dWZUV2Z1eHpEVkhrSFNGV0sxOW1SWWI4d205d1VwZkxtUWl4QVdtMndFWVZqU2dENEd6YzhVUDlDSjFxMkY4ZXlpVXViMThGbld4Y2djUWhqdXB3OTNxUlMzWDlXUDViemlSYjE4TTZ0Vm8zaUJ4ZUozb2lrRTNaa3RScEtTZDlkcHU5WWNXZFhoeDZDQmY5NTZ1UXhkTDZoTkppNmVMbmZ1eFY2NEhpZU1rZFVoTTJSeThRd3lqZjQ4ZnZWMVhFVU1zeEM5YWFjNEtCcGJONDJHR3U4UmFkRDU3cjZuMWFOc2IyTjU3RkNOYnFIMXVLdHhNTmVHZHJ2QWlUUGRzVjJBRmppczJvN243ajhMNW41YmJ4TFl4VThNVHB3QVphdFpkSiIsIm5iZiI6MTY0MDE5MTQ1NywicHJmIjoiZXlKaGJHY2lPaUpTVXpJMU5pSXNJblI1Y0NJNklrcFhWQ0lzSW5WaGRpSTZJakV1TUM0d0luMC5leUpoZFdRaU9pSmthV1E2YTJWNU9ub3hNMVl6VTI5bk1sbGhWVXRvWkVkRGJXZDRPVlZhZFZjeGJ6RlRhRVpLV1dNMlJIWkhXV1UzVGxSME5qZzVUbTlNTWxacWRrRkhZbGQxTVd0MlpsWlJZWEpWTlZZeGNGTlNjMk01WVhCSGFIWjBOMW80TW1aUmFEVkJZVFUxYmpWbWJUTmthelpLY1c1TmR6TmtaVGhZYjNWMVpsUlhablY0ZWtSV1NHdElVMFpYU3pFNWJWSlpZamgzYlRsM1ZYQm1URzFSYVhoQlYyMHlkMFZaVm1wVFowUTBSM3BqT0ZWUU9VTktNWEV5UmpobGVXbFZkV0l4T0VadVYzaGpaMk5SYUdwMWNIYzVNM0ZTVXpOWU9WZFFOV0o2YVZKaU1UaE5OblJXYnpOcFFuaGxTak52YVd0Rk0xcHJkRkp3UzFOa09XUndkVGxaWTFka1dHaDROa05DWmprMU5uVlJlR1JNTm1oT1NtazJaVXh1Wm5WNFZqWTBTR2xsVFd0a1ZXaE5NbEo1T0ZGM2VXcG1ORGhtZGxZeFdFVlZUWE40UXpsaFlXTTBTMEp3WWs0ME1rZEhkVGhTWVdSRU5UZHlObTR4WVU1ellqSk9OVGRHUTA1aWNVZ3hkVXQwZUUxT1pVZGtjblpCYVZSUVpITldNa0ZHYW1sek1tODNiamRxT0V3MWJqVmlZbmhNV1hoVk9FMVVjSGRCV21GMFdtUktJaXdpWlhod0lqb3pNamMwTkRFNU1UUXlNeXdpWm1OMElqcGJYU3dpYVhOeklqb2laR2xrT210bGVUcDZNVE5XTTFOdlp6SlpZVlZMYUdSSFEyMW5lRGxWV25WWE1XOHhVMmhHU2xsak5rUjJSMWxsTjA1VWREWTRPVTV2VERKaE5VcE9hMlI0VmpabWJYVm9WbU5SWkRkSVIycHhkRXBRYVc1WlZWQTRRMUp4Y21veVkyVm5hVTFyT1RKUlNIazJRbWRXT1hveVVGQnJWMkZZU0RkUlRsQmlRekphZEUxNWFXbGFjWGRLUkVOd05sZG9VbkZVUzJodVFtaENUbWQ1WkRkTFJuUTNjRkkyTkhCa1ZIQjZUbXRNUlZKNGFHNTNUVUZqZURKcVJGZFlOelpDVG5SS04xUTFWVXQ0TTIxcWRHWTBaak0wWjJwVGRUaHJkME5UY0V0alFuQTRWV2RwU0hkdllVSkhkREUxVkZjNVUzQlNXVkoxYUZKdk1tdEljVFZ5Y0ROTmRFSnFSa2QyVUdZeVRsTlpZbUUzTmxoSGJYcFhlVEZyZUZOelEySTVUSGhqTW5welEwdG1lSEF5ZUd0VVFqWmtPVVJDUlVwVE5sUnhXbFo1WkhKU05GWmFNVkE1ZFhJeGRGcHBlbk5qYWtWd1kzVlViV1EzV0VRemRYSjZVelpqY0RSdU1sZHdSbFZNYjNsMk5tOW5ibWxaZEVOSGFUVlVlbWxEY2pKT1FWRjNWMEZYY25CMldVMWllbVEyVmt0a2RUVmpaekZZUWxoTVZFNWhUQ0lzSW01aVppSTZNVFkwTURFNU1UTTJNeXdpY0hSaklqb2lVMVZRUlZKZlZWTkZVaUlzSW5Kell5STZJaW9pZlEuQ0k5SjlOLVhUZUxQNEM5WTktUl9TcEE1aE80dHdpNUQxNFpTR2lwUzdjNS1jTlJWTVItc285Z0JZMlQzSFNaTHFmQ2xyMEtlQVJicFk2TFBwSm1NRGQ1ODdvck1TVVRnMndqN043eUNVeksxSWhOazhQMkQ3RGVlSHNxQ1lsTVotdXpjMHBSbnFJb3dPTWl6MVFkbHZXaTZ0UHNxZkZVYnl4bEx1bXRHdjV1a1hqc1FZcmYzdko3aU5DMkJibWotMGhTV25wNTNBN01TQTllLWFXVGpLUWEwSkpXVVVhWG5XS19CNjRaa3NyTWRXdW5mVFNuSE9lR2o3MFRuSXhieVcxbFhodk5pcnhIUV90ZVlKZ2xIZTRBbldEQXdUa2dnaVotdkp0WUhsYnVwQkt4S1YtNm9OMTlXS3dUT3U3QnpPX2QyUHAtWVVyY1RSSS1KZ0F2NUpnIiwicHRjIjoiU1VQRVJfVVNFUiIsInJzYyI6IioifQ.CRLB4gBBHhnsbfUhLALiCfo6mHnHSlEUczyZsWhh9TNjv9UxdgvsSWQsehGIT4XR0jQeYZo2OhasEVaF-Gtt_qqtUQIrducKngd0qzmpfjVbicsQPKVdJjlcTwm9dqhLSEtL195El0oucLzYdqMEZMf-txEmyhCd_Q8CaNExhAnwN32v1salnO6vrAw33ZJID7ZaFmBleoGUXBHQwnkv9_m_P6Fh-UGIKjaOuNmBkGXGn-4irm-eXrne2OPZCoPjhiaf0xTONu4ROrQQYykG8CppvsSXeiylOFY11Ot0sdAlHGSlyZk1_chJ3ud17K9S-CKWK9NtqiMNcUdQGFnNQQ" @@ -10,11 +9,8 @@ const header = JSON.parse(uint8arrays.toString(uint8arrays.fromString(encodedHea const payload = JSON.parse(uint8arrays.toString(uint8arrays.fromString(encodedPayload, "base64url"))) describe("compatibility", () => { - - beforeAll(loadTestPlugins) - it("allows parsing UCANs with 'uav: 1.0.0' into 'ucv: 0.3.0'", async () => { - const ucan = await token.validate(oldUcan, { checkIsExpired: false, checkIsTooEarly: false, checkSignature: false }) + const ucan = await ucans.validate(oldUcan, { checkIsExpired: false, checkIsTooEarly: false, checkSignature: false }) expect(ucan).toEqual({ header: { alg: header.alg, // "RS256", diff --git a/packages/core/tests/setup.ts b/packages/core/tests/setup.ts index 937a9eb..afdabdf 100644 --- a/packages/core/tests/setup.ts +++ b/packages/core/tests/setup.ts @@ -1,6 +1,17 @@ +import * as core from "../src" import * as plugins from "@ucans/plugins" -import { loadPlugins } from "../src/plugins" -export const loadTestPlugins = () => { - loadPlugins(plugins.defaults) -} \ No newline at end of file +export * from "../src" + +const injected = core.injectPlugins(plugins.defaults) + +export const build = injected.build +export const sign = injected.sign +export const signWithKeypair = injected.signWithKeypair +export const validate = injected.validate +export const validateProofs = injected.validateProofs +export const verify = injected.verify +export const createBuilder = injected.createBuilder +export const storeFromTokens = injected.storeFromTokens +export const emptyStore = injected.emptyStore +export const delegationChains = injected.delegationChains \ No newline at end of file diff --git a/packages/core/tests/store.test.ts b/packages/core/tests/store.test.ts index 0e387a8..8e212a2 100644 --- a/packages/core/tests/store.test.ts +++ b/packages/core/tests/store.test.ts @@ -1,60 +1,53 @@ -import * as token from "../src/token" -import { Store } from "../src/store" -import { Builder } from "../src/builder" import { alice, bob, mallory } from "./fixtures" import { wnfsCapability, wnfsPublicSemantics } from "./capability/wnfs" import { Ucan } from "../src/types" -import { equalCanDelegate } from "../src/attenuation" import { all } from "../src/util" -import { loadTestPlugins } from "./setup.js" - +import * as ucans from "./setup" describe("Store.add", () => { - beforeAll(loadTestPlugins) - it("makes added items retrievable with findByAudience", async () => { - const ucan = await Builder.create() + const ucan = await ucans.createBuilder() .issuedBy(alice) .toAudience(bob.did()) .withLifetimeInSeconds(30) .build() - const encoded = token.encode(ucan) + const encoded = ucans.encode(ucan) - const store = await Store.fromTokens(equalCanDelegate, []) + const store = await ucans.storeFromTokens(ucans.equalCanDelegate, []) await store.add(ucan) - expect(encodeOrNull(store.findByAudience(ucan.payload.aud, find => token.encode(find) === encoded))).toEqual(encoded) + expect(encodeOrNull(store.findByAudience(ucan.payload.aud, find => ucans.encode(find) === encoded))).toEqual(encoded) }) it("makes added items retrievable with findByAudience among multiple others", async () => { - const ucan = await Builder.create() + const ucan = await ucans.createBuilder() .issuedBy(alice) .toAudience(bob.did()) .withLifetimeInSeconds(30) .build() - const ucan2 = await Builder.create() + const ucan2 = await ucans.createBuilder() .issuedBy(alice) .toAudience(mallory.did()) .withLifetimeInSeconds(30) .build() - const encoded = token.encode(ucan) - const store = await Store.fromTokens(equalCanDelegate, []) + const encoded = ucans.encode(ucan) + const store = await ucans.storeFromTokens(ucans.equalCanDelegate, []) await store.add(ucan2) await store.add(ucan) - expect(encodeOrNull(store.findByAudience(ucan.payload.aud, find => token.encode(find) === encoded))).toEqual(encoded) + expect(encodeOrNull(store.findByAudience(ucan.payload.aud, find => ucans.encode(find) === encoded))).toEqual(encoded) }) it("doesn't add items twice", async () => { - const ucan = await Builder.create() + const ucan = await ucans.createBuilder() .issuedBy(alice) .toAudience(bob.did()) .withLifetimeInSeconds(30) .build() - const store = await Store.fromTokens(equalCanDelegate, []) + const store = await ucans.storeFromTokens(ucans.equalCanDelegate, []) await store.add(ucan) await store.add(ucan) expect(store.getByAudience(ucan.payload.aud)).toEqual([ ucan ]) @@ -65,22 +58,22 @@ describe("Store.add", () => { describe("Store.findByAudience", () => { it("only returns ucans with given audience", async () => { - const ucanBob = await Builder.create() + const ucanBob = await ucans.createBuilder() .issuedBy(alice) .toAudience(bob.did()) .withLifetimeInSeconds(30) .build() - const ucanAlice = await Builder.create() + const ucanAlice = await ucans.createBuilder() .issuedBy(bob) .toAudience(alice.did()) .withLifetimeInSeconds(30) .build() - const store = await Store.fromTokens(equalCanDelegate, [ ucanBob, ucanAlice ].map(ucan => token.encode(ucan))) + const store = await ucans.storeFromTokens(ucans.equalCanDelegate, [ ucanBob, ucanAlice ].map(ucan => ucans.encode(ucan))) expect(store.findByAudience(mallory.did(), () => true)).toEqual(null) - expect(encodeOrNull(store.findByAudience(bob.did(), () => true))).toEqual(token.encode(ucanBob)) - expect(encodeOrNull(store.findByAudience(alice.did(), () => true))).toEqual(token.encode(ucanAlice)) + expect(encodeOrNull(store.findByAudience(bob.did(), () => true))).toEqual(ucans.encode(ucanBob)) + expect(encodeOrNull(store.findByAudience(alice.did(), () => true))).toEqual(ucans.encode(ucanAlice)) }) }) @@ -88,14 +81,14 @@ describe("Store.findByAudience", () => { describe("Store.findWithCapability", () => { it("finds ucans with more capabilities than the given", async () => { - const ucan = await Builder.create() + const ucan = await ucans.createBuilder() .issuedBy(alice) .toAudience(bob.did()) .withLifetimeInSeconds(30) .claimCapability(wnfsCapability("alice.fission.name/public/", "SUPER_USER")) .build() - const store = await Store.fromTokens(wnfsPublicSemantics, [ token.encode(ucan) ]) + const store = await ucans.storeFromTokens(wnfsPublicSemantics, [ ucans.encode(ucan) ]) const results = all(store.findWithCapability( bob.did(), @@ -107,24 +100,24 @@ describe("Store.findWithCapability", () => { throw "no capability" } - expect(encodeOrNull(results[0]?.ucan)).toEqual(token.encode(ucan)) + expect(encodeOrNull(results[0]?.ucan)).toEqual(ucans.encode(ucan)) }) it("reports an error if the capability can't be found with given audience", async () => { - const ucanBob = await Builder.create() + const ucanBob = await ucans.createBuilder() .issuedBy(alice) .toAudience(bob.did()) .withLifetimeInSeconds(30) .claimCapability(wnfsCapability("alice.fission.name/public/", "SUPER_USER")) .build() - const ucanAlice = await Builder.create() + const ucanAlice = await ucans.createBuilder() .issuedBy(alice) .toAudience(bob.did()) .withLifetimeInSeconds(30) .build() - const store = await Store.fromTokens(wnfsPublicSemantics, [ token.encode(ucanAlice), token.encode(ucanBob) ]) + const store = await ucans.storeFromTokens(wnfsPublicSemantics, [ ucans.encode(ucanAlice), ucans.encode(ucanBob) ]) const results = all(store.findWithCapability( alice.did(), @@ -141,5 +134,5 @@ function encodeOrNull(ucan: Ucan | null): string { if (ucan == null) { return "null" } - return token.encode(ucan) + return ucans.encode(ucan) } diff --git a/packages/core/tests/token.test.ts b/packages/core/tests/token.test.ts index b3fbb79..9ff27ac 100644 --- a/packages/core/tests/token.test.ts +++ b/packages/core/tests/token.test.ts @@ -1,20 +1,16 @@ import * as uint8arrays from "uint8arrays" - -import * as capability from "../src/capability" -import * as token from "../src/token" -import { loadTestPlugins } from "./setup.js" import { alice, bob } from "./fixtures" - +import * as ucans from "./setup" // COMPOSING describe("token.build", () => { - beforeAll(loadTestPlugins) + // beforeAll(loadTestPlugins) it("can build payloads without nbf", () => { - const payload = token.buildPayload({ + const payload = ucans.buildPayload({ issuer: alice.did(), audience: bob.did(), }) @@ -22,7 +18,7 @@ describe("token.build", () => { }) it("builds payloads that expire in the future", () => { - const payload = token.buildPayload({ + const payload = ucans.buildPayload({ issuer: alice.did(), audience: bob.did(), @@ -33,12 +29,12 @@ describe("token.build", () => { it("throws when enclosing tokens with an invalid key type", async () => { await expect(() => { - const payload = token.buildPayload({ + const payload = ucans.buildPayload({ issuer: alice.did(), audience: bob.did(), }) - return token.sign( + return ucans.sign( payload, "rsa", data => alice.sign(data) @@ -55,21 +51,19 @@ describe("token.build", () => { describe("token.encodePayload", () => { - beforeAll(loadTestPlugins) - it("encodes capabilities", () => { const encodedCaps = { with: "wnfs://boris.fission.name/public/photos/", can: "crud/DELETE" } - const payload = token.buildPayload({ + const payload = ucans.buildPayload({ issuer: alice.did(), audience: bob.did(), - capabilities: [ capability.parse(encodedCaps) ] + capabilities: [ ucans.capability.parse(encodedCaps) ] }) - const encoded = token.encodePayload(payload) + const encoded = ucans.encodePayload(payload) const decodedString = uint8arrays.toString( uint8arrays.fromString(encoded, "base64url"), "utf8" @@ -93,10 +87,8 @@ describe("token.encodePayload", () => { describe("token.validate", () => { - beforeAll(loadTestPlugins) - async function makeUcan() { - return await token.build({ + return await ucans.build({ audience: bob.did(), issuer: alice, capabilities: [ @@ -118,7 +110,7 @@ describe("token.validate", () => { it("round-trips with token.build", async () => { const ucan = await makeUcan() - const parsedUcan = await token.validate(token.encode(ucan)) + const parsedUcan = await ucans.validate(ucans.encode(ucan)) expect(parsedUcan).toBeDefined() }) @@ -128,8 +120,8 @@ describe("token.validate", () => { ...ucan.payload, aud: "fakeaudience" } - const badUcan = `${token.encodeHeader(ucan.header)}.${token.encodePayload(badPayload)}.${ucan.signature}` - await expect(() => token.validate(badUcan)).rejects.toBeDefined() + const badUcan = `${ucans.encodeHeader(ucan.header)}.${ucans.encodePayload(badPayload)}.${ucan.signature}` + await expect(() => ucans.validate(badUcan)).rejects.toBeDefined() }) it("throws with a bad issuer", async () => { @@ -138,8 +130,8 @@ describe("token.validate", () => { ...ucan.header, alg: "RS256" } - const badUcan = `${token.encodeHeader(badHeader)}.${token.encodePayload(ucan.payload)}.${ucan.signature}` - await expect(() => token.validate(badUcan)).rejects.toBeDefined() + const badUcan = `${ucans.encodeHeader(badHeader)}.${ucans.encodePayload(ucan.payload)}.${ucan.signature}` + await expect(() => ucans.validate(badUcan)).rejects.toBeDefined() }) it("identifies a ucan that is not active yet", async () => { @@ -152,7 +144,7 @@ describe("token.validate", () => { exp: 2637352774 } } - expect(token.isTooEarly(badUcan)).toBe(true) + expect(ucans.isTooEarly(badUcan)).toBe(true) }) it("identifies a ucan that has become active", async () => { @@ -165,6 +157,6 @@ describe("token.validate", () => { lifetimeInSeonds: 30 } } - expect(token.isTooEarly(activeUcan)).toBe(false) + expect(ucans.isTooEarly(activeUcan)).toBe(false) }) }) \ No newline at end of file diff --git a/packages/core/tests/verify.test.ts b/packages/core/tests/verify.test.ts index 7a1ad09..1764705 100644 --- a/packages/core/tests/verify.test.ts +++ b/packages/core/tests/verify.test.ts @@ -1,37 +1,29 @@ -import * as token from "../src/token" -import * as capability from "../src/capability" -import { SUPERUSER } from "../src/capability/super-user" -import { verify } from "../src/verify" import { emailCapability } from "./capability/email" import { alice, bob, mallory } from "./fixtures" -import { REDELEGATE } from "../src/capability/ability" -import { loadTestPlugins } from "./setup" - +import * as ucans from "./setup" describe("verify", () => { - beforeAll(loadTestPlugins) - async function aliceEmailDelegationExample(expiration?: number) { // alice -> bob, bob -> mallory // alice delegates access to sending email as her to bob // and bob delegates it further to mallory - const leafUcan = await token.build({ + const leafUcan = await ucans.build({ issuer: alice, audience: bob.did(), expiration, capabilities: [ emailCapability("alice@email.com") ] }) - const ucan = await token.build({ + const ucan = await ucans.build({ issuer: bob, audience: mallory.did(), expiration, capabilities: [ emailCapability("alice@email.com") ], - proofs: [ token.encode(leafUcan) ] + proofs: [ ucans.encode(leafUcan) ] }) - return token.encode(ucan) + return ucans.encode(ucan) } const alicesEmail = { @@ -42,7 +34,7 @@ describe("verify", () => { it("verifies a delegation chain", async () => { const ucan = await aliceEmailDelegationExample() - const result = await verify(ucan, { + const result = await ucans.verify(ucan, { audience: mallory.did(), requiredCapabilities: [ alicesEmail ] }) @@ -62,12 +54,12 @@ describe("verify", () => { it("rejects an invalid escalation", async () => { const ucan = await aliceEmailDelegationExample() - const result = await verify(ucan, { + const result = await ucans.verify(ucan, { audience: mallory.did(), requiredCapabilities: [ { capability: { ...emailCapability("alice@email.com"), - can: SUPERUSER, + can: ucans.ability.SUPERUSER, }, rootIssuer: alice.did() } ] @@ -79,7 +71,7 @@ describe("verify", () => { it("rejects for an invalid audience", async () => { const ucan = await aliceEmailDelegationExample() - const result = await verify(ucan, { + const result = await ucans.verify(ucan, { audience: bob.did(), requiredCapabilities: [ alicesEmail ] }) @@ -90,7 +82,7 @@ describe("verify", () => { it("rejects for an invalid rootIssuer", async () => { const ucan = await aliceEmailDelegationExample() - const result = await verify(ucan, { + const result = await ucans.verify(ucan, { audience: mallory.did(), requiredCapabilities: [ { capability: emailCapability("alice@email.com"), @@ -108,7 +100,7 @@ describe("verify", () => { // expiry is in the past const ucan = await aliceEmailDelegationExample(nowInSeconds - 60 * 60 * 24) - const result = await verify(ucan, { + const result = await ucans.verify(ucan, { audience: mallory.did(), requiredCapabilities: [ alicesEmail ] }) @@ -120,20 +112,20 @@ describe("verify", () => { // alice -> bob // alice delegates access to sending email as her to bob // and bob delegates it further to mallory - const leafUcan = await token.build({ + const leafUcan = await ucans.build({ issuer: alice, audience: bob.did(), capabilities: [ emailCapability("alice@email.com") ] }) - const ucan = await token.build({ + const ucan = await ucans.build({ issuer: bob, audience: mallory.did(), - capabilities: [ capability.prf(capability.superUser.SUPERUSER, REDELEGATE) ], - proofs: [ token.encode(leafUcan) ] + capabilities: [ ucans.capability.prf(ucans.ability.SUPERUSER, ucans.ability.REDELEGATE) ], + proofs: [ ucans.encode(leafUcan) ] }) - const result = await verify(token.encode(ucan), { + const result = await ucans.verify(ucans.encode(ucan), { audience: mallory.did(), requiredCapabilities: [ alicesEmail ] }) @@ -150,26 +142,26 @@ describe("verify", () => { // alice -> bob // alice delegates access to sending email as her to bob // and bob delegates it further to mallory - const leafUcanA = await token.build({ + const leafUcanA = await ucans.build({ issuer: alice, audience: bob.did(), capabilities: [ emailCapability("ignore-me@email.com") ] }) - const leafUcanB = await token.build({ + const leafUcanB = await ucans.build({ issuer: alice, audience: bob.did(), capabilities: [ emailCapability("alice@email.com") ] }) - const ucan = await token.build({ + const ucan = await ucans.build({ issuer: bob, audience: mallory.did(), - capabilities: [ capability.prf(1, REDELEGATE) ], - proofs: [ token.encode(leafUcanA), token.encode(leafUcanB) ] + capabilities: [ ucans.capability.prf(1, ucans.ability.REDELEGATE) ], + proofs: [ ucans.encode(leafUcanA), ucans.encode(leafUcanB) ] }) - const result = await verify(token.encode(ucan), { + const result = await ucans.verify(ucans.encode(ucan), { audience: mallory.did(), requiredCapabilities: [ alicesEmail ] }) @@ -183,26 +175,26 @@ describe("verify", () => { }) it("ignores other proofs not referred to by `prf:0`", async () => { - const leafUcanA = await token.build({ + const leafUcanA = await ucans.build({ issuer: alice, audience: bob.did(), capabilities: [ emailCapability("ignore-me@email.com") ] }) - const leafUcanB = await token.build({ + const leafUcanB = await ucans.build({ issuer: alice, audience: bob.did(), capabilities: [ emailCapability("alice@email.com") ] }) - const faultyUcan = await token.build({ + const faultyUcan = await ucans.build({ issuer: bob, audience: mallory.did(), - capabilities: [ capability.prf(0, REDELEGATE) ], - proofs: [ token.encode(leafUcanA), token.encode(leafUcanB) ] + capabilities: [ ucans.capability.prf(0, ucans.ability.REDELEGATE) ], + proofs: [ ucans.encode(leafUcanA), ucans.encode(leafUcanB) ] }) - const result = await verify(token.encode(faultyUcan), { + const result = await ucans.verify(ucans.encode(faultyUcan), { audience: mallory.did(), requiredCapabilities: [ alicesEmail ] }) @@ -214,20 +206,20 @@ describe("verify", () => { // alice -> bob // alice delegates access to sending email as her to bob // and bob delegates it further to mallory - const leafUcan = await token.build({ + const leafUcan = await ucans.build({ issuer: alice, audience: bob.did(), capabilities: [ emailCapability("invalid@email.com") ] }) - const ucan = await token.build({ + const ucan = await ucans.build({ issuer: bob, audience: mallory.did(), - capabilities: [ capability.prf(capability.superUser.SUPERUSER, REDELEGATE) ], - proofs: [ token.encode(leafUcan) ] + capabilities: [ ucans.capability.prf(ucans.ability.SUPERUSER, ucans.ability.REDELEGATE) ], + proofs: [ ucans.encode(leafUcan) ] }) - const result = await verify(token.encode(ucan), { + const result = await ucans.verify(ucans.encode(ucan), { audience: mallory.did(), requiredCapabilities: [ alicesEmail ] }) @@ -239,20 +231,20 @@ describe("verify", () => { // alice -> bob // alice delegates access to sending email as her to bob // and bob delegates it further to mallory - const leafUcan = await token.build({ + const leafUcan = await ucans.build({ issuer: alice, audience: bob.did(), - capabilities: [ capability.my(capability.superUser.SUPERUSER) ] + capabilities: [ ucans.capability.my(ucans.ability.SUPERUSER) ] }) - const ucan = await token.build({ + const ucan = await ucans.build({ issuer: bob, audience: mallory.did(), capabilities: [ emailCapability("alice@email.com") ], - proofs: [ token.encode(leafUcan) ] + proofs: [ ucans.encode(leafUcan) ] }) - const result = await verify(token.encode(ucan), { + const result = await ucans.verify(ucans.encode(ucan), { audience: mallory.did(), requiredCapabilities: [ alicesEmail ], }) @@ -268,27 +260,27 @@ describe("verify", () => { // alice -> bob, bob -> mallory, mallory -> "someone" // alice delegates access to sending email as her to bob // and bob delegates it further to mallory - const leafUcan = await token.build({ + const leafUcan = await ucans.build({ issuer: alice, audience: bob.did(), - capabilities: [ capability.my(capability.superUser.SUPERUSER) ] + capabilities: [ ucans.capability.my(ucans.ability.SUPERUSER) ] }) - const middleUcan = await token.build({ + const middleUcan = await ucans.build({ issuer: bob, audience: mallory.did(), - capabilities: [ capability.as(alice.did(), SUPERUSER) ], - proofs: [ token.encode(leafUcan) ] + capabilities: [ ucans.capability.as(alice.did(), ucans.ability.SUPERUSER) ], + proofs: [ ucans.encode(leafUcan) ] }) - const ucan = await token.build({ + const ucan = await ucans.build({ issuer: mallory, audience: "did:key:someone", capabilities: [ emailCapability("alice@email.com") ], - proofs: [ token.encode(middleUcan) ] + proofs: [ ucans.encode(middleUcan) ] }) - const result = await verify(token.encode(ucan), { + const result = await ucans.verify(ucans.encode(ucan), { audience: "did:key:someone", requiredCapabilities: [ alicesEmail ] }) @@ -304,20 +296,20 @@ describe("verify", () => { // alice -> bob // alice delegates access to sending email as her to bob // and bob delegates it further to mallory - const leafUcan = await token.build({ + const leafUcan = await ucans.build({ issuer: alice, audience: bob.did(), capabilities: [] }) - const ucan = await token.build({ + const ucan = await ucans.build({ issuer: bob, audience: mallory.did(), capabilities: [ emailCapability("alice@email.com") ], - proofs: [ token.encode(leafUcan) ] + proofs: [ ucans.encode(leafUcan) ] }) - const result = await verify(token.encode(ucan), { + const result = await ucans.verify(ucans.encode(ucan), { audience: mallory.did(), requiredCapabilities: [ alicesEmail ] }) @@ -329,20 +321,20 @@ describe("verify", () => { // alice -> bob // alice delegates access to sending email as her to bob // and bob delegates it further to mallory - const leafUcan = await token.build({ + const leafUcan = await ucans.build({ issuer: alice, audience: bob.did(), - capabilities: [ capability.as(bob.did(), SUPERUSER) ] + capabilities: [ ucans.capability.as(bob.did(), ucans.ability.SUPERUSER) ] }) - const ucan = await token.build({ + const ucan = await ucans.build({ issuer: bob, audience: mallory.did(), capabilities: [ emailCapability("alice@email.com") ], - proofs: [ token.encode(leafUcan) ] + proofs: [ ucans.encode(leafUcan) ] }) - const result = await verify(token.encode(ucan), { + const result = await ucans.verify(ucans.encode(ucan), { audience: mallory.did(), requiredCapabilities: [ alicesEmail ] }) diff --git a/packages/plugins/src/default-plugins.ts b/packages/plugins/src/default-plugins.ts index c5a6000..25d6a96 100644 --- a/packages/plugins/src/default-plugins.ts +++ b/packages/plugins/src/default-plugins.ts @@ -3,7 +3,7 @@ import { ed25519Plugin } from "./ed25519/plugin.js" import { p256Plugin } from "./p256/plugin.js" import { rsaPlugin, rsaOldPlugin } from "./rsa/plugin.js" -export const defaults: Plugins = { - keys: [ed25519Plugin, p256Plugin, rsaPlugin, rsaOldPlugin], - methods: {}, -} \ No newline at end of file +export const defaults = new Plugins( + [ed25519Plugin, p256Plugin, rsaPlugin, rsaOldPlugin], + {}, +) \ No newline at end of file diff --git a/packages/ucans/src/index.ts b/packages/ucans/src/index.ts index 6f22420..0036bab 100644 --- a/packages/ucans/src/index.ts +++ b/packages/ucans/src/index.ts @@ -1,7 +1,18 @@ -import { loadPlugins } from "@ucans/core" import * as plugins from "@ucans/plugins" - -loadPlugins(plugins.defaults) +import * as core from "@ucans/core" export * from "@ucans/core" -export * from "@ucans/plugins" \ No newline at end of file +export * from "@ucans/plugins" + +const injected = core.injectPlugins(plugins.defaults) + +export const build = injected.build +export const sign = injected.sign +export const signWithKeypair = injected.signWithKeypair +export const validate = injected.validate +export const validateProofs = injected.validateProofs +export const verify = injected.verify +export const createBuilder = injected.createBuilder +export const storeFromTokens = injected.storeFromTokens +export const emptyStore = injected.emptyStore +export const delegationChains = injected.delegationChains \ No newline at end of file From ce32e24667d320926a9686f846323d94bfc4444c Mon Sep 17 00:00:00 2001 From: dholms Date: Tue, 5 Jul 2022 14:07:08 -0500 Subject: [PATCH 14/25] add test for rsa --- packages/plugins/tests/rsa.test.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/packages/plugins/tests/rsa.test.ts b/packages/plugins/tests/rsa.test.ts index 1b4a486..e18f279 100644 --- a/packages/plugins/tests/rsa.test.ts +++ b/packages/plugins/tests/rsa.test.ts @@ -28,6 +28,14 @@ describe("rsa", () => { expect(isValid).toBeTruthy() }) + it("handles old RSA keys", () => { + const toDecode = "did:key:z13V3Sog2YaUKhdGCmgx9UZuW1o1ShFJYc6DvGYe7NTt689NoL2RtpVs65Zw899YrTN9WuxdEEDm54YxWuQHQvcKfkZwa8HTgokHxGDPEmNLhvh69zUMEP4zjuARQ3T8bMUumkSLGpxNe1bfQX624ef45GhWb3S9HM3gvAJ7Qftm8iqnDQVcxwKHjmkV4hveKMTix4bTRhieVHi1oqU4QCVy4QPWpAAympuCP9dAoJFxSP6TNBLY9vPKLazsg7XcFov6UuLWsEaxJ5SomCpDx181mEgW2qTug5oQbrJwExbD9CMgXHLVDE2QgLoQMmgsrPevX57dH715NXC2uY6vo2mYCzRY4KuDRUsrkuYCkewL8q2oK1BEDVvi3Sg8pbC9QYQ5mMiHf8uxiHxTAmPedv8" + const expectedKey = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAnzyis1ZjfNB0bBgKFMSvvkTtwlvBsaJq7S5wA+kzeVOVpVWwkWdVha4s38XM/pa/yr47av7+z3VTmvDRyAHcaT92whREFpLv9cj5lTeJSibyr/Mrm/YtjCZVWgaOYIhwrXwKLqPr/11inWsAkfIytvHWTxZYEcXLgAXFuUuaS3uF9gEiNQwzGTU1v0FqkqTBr4B8nW3HCN47XUu0t8Y0e+lf4s4OxQawWD79J9/5d3Ry0vbV3Am1FtGJiJvOwRsIfVChDpYStTcHTCMqtvWbV6L11BWkpzGXSW4Hv43qa+GSYOD2QU68Mb59oSk2OB+BtOLpJofmbGEGgvmwyCI9MwIDAQAB" + const publicKey = rsaCrypto.oldDidToPublicKey(toDecode) + const publicKeyB64 = uint8arrays.toString(publicKey, "base64pad") + expect(publicKeyB64).toEqual(expectedKey) + }) + }) describe("ASN", () => { From b60d09cf954002862ffb9bda467e6d79841a1211 Mon Sep 17 00:00:00 2001 From: dholms Date: Tue, 5 Jul 2022 14:10:39 -0500 Subject: [PATCH 15/25] minor naming chnage --- packages/core/src/index.ts | 2 +- packages/core/tests/setup.ts | 2 +- packages/ucans/src/index.ts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 429bae9..b4b717a 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -18,7 +18,7 @@ export * as ability from "./capability/ability.js" export { Capability, EncodedCapability, isCapability } from "./capability/index.js" -export const injectPlugins = (plugins: Plugins) => { +export const getPluginInjectedApi = (plugins: Plugins) => { const build = token.build(plugins) const sign = token.sign(plugins) const signWithKeypair = token.signWithKeypair(plugins) diff --git a/packages/core/tests/setup.ts b/packages/core/tests/setup.ts index afdabdf..950bfb2 100644 --- a/packages/core/tests/setup.ts +++ b/packages/core/tests/setup.ts @@ -3,7 +3,7 @@ import * as plugins from "@ucans/plugins" export * from "../src" -const injected = core.injectPlugins(plugins.defaults) +const injected = core.getPluginInjectedApi(plugins.defaults) export const build = injected.build export const sign = injected.sign diff --git a/packages/ucans/src/index.ts b/packages/ucans/src/index.ts index 0036bab..6bc6799 100644 --- a/packages/ucans/src/index.ts +++ b/packages/ucans/src/index.ts @@ -4,7 +4,7 @@ import * as core from "@ucans/core" export * from "@ucans/core" export * from "@ucans/plugins" -const injected = core.injectPlugins(plugins.defaults) +const injected = core.getPluginInjectedApi(plugins.defaults) export const build = injected.build export const sign = injected.sign From ccd4f34f2281ce57878c22199761caf4e79d7a49 Mon Sep 17 00:00:00 2001 From: dholms Date: Tue, 5 Jul 2022 14:17:36 -0500 Subject: [PATCH 16/25] setup -> lib --- packages/core/tests/attenuation.test.ts | 2 +- packages/core/tests/builder.test.ts | 2 +- packages/core/tests/capability/email.ts | 2 +- packages/core/tests/capability/wnfs.test.ts | 2 +- packages/core/tests/capability/wnfs.ts | 2 +- packages/core/tests/compatibility.test.ts | 2 +- packages/core/tests/{setup.ts => lib.ts} | 0 packages/core/tests/store.test.ts | 2 +- packages/core/tests/token.test.ts | 2 +- packages/core/tests/verify.test.ts | 2 +- 10 files changed, 9 insertions(+), 9 deletions(-) rename packages/core/tests/{setup.ts => lib.ts} (100%) diff --git a/packages/core/tests/attenuation.test.ts b/packages/core/tests/attenuation.test.ts index 23015f2..bbf2736 100644 --- a/packages/core/tests/attenuation.test.ts +++ b/packages/core/tests/attenuation.test.ts @@ -2,7 +2,7 @@ import { emailCapabilities, emailCapability } from "./capability/email" import { alice, bob, mallory } from "./fixtures" import { all } from "../src/util" -import * as ucans from "./setup" +import * as ucans from "./lib" describe("attenuation.emailCapabilities", () => { diff --git a/packages/core/tests/builder.test.ts b/packages/core/tests/builder.test.ts index 5205750..0ae84d9 100644 --- a/packages/core/tests/builder.test.ts +++ b/packages/core/tests/builder.test.ts @@ -3,7 +3,7 @@ import { wnfsCapability, wnfsPublicSemantics } from "./capability/wnfs" import { EMAIL_SEMANTICS } from "./capability/email" import { alice, bob, mallory } from "./fixtures" import { first } from "../src/util" -import * as ucans from "./setup" +import * as ucans from "./lib" describe("Builder", () => { diff --git a/packages/core/tests/capability/email.ts b/packages/core/tests/capability/email.ts index eb09d94..f305c38 100644 --- a/packages/core/tests/capability/email.ts +++ b/packages/core/tests/capability/email.ts @@ -4,7 +4,7 @@ import { Ability } from "../../src/capability/ability" import { Capability } from "../../src/capability" import { SUPERUSER } from "../../src/capability/super-user" import { ResourcePointer } from "../../src/capability/resource-pointer" -import * as ucans from "../setup" +import * as ucans from "../lib" // 🌸 diff --git a/packages/core/tests/capability/wnfs.test.ts b/packages/core/tests/capability/wnfs.test.ts index c00fbb8..68daeae 100644 --- a/packages/core/tests/capability/wnfs.test.ts +++ b/packages/core/tests/capability/wnfs.test.ts @@ -3,7 +3,7 @@ import { wnfsCapability, wnfsPrivateCapabilities, wnfsPublicCapabilities } from import { alice, bob, mallory } from "../fixtures" import { all } from "../../src/util" -import * as ucans from "../setup" +import * as ucans from "../lib" describe("wnfs public capability", () => { diff --git a/packages/core/tests/capability/wnfs.ts b/packages/core/tests/capability/wnfs.ts index 94f9b00..5013818 100644 --- a/packages/core/tests/capability/wnfs.ts +++ b/packages/core/tests/capability/wnfs.ts @@ -4,7 +4,7 @@ import { DelegationSemantics, DelegatedCapability, DelegatedOwnership, rootIssue import { SUPERUSER } from "../../src/capability/super-user" import { Ucan } from "../../src/types" import { ResourcePointer } from "../../src/capability/resource-pointer" -import * as ucans from "../setup" +import * as ucans from "../lib" export const WNFS_ABILITY_LEVELS = { diff --git a/packages/core/tests/compatibility.test.ts b/packages/core/tests/compatibility.test.ts index 4b23022..6ad7e3a 100644 --- a/packages/core/tests/compatibility.test.ts +++ b/packages/core/tests/compatibility.test.ts @@ -1,5 +1,5 @@ import * as uint8arrays from "uint8arrays" -import * as ucans from "./setup" +import * as ucans from "./lib" const oldUcan = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsInVhdiI6IjEuMC4wIn0.eyJhdWQiOiJkaWQ6a2V5OnoxM1YzU29nMllhVUtoZEdDbWd4OVVadVcxbzFTaEZKWWM2RHZHWWU3TlR0Njg5Tm9MMXRrZUd3NGMydGFQa2dBdWloUjh0cmg2azg2VHRVaTNIR2ZrNEh1NDg3czNiTWY4V1MzWjJoU3VwRktiNmhnV3VwajFIRzhheUxRdDFmeWJSdThjTGdBMkNKanFRYm16YzRFOEFKU0tKeDNndVFYa2F4c3R2Um5RRGN1eDFkZzhVR1BRS3haN2lLeUFKWkFuQlcyWXJUM2o0TVQxdTJNcWZQWG9RYU01WFZQMk04clBFN0FCSEREOXdMbWlKdjkzUUFDRFR5MllnZkVSS3JualNWaTdFb3RNOFR3NHg3M1pNUXJEQnZRRW01Zm9tTWZVaTZVSmJUTmVaaldDTUJQYllNbXRKUDZQZlRpaWZYZG0zdXprVFg5NnExUkVFOExodkU2Rzg2cUR0Wjg5MzdFYUdXdXFpNkRHVDFvc2FRMUVnR3NFN3Jac2JSdDFLNnRXeTZpYktlNTlKZWtnTWFlNW9XNER2IiwiZXhwIjozMjc0NDE5MTQyMywiZmN0IjpbXSwiaXNzIjoiZGlkOmtleTp6MTNWM1NvZzJZYVVLaGRHQ21neDlVWnVXMW8xU2hGSlljNkR2R1llN05UdDY4OU5vTDJWanZBR2JXdTFrdmZWUWFyVTVWMXBTUnNjOWFwR2h2dDdaODJmUWg1QWE1NW41Zm0zZGs2SnFuTXczZGU4WG91dWZUV2Z1eHpEVkhrSFNGV0sxOW1SWWI4d205d1VwZkxtUWl4QVdtMndFWVZqU2dENEd6YzhVUDlDSjFxMkY4ZXlpVXViMThGbld4Y2djUWhqdXB3OTNxUlMzWDlXUDViemlSYjE4TTZ0Vm8zaUJ4ZUozb2lrRTNaa3RScEtTZDlkcHU5WWNXZFhoeDZDQmY5NTZ1UXhkTDZoTkppNmVMbmZ1eFY2NEhpZU1rZFVoTTJSeThRd3lqZjQ4ZnZWMVhFVU1zeEM5YWFjNEtCcGJONDJHR3U4UmFkRDU3cjZuMWFOc2IyTjU3RkNOYnFIMXVLdHhNTmVHZHJ2QWlUUGRzVjJBRmppczJvN243ajhMNW41YmJ4TFl4VThNVHB3QVphdFpkSiIsIm5iZiI6MTY0MDE5MTQ1NywicHJmIjoiZXlKaGJHY2lPaUpTVXpJMU5pSXNJblI1Y0NJNklrcFhWQ0lzSW5WaGRpSTZJakV1TUM0d0luMC5leUpoZFdRaU9pSmthV1E2YTJWNU9ub3hNMVl6VTI5bk1sbGhWVXRvWkVkRGJXZDRPVlZhZFZjeGJ6RlRhRVpLV1dNMlJIWkhXV1UzVGxSME5qZzVUbTlNTWxacWRrRkhZbGQxTVd0MlpsWlJZWEpWTlZZeGNGTlNjMk01WVhCSGFIWjBOMW80TW1aUmFEVkJZVFUxYmpWbWJUTmthelpLY1c1TmR6TmtaVGhZYjNWMVpsUlhablY0ZWtSV1NHdElVMFpYU3pFNWJWSlpZamgzYlRsM1ZYQm1URzFSYVhoQlYyMHlkMFZaVm1wVFowUTBSM3BqT0ZWUU9VTktNWEV5UmpobGVXbFZkV0l4T0VadVYzaGpaMk5SYUdwMWNIYzVNM0ZTVXpOWU9WZFFOV0o2YVZKaU1UaE5OblJXYnpOcFFuaGxTak52YVd0Rk0xcHJkRkp3UzFOa09XUndkVGxaWTFka1dHaDROa05DWmprMU5uVlJlR1JNTm1oT1NtazJaVXh1Wm5WNFZqWTBTR2xsVFd0a1ZXaE5NbEo1T0ZGM2VXcG1ORGhtZGxZeFdFVlZUWE40UXpsaFlXTTBTMEp3WWs0ME1rZEhkVGhTWVdSRU5UZHlObTR4WVU1ellqSk9OVGRHUTA1aWNVZ3hkVXQwZUUxT1pVZGtjblpCYVZSUVpITldNa0ZHYW1sek1tODNiamRxT0V3MWJqVmlZbmhNV1hoVk9FMVVjSGRCV21GMFdtUktJaXdpWlhod0lqb3pNamMwTkRFNU1UUXlNeXdpWm1OMElqcGJYU3dpYVhOeklqb2laR2xrT210bGVUcDZNVE5XTTFOdlp6SlpZVlZMYUdSSFEyMW5lRGxWV25WWE1XOHhVMmhHU2xsak5rUjJSMWxsTjA1VWREWTRPVTV2VERKaE5VcE9hMlI0VmpabWJYVm9WbU5SWkRkSVIycHhkRXBRYVc1WlZWQTRRMUp4Y21veVkyVm5hVTFyT1RKUlNIazJRbWRXT1hveVVGQnJWMkZZU0RkUlRsQmlRekphZEUxNWFXbGFjWGRLUkVOd05sZG9VbkZVUzJodVFtaENUbWQ1WkRkTFJuUTNjRkkyTkhCa1ZIQjZUbXRNUlZKNGFHNTNUVUZqZURKcVJGZFlOelpDVG5SS04xUTFWVXQ0TTIxcWRHWTBaak0wWjJwVGRUaHJkME5UY0V0alFuQTRWV2RwU0hkdllVSkhkREUxVkZjNVUzQlNXVkoxYUZKdk1tdEljVFZ5Y0ROTmRFSnFSa2QyVUdZeVRsTlpZbUUzTmxoSGJYcFhlVEZyZUZOelEySTVUSGhqTW5welEwdG1lSEF5ZUd0VVFqWmtPVVJDUlVwVE5sUnhXbFo1WkhKU05GWmFNVkE1ZFhJeGRGcHBlbk5qYWtWd1kzVlViV1EzV0VRemRYSjZVelpqY0RSdU1sZHdSbFZNYjNsMk5tOW5ibWxaZEVOSGFUVlVlbWxEY2pKT1FWRjNWMEZYY25CMldVMWllbVEyVmt0a2RUVmpaekZZUWxoTVZFNWhUQ0lzSW01aVppSTZNVFkwTURFNU1UTTJNeXdpY0hSaklqb2lVMVZRUlZKZlZWTkZVaUlzSW5Kell5STZJaW9pZlEuQ0k5SjlOLVhUZUxQNEM5WTktUl9TcEE1aE80dHdpNUQxNFpTR2lwUzdjNS1jTlJWTVItc285Z0JZMlQzSFNaTHFmQ2xyMEtlQVJicFk2TFBwSm1NRGQ1ODdvck1TVVRnMndqN043eUNVeksxSWhOazhQMkQ3RGVlSHNxQ1lsTVotdXpjMHBSbnFJb3dPTWl6MVFkbHZXaTZ0UHNxZkZVYnl4bEx1bXRHdjV1a1hqc1FZcmYzdko3aU5DMkJibWotMGhTV25wNTNBN01TQTllLWFXVGpLUWEwSkpXVVVhWG5XS19CNjRaa3NyTWRXdW5mVFNuSE9lR2o3MFRuSXhieVcxbFhodk5pcnhIUV90ZVlKZ2xIZTRBbldEQXdUa2dnaVotdkp0WUhsYnVwQkt4S1YtNm9OMTlXS3dUT3U3QnpPX2QyUHAtWVVyY1RSSS1KZ0F2NUpnIiwicHRjIjoiU1VQRVJfVVNFUiIsInJzYyI6IioifQ.CRLB4gBBHhnsbfUhLALiCfo6mHnHSlEUczyZsWhh9TNjv9UxdgvsSWQsehGIT4XR0jQeYZo2OhasEVaF-Gtt_qqtUQIrducKngd0qzmpfjVbicsQPKVdJjlcTwm9dqhLSEtL195El0oucLzYdqMEZMf-txEmyhCd_Q8CaNExhAnwN32v1salnO6vrAw33ZJID7ZaFmBleoGUXBHQwnkv9_m_P6Fh-UGIKjaOuNmBkGXGn-4irm-eXrne2OPZCoPjhiaf0xTONu4ROrQQYykG8CppvsSXeiylOFY11Ot0sdAlHGSlyZk1_chJ3ud17K9S-CKWK9NtqiMNcUdQGFnNQQ" diff --git a/packages/core/tests/setup.ts b/packages/core/tests/lib.ts similarity index 100% rename from packages/core/tests/setup.ts rename to packages/core/tests/lib.ts diff --git a/packages/core/tests/store.test.ts b/packages/core/tests/store.test.ts index 8e212a2..3ebe1f7 100644 --- a/packages/core/tests/store.test.ts +++ b/packages/core/tests/store.test.ts @@ -2,7 +2,7 @@ import { alice, bob, mallory } from "./fixtures" import { wnfsCapability, wnfsPublicSemantics } from "./capability/wnfs" import { Ucan } from "../src/types" import { all } from "../src/util" -import * as ucans from "./setup" +import * as ucans from "./lib" describe("Store.add", () => { diff --git a/packages/core/tests/token.test.ts b/packages/core/tests/token.test.ts index 9ff27ac..68495c8 100644 --- a/packages/core/tests/token.test.ts +++ b/packages/core/tests/token.test.ts @@ -1,6 +1,6 @@ import * as uint8arrays from "uint8arrays" import { alice, bob } from "./fixtures" -import * as ucans from "./setup" +import * as ucans from "./lib" // COMPOSING diff --git a/packages/core/tests/verify.test.ts b/packages/core/tests/verify.test.ts index 1764705..f21ba57 100644 --- a/packages/core/tests/verify.test.ts +++ b/packages/core/tests/verify.test.ts @@ -1,6 +1,6 @@ import { emailCapability } from "./capability/email" import { alice, bob, mallory } from "./fixtures" -import * as ucans from "./setup" +import * as ucans from "./lib" describe("verify", () => { From 62d07c08e5a6cdf55168c165683363d470a59e03 Mon Sep 17 00:00:00 2001 From: dholms Date: Tue, 5 Jul 2022 15:22:42 -0500 Subject: [PATCH 17/25] Moved jest config to root --- .../core/jest.config.js => jest.config.ts | 2 +- package.json | 5 +- packages/core/jest.config.ts | 3 + packages/plugins/jest.config.js | 21 ----- packages/plugins/jest.config.ts | 3 + yarn.lock | 88 ++++++++++++++++++- 6 files changed, 97 insertions(+), 25 deletions(-) rename packages/core/jest.config.js => jest.config.ts (89%) create mode 100644 packages/core/jest.config.ts delete mode 100644 packages/plugins/jest.config.js create mode 100644 packages/plugins/jest.config.ts diff --git a/packages/core/jest.config.js b/jest.config.ts similarity index 89% rename from packages/core/jest.config.js rename to jest.config.ts index 1a5935b..7b69fbc 100644 --- a/packages/core/jest.config.js +++ b/jest.config.ts @@ -1,4 +1,4 @@ -module.exports = { // eslint-disable-line +export default { transform: { ".(ts|tsx)": "ts-jest" }, diff --git a/package.json b/package.json index 2139bad..d8a1bd8 100644 --- a/package.json +++ b/package.json @@ -15,5 +15,8 @@ "private": true, "workspaces": [ "packages/*" - ] + ], + "devDependencies": { + "ts-node": "^10.8.2" + } } diff --git a/packages/core/jest.config.ts b/packages/core/jest.config.ts new file mode 100644 index 0000000..66af5b0 --- /dev/null +++ b/packages/core/jest.config.ts @@ -0,0 +1,3 @@ +import baseConfig from "../../jest.config" + +export default baseConfig diff --git a/packages/plugins/jest.config.js b/packages/plugins/jest.config.js deleted file mode 100644 index 1a5935b..0000000 --- a/packages/plugins/jest.config.js +++ /dev/null @@ -1,21 +0,0 @@ -module.exports = { // eslint-disable-line - transform: { - ".(ts|tsx)": "ts-jest" - }, - testEnvironment: "node", - testRegex: "(/__tests__/.*|\\.(test|spec))\\.(ts|tsx|js)$", - moduleFileExtensions: [ - "ts", - "tsx", - "js" - ], - preset: "ts-jest/presets/default-esm", - globals: { - "ts-jest": { - useESM: true, - }, - }, - moduleNameMapper: { - "^(\\.{1,2}/.*)\\.js$": "$1", - }, -} diff --git a/packages/plugins/jest.config.ts b/packages/plugins/jest.config.ts new file mode 100644 index 0000000..66af5b0 --- /dev/null +++ b/packages/plugins/jest.config.ts @@ -0,0 +1,3 @@ +import baseConfig from "../../jest.config" + +export default baseConfig diff --git a/yarn.lock b/yarn.lock index a78c185..e46a095 100644 --- a/yarn.lock +++ b/yarn.lock @@ -284,6 +284,13 @@ resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39" integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw== +"@cspotcode/source-map-support@^0.8.0": + version "0.8.1" + resolved "https://registry.yarnpkg.com/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz#00629c35a688e05a88b1cda684fb9d5e73f000a1" + integrity sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw== + dependencies: + "@jridgewell/trace-mapping" "0.3.9" + "@eslint/eslintrc@^1.3.0": version "1.3.0" resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-1.3.0.tgz#29f92c30bb3e771e4a2048c95fa6855392dfac4f" @@ -530,6 +537,14 @@ resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz#add4c98d341472a289190b424efbdb096991bb24" integrity sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw== +"@jridgewell/trace-mapping@0.3.9": + version "0.3.9" + resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz#6534fd5933a53ba7cbf3a17615e273a0d1273ff9" + integrity sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ== + dependencies: + "@jridgewell/resolve-uri" "^3.0.3" + "@jridgewell/sourcemap-codec" "^1.4.10" + "@jridgewell/trace-mapping@^0.3.9": version "0.3.14" resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.14.tgz#b231a081d8f66796e475ad588a1ef473112701ed" @@ -626,6 +641,26 @@ resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-1.1.2.tgz#ccb91445360179a04e7fe6aff78c00ffc1eeaf82" integrity sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw== +"@tsconfig/node10@^1.0.7": + version "1.0.9" + resolved "https://registry.yarnpkg.com/@tsconfig/node10/-/node10-1.0.9.tgz#df4907fc07a886922637b15e02d4cebc4c0021b2" + integrity sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA== + +"@tsconfig/node12@^1.0.7": + version "1.0.11" + resolved "https://registry.yarnpkg.com/@tsconfig/node12/-/node12-1.0.11.tgz#ee3def1f27d9ed66dac6e46a295cffb0152e058d" + integrity sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag== + +"@tsconfig/node14@^1.0.0": + version "1.0.3" + resolved "https://registry.yarnpkg.com/@tsconfig/node14/-/node14-1.0.3.tgz#e4386316284f00b98435bf40f72f75a09dabf6c1" + integrity sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow== + +"@tsconfig/node16@^1.0.2": + version "1.0.3" + resolved "https://registry.yarnpkg.com/@tsconfig/node16/-/node16-1.0.3.tgz#472eaab5f15c1ffdd7f8628bd4c4f753995ec79e" + integrity sha512-yOlFc+7UtL/89t2ZhjPvvB/DeAr3r+Dq58IgzsFkOAvVC6NMJXmCGjbptdXdR9qsX7pKcTL+s87FtYREi2dEEQ== + "@types/babel__core@^7.0.0", "@types/babel__core@^7.1.14": version "7.1.19" resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.1.19.tgz#7b497495b7d1b4812bdb9d02804d0576f43ee460" @@ -833,12 +868,17 @@ acorn-walk@^7.1.1: resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-7.2.0.tgz#0de889a601203909b0fbe07b8938dc21d2e967bc" integrity sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA== +acorn-walk@^8.1.1: + version "8.2.0" + resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.2.0.tgz#741210f2e2426454508853a2f44d0ab83b7f69c1" + integrity sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA== + acorn@^7.1.1: version "7.4.1" resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.4.1.tgz#feaed255973d2e77555b83dbc08851a6c63520fa" integrity sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A== -acorn@^8.2.4, acorn@^8.7.1: +acorn@^8.2.4, acorn@^8.4.1, acorn@^8.7.1: version "8.7.1" resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.7.1.tgz#0197122c843d1bf6d0a5e83220a788f278f63c30" integrity sha512-Xx54uLJQZ19lKygFXOWsscKUbsBZW0CPykPhVQdhIeIwrbPmJzqeASDInc8nKBnp/JT6igTs82qPXz069H8I/A== @@ -899,6 +939,11 @@ anymatch@^3.0.3: normalize-path "^3.0.0" picomatch "^2.0.4" +arg@^4.1.0: + version "4.1.3" + resolved "https://registry.yarnpkg.com/arg/-/arg-4.1.3.tgz#269fc7ad5b8e42cb63c896d5666017261c144089" + integrity sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA== + argparse@^1.0.7: version "1.0.10" resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" @@ -1173,6 +1218,11 @@ core-util-is@~1.0.0: resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.3.tgz#a6042d3634c2b27e9328f837b965fac83808db85" integrity sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ== +create-require@^1.1.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/create-require/-/create-require-1.1.1.tgz#c1d7e8f1e5f6cfc9ff65f9cd352d37348756c333" + integrity sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ== + cross-spawn@^7.0.2, cross-spawn@^7.0.3: version "7.0.3" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" @@ -1250,6 +1300,11 @@ diff-sequences@^27.5.1: resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-27.5.1.tgz#eaecc0d327fd68c8d9672a1e64ab8dccb2ef5327" integrity sha512-k1gCAXAsNgLwEL+Y8Wvl+M6oEFj5bgazfZULpS5CneoPPXRaCCW7dm+q21Ky2VEE5X+VeRDBVg1Pcvvsr4TtNQ== +diff@^4.0.1: + version "4.0.2" + resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d" + integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A== + dir-glob@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-3.0.1.tgz#56dbf73d992a4a93ba1584f4534063fd2e41717f" @@ -2416,7 +2471,7 @@ make-dir@^3.0.0: dependencies: semver "^6.0.0" -make-error@1.x: +make-error@1.x, make-error@^1.1.1: version "1.3.6" resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.6.tgz#2eb2e37ea9b67c4891f684a1394799af484cf7a2" integrity sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw== @@ -3054,6 +3109,25 @@ ts-jest@^27.1.4: semver "7.x" yargs-parser "20.x" +ts-node@^10.8.2: + version "10.8.2" + resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-10.8.2.tgz#3185b75228cef116bf82ffe8762594f54b2a23f2" + integrity sha512-LYdGnoGddf1D6v8REPtIH+5iq/gTDuZqv2/UJUU7tKjuEU8xVZorBM+buCGNjj+pGEud+sOoM4CX3/YzINpENA== + dependencies: + "@cspotcode/source-map-support" "^0.8.0" + "@tsconfig/node10" "^1.0.7" + "@tsconfig/node12" "^1.0.7" + "@tsconfig/node14" "^1.0.0" + "@tsconfig/node16" "^1.0.2" + acorn "^8.4.1" + acorn-walk "^8.1.1" + arg "^4.1.0" + create-require "^1.1.0" + diff "^4.0.1" + make-error "^1.1.1" + v8-compile-cache-lib "^3.0.1" + yn "3.1.1" + tslib@^1.8.1: version "1.14.1" resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" @@ -3144,6 +3218,11 @@ util-deprecate@~1.0.1: resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw== +v8-compile-cache-lib@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz#6336e8d71965cb3d35a1bbb7868445a7c05264bf" + integrity sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg== + v8-compile-cache@^2.0.3: version "2.3.0" resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz#2de19618c66dc247dcfb6f99338035d8245a2cee" @@ -3298,3 +3377,8 @@ yarn@^1.22.18: version "1.22.19" resolved "https://registry.yarnpkg.com/yarn/-/yarn-1.22.19.tgz#4ba7fc5c6e704fce2066ecbfb0b0d8976fe62447" integrity sha512-/0V5q0WbslqnwP91tirOvldvYISzaqhClxzyUKXYxs07yUILIs5jx/k6CFe8bvKSkds5w+eiOqta39Wk3WxdcQ== + +yn@3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/yn/-/yn-3.1.1.tgz#1e87401a09d767c1d5eab26a6e4c185182d2eb50" + integrity sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q== From f8adc5075385f7b744b07597e8157d4eba562bdd Mon Sep 17 00:00:00 2001 From: dholms Date: Tue, 5 Jul 2022 15:45:16 -0500 Subject: [PATCH 18/25] v0.0.1-alpha --- packages/core/package.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/core/package.json b/packages/core/package.json index 795dca7..af3f8c2 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,6 +1,6 @@ { "name": "@ucans/core", - "version": "0.9.1", + "version": "0.0.1-alpha", "description": "Core UCAN implementation", "author": "Daniel Holmgren ", "repository": { @@ -25,8 +25,8 @@ "dist:types": "tsc --project ./dist/ --emitDeclarationOnly --declaration --declarationDir ./dist/types/", "lint": "eslint src/**/*.ts src/*.ts tests/**/*.ts tests/*.ts", "prepare": "yarn build", - "publish-alpha": "yarn publish --tag alpha", - "publish-stable": "yarn publish --tag latest", + "publish-alpha": "yarn publish --tag alpha --access public", + "publish-stable": "yarn publish --tag latest --access public", "test": "jest", "test:watch": "jest --watch" }, From e31325423a762d77e00671275be4d67c07044789 Mon Sep 17 00:00:00 2001 From: dholms Date: Tue, 5 Jul 2022 18:09:54 -0500 Subject: [PATCH 19/25] v0.0.1-alpha2 --- packages/core/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core/package.json b/packages/core/package.json index af3f8c2..34ca981 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,6 +1,6 @@ { "name": "@ucans/core", - "version": "0.0.1-alpha", + "version": "0.0.1-alpha2", "description": "Core UCAN implementation", "author": "Daniel Holmgren ", "repository": { From 91f1fb81919211b5f8d674447a8754a70db1ccec Mon Sep 17 00:00:00 2001 From: dholms Date: Mon, 11 Jul 2022 14:43:02 -0500 Subject: [PATCH 20/25] mkBuilder & mkState --- packages/core/src/builder.ts | 411 +++++++++++++++++------------------ packages/core/src/index.ts | 15 +- packages/core/src/store.ts | 150 ++++++------- packages/core/src/types.ts | 69 +++++- 4 files changed, 344 insertions(+), 301 deletions(-) diff --git a/packages/core/src/builder.ts b/packages/core/src/builder.ts index bc5e0ae..0510053 100644 --- a/packages/core/src/builder.ts +++ b/packages/core/src/builder.ts @@ -2,17 +2,11 @@ import * as token from "./token.js" import * as util from "./util.js" import Plugins from "./plugins.js" -import { Keypair, Fact, UcanPayload, isKeypair, Ucan, DidableKey } from "./types.js" +import { Fact, UcanPayload, isKeypair, Ucan, DidableKey, StoreI, BuilderI, BuildableState, CapabilityLookupCapableState, DefaultableState } from "./types.js" import { Capability, isCapability } from "./capability/index.js" import { capabilityCanBeDelegated, DelegationSemantics, DelegationChain } from "./attenuation.js" -import { Store } from "./store.js" -export interface BuildableState { - issuer: DidableKey - audience: string - expiration: number -} function isBuildableState(obj: unknown): obj is BuildableState { return util.isRecord(obj) @@ -21,40 +15,13 @@ function isBuildableState(obj: unknown): obj is BuildableState { && util.hasProp(obj, "expiration") && typeof obj.expiration === "number" } -interface DefaultableState { - capabilities: Capability[] - facts: Fact[] - proofs: Ucan[] - addNonce: boolean - notBefore?: number -} - -// the state neccessary for being able to lookup fitting capabilities in the UCAN store -export interface CapabilityLookupCapableState { - issuer: Keypair - expiration: number -} - function isCapabilityLookupCapableState(obj: unknown): obj is CapabilityLookupCapableState { return util.isRecord(obj) && util.hasProp(obj, "issuer") && isKeypair(obj.issuer) && util.hasProp(obj, "expiration") && typeof obj.expiration === "number" } - - /** - * Create an empty builder. - * Before finalising the builder, you need to at least call - * - `issuedBy` - * - `toAudience` and - * - `withLifetimeInSeconds` or `withExpiration`. - * To finalise the builder, call its `build` or `buildPayload` method. - */ - -export const createBuilder = (plugins: Plugins) => - (): Builder> => { - return new Builder(plugins, {}, { capabilities: [], facts: [], proofs: [], addNonce: false }) -} +type BuilderConstructor = new >(state: State, defaultable: DefaultableState) => BuilderI /** * A builder API for UCANs. @@ -73,222 +40,238 @@ export const createBuilder = (plugins: Plugins) => * .build() * ``` */ -export class Builder> { +const mkBuilderClass = (plugins: Plugins): BuilderConstructor => { - private plugins: Plugins - private state: State // portion of the state that's required to be set before building - private defaultable: DefaultableState // portion of the state that has sensible defaults + return class Builder> implements BuilderI { - constructor(plugins: Plugins, state: State, defaultable: DefaultableState) { - this.plugins = plugins - this.state = state - this.defaultable = defaultable - } + private state: State // portion of the state that's required to be set before building + private defaultable: DefaultableState // portion of the state that has sensible defaults - /** - * @param issuer The issuer as a DID string ("did:key:..."). - * - * The UCAN must be signed with the private key of the issuer to be valid. - */ - issuedBy(issuer: DidableKey): Builder { - if (!isKeypair(issuer)) { - throw new TypeError(`Expected a Keypair, but got ${issuer}`) + constructor(state: State, defaultable: DefaultableState) { + this.state = state + this.defaultable = defaultable } - return new Builder(this.plugins, { ...this.state, issuer }, this.defaultable) - } - /** - * @param audience The audience as a DID string ("did:key:..."). - * - * This is the identity this UCAN transfers rights to. - * It could e.g. be the DID of a service you're posting this UCAN as a JWT to, - * or it could be the DID of something that'll use this UCAN as a proof to - * continue the UCAN chain as an issuer. - */ - toAudience(audience: string): Builder { - if (typeof audience !== "string") { - throw new TypeError(`Expected audience DID as string, but got ${audience}`) + /** + * Create an empty builder. + * Before finalising the builder, you need to at least call + * - `issuedBy` + * - `toAudience` and + * - `withLifetimeInSeconds` or `withExpiration`. + * To finalise the builder, call its `build` or `buildPayload` method. + */ + static create(): Builder> { + return new Builder({}, { capabilities: [], facts: [], proofs: [], addNonce: false }) } - return new Builder(this.plugins, { ...this.state, audience }, this.defaultable) - } - /** - * @param seconds The number of seconds from the calltime of this function - * to set the expiry timestamp to. - */ - withLifetimeInSeconds(seconds: number): Builder { - if (typeof seconds !== "number") { - throw new TypeError(`Expected seconds as number, but got ${seconds}`) - } - if (!isFinite(seconds) || seconds <= 0) { - throw new TypeError(`Expected seconds to be a positive number, but got ${seconds}`) + /** + * @param issuer The issuer as a DID string ("did:key:..."). + * + * The UCAN must be signed with the private key of the issuer to be valid. + */ + issuedBy(issuer: DidableKey): Builder { + if (!isKeypair(issuer)) { + throw new TypeError(`Expected a Keypair, but got ${issuer}`) + } + return new Builder({ ...this.state, issuer }, this.defaultable) } - return this.withExpiration(Math.floor(Date.now() / 1000) + seconds) - } - /** - * @param expiration The UTCTime timestamp (in seconds) for when the UCAN should expire. - */ - withExpiration(expiration: number): Builder { - if (typeof expiration !== "number" || !isFinite(expiration)) { - throw new TypeError(`Expected expiration as number, but got ${expiration}`) - } - if (this.defaultable.notBefore != null && expiration < this.defaultable.notBefore) { - throw new Error(`Can't set expiration to ${expiration} which is before 'notBefore': ${this.defaultable.notBefore}`) + /** + * @param audience The audience as a DID string ("did:key:..."). + * + * This is the identity this UCAN transfers rights to. + * It could e.g. be the DID of a service you're posting this UCAN as a JWT to, + * or it could be the DID of something that'll use this UCAN as a proof to + * continue the UCAN chain as an issuer. + */ + toAudience(audience: string): Builder { + if (typeof audience !== "string") { + throw new TypeError(`Expected audience DID as string, but got ${audience}`) + } + return new Builder({ ...this.state, audience }, this.defaultable) } - return new Builder(this.plugins, { ...this.state, expiration }, this.defaultable) - } - /** - * @param notBeforeTimestamp The UTCTime timestamp (in seconds) of when the UCAN becomes active. - */ - withNotBefore(notBeforeTimestamp: number): Builder { - if (typeof notBeforeTimestamp !== "number" || !isFinite(notBeforeTimestamp)) { - throw new TypeError(`Expected notBeforeTimestamp as number, but got ${notBeforeTimestamp}`) - } - if (util.hasProp(this.state, "expiration") && typeof this.state.expiration === "number" && this.state.expiration < notBeforeTimestamp) { - throw new Error(`Can't set 'notBefore' to ${notBeforeTimestamp} which is after expiration: ${this.state.expiration}`) + /** + * @param seconds The number of seconds from the calltime of this function + * to set the expiry timestamp to. + */ + withLifetimeInSeconds(seconds: number): Builder { + if (typeof seconds !== "number") { + throw new TypeError(`Expected seconds as number, but got ${seconds}`) + } + if (!isFinite(seconds) || seconds <= 0) { + throw new TypeError(`Expected seconds to be a positive number, but got ${seconds}`) + } + return this.withExpiration(Math.floor(Date.now() / 1000) + seconds) } - return new Builder(this.plugins, this.state, { ...this.defaultable, notBefore: notBeforeTimestamp }) - } - /** - * @param fact Any fact or proof of knowledge in this UCAN as a record. - * @param facts Arbitrary more facts or proofs of knowledge. - */ - withFact(fact: Fact): Builder - withFact(fact: Fact, ...facts: Fact[]): Builder - withFact(fact: Fact, ...facts: Fact[]): Builder { - if (!util.isRecord(fact) || facts.some(fct => !util.isRecord(fct))) { - throw new TypeError(`Expected fact(s) to be a record, but got ${fact}`) + /** + * @param expiration The UTCTime timestamp (in seconds) for when the UCAN should expire. + */ + withExpiration(expiration: number): Builder { + if (typeof expiration !== "number" || !isFinite(expiration)) { + throw new TypeError(`Expected expiration as number, but got ${expiration}`) + } + if (this.defaultable.notBefore != null && expiration < this.defaultable.notBefore) { + throw new Error(`Can't set expiration to ${expiration} which is before 'notBefore': ${this.defaultable.notBefore}`) + } + return new Builder({ ...this.state, expiration }, this.defaultable) } - return new Builder(this.plugins, this.state, { - ...this.defaultable, - facts: [ ...this.defaultable.facts, fact, ...facts ] - }) - } - - /** - * Will ensure that the built UCAN includes a number used once. - */ - withNonce(): Builder { - return new Builder(this.plugins, this.state, { ...this.defaultable, addNonce: true }) - } - /** - * Claim capabilities 'by parenthood'. - */ - claimCapability(capability: Capability): Builder - claimCapability(capability: Capability, ...capabilities: Capability[]): Builder - claimCapability(capability: Capability, ...capabilities: Capability[]): Builder { - if (!isCapability(capability)) { - throw new TypeError(`Expected capability, but got ${JSON.stringify(capability, null, " ")}`) + /** + * @param notBeforeTimestamp The UTCTime timestamp (in seconds) of when the UCAN becomes active. + */ + withNotBefore(notBeforeTimestamp: number): Builder { + if (typeof notBeforeTimestamp !== "number" || !isFinite(notBeforeTimestamp)) { + throw new TypeError(`Expected notBeforeTimestamp as number, but got ${notBeforeTimestamp}`) + } + if (util.hasProp(this.state, "expiration") && typeof this.state.expiration === "number" && this.state.expiration < notBeforeTimestamp) { + throw new Error(`Can't set 'notBefore' to ${notBeforeTimestamp} which is after expiration: ${this.state.expiration}`) + } + return new Builder(this.state, { ...this.defaultable, notBefore: notBeforeTimestamp }) } - return new Builder(this.plugins, this.state, { - ...this.defaultable, - capabilities: [ ...this.defaultable.capabilities, capability, ...capabilities ] - }) - } - /** - * Delegate capabilities from a given proof to the audience of the UCAN you're building. - * - * @param semantics The rules for which delegations of capabilities are allowed. - * @param requiredCapability The capability you want to delegate. - * - * Then, one of - * @param proof The proof chain that grants the issuer of this UCAN at least the capabilities you want to delegate, or - * @param store The UCAN store in which to try to find a UCAN granting you enough capabilities to delegate given capabilities. - * - * @throws If given store can't provide a UCAN for delegating given capability - * @throws If given proof can't be used to delegate given capability - * @throws If the builder hasn't set an issuer and expiration yet - */ - delegateCapability(requiredCapability: Capability, store: Store): State extends CapabilityLookupCapableState ? Builder : never - delegateCapability(requiredCapability: Capability, proof: DelegationChain, semantics: DelegationSemantics): State extends CapabilityLookupCapableState ? Builder : never - delegateCapability(requiredCapability: Capability, storeOrProof: Store | DelegationChain, semantics?: DelegationSemantics): Builder { - if (!isCapability(requiredCapability)) { - throw new TypeError(`Expected 'requiredCapability' as a second argument, but got ${requiredCapability}`) - } - if (!isCapabilityLookupCapableState(this.state)) { - throw new Error(`Can't delegate capabilities without having these paramenters set in the builder: issuer and expiration.`) + /** + * @param fact Any fact or proof of knowledge in this UCAN as a record. + * @param facts Arbitrary more facts or proofs of knowledge. + */ + withFact(fact: Fact): Builder + withFact(fact: Fact, ...facts: Fact[]): Builder + withFact(fact: Fact, ...facts: Fact[]): Builder { + if (!util.isRecord(fact) || facts.some(fct => !util.isRecord(fct))) { + throw new TypeError(`Expected fact(s) to be a record, but got ${fact}`) + } + return new Builder(this.state, { + ...this.defaultable, + facts: [ ...this.defaultable.facts, fact, ...facts ] + }) } - function isProof(proof: Store | DelegationChain): proof is DelegationChain { - return util.hasProp(proof, "capability") || util.hasProp(proof, "ownershipDID") + /** + * Will ensure that the built UCAN includes a number used once. + */ + withNonce(): Builder { + return new Builder(this.state, { ...this.defaultable, addNonce: true }) } - if (isProof(storeOrProof)) { - if (semantics == null) { - throw new TypeError(`Expected 'semantics' as third argument if a 'proof' DelegationChain was passed as second.`) - } - const proof: DelegationChain = storeOrProof - const ucan = proof.ucan - if (!capabilityCanBeDelegated(semantics, requiredCapability, proof)) { - throw new Error(`Can't add capability to UCAN: Given proof doesn't give required rights to delegate.`) + /** + * Claim capabilities 'by parenthood'. + */ + claimCapability(capability: Capability): Builder + claimCapability(capability: Capability, ...capabilities: Capability[]): Builder + claimCapability(capability: Capability, ...capabilities: Capability[]): Builder { + if (!isCapability(capability)) { + throw new TypeError(`Expected capability, but got ${JSON.stringify(capability, null, " ")}`) } - return new Builder(this.plugins, this.state, { + return new Builder(this.state, { ...this.defaultable, - capabilities: [ ...this.defaultable.capabilities, requiredCapability ], - proofs: this.defaultable.proofs.find(p => token.encode(p) === token.encode(ucan)) == null - ? [ ...this.defaultable.proofs, ucan ] - : this.defaultable.proofs + capabilities: [ ...this.defaultable.capabilities, capability, ...capabilities ] }) - } else { - const store: Store = storeOrProof - const issuer = this.state.issuer.did() - // we look up a proof that has our issuer as an audience - const result = util.first(store.findWithCapability(issuer, requiredCapability, issuer)) - if (result != null) { - const ucan = result.ucan - const ucanEncoded = token.encode(ucan) - return new Builder(this.plugins, this.state, { + } + + /** + * Delegate capabilities from a given proof to the audience of the UCAN you're building. + * + * @param semantics The rules for which delegations of capabilities are allowed. + * @param requiredCapability The capability you want to delegate. + * + * Then, one of + * @param proof The proof chain that grants the issuer of this UCAN at least the capabilities you want to delegate, or + * @param store The UCAN store in which to try to find a UCAN granting you enough capabilities to delegate given capabilities. + * + * @throws If given store can't provide a UCAN for delegating given capability + * @throws If given proof can't be used to delegate given capability + * @throws If the builder hasn't set an issuer and expiration yet + */ + delegateCapability(requiredCapability: Capability, store: StoreI): State extends CapabilityLookupCapableState ? Builder : never + delegateCapability(requiredCapability: Capability, proof: DelegationChain, semantics: DelegationSemantics): State extends CapabilityLookupCapableState ? Builder : never + delegateCapability(requiredCapability: Capability, storeOrProof: StoreI | DelegationChain, semantics?: DelegationSemantics): Builder { + if (!isCapability(requiredCapability)) { + throw new TypeError(`Expected 'requiredCapability' as a second argument, but got ${requiredCapability}`) + } + if (!isCapabilityLookupCapableState(this.state)) { + throw new Error(`Can't delegate capabilities without having these paramenters set in the builder: issuer and expiration.`) + } + + function isProof(proof: StoreI | DelegationChain): proof is DelegationChain { + return util.hasProp(proof, "capability") || util.hasProp(proof, "ownershipDID") + } + + if (isProof(storeOrProof)) { + if (semantics == null) { + throw new TypeError(`Expected 'semantics' as third argument if a 'proof' DelegationChain was passed as second.`) + } + const proof: DelegationChain = storeOrProof + const ucan = proof.ucan + if (!capabilityCanBeDelegated(semantics, requiredCapability, proof)) { + throw new Error(`Can't add capability to UCAN: Given proof doesn't give required rights to delegate.`) + } + return new Builder(this.state, { ...this.defaultable, capabilities: [ ...this.defaultable.capabilities, requiredCapability ], - proofs: this.defaultable.proofs.find(proof => token.encode(proof) === ucanEncoded) == null + proofs: this.defaultable.proofs.find(p => token.encode(p) === token.encode(ucan)) == null ? [ ...this.defaultable.proofs, ucan ] : this.defaultable.proofs }) } else { - throw new Error(`Couldn't add capability to UCAN. Couldn't find anything providing this capability in given store.`) + const store: StoreI = storeOrProof + const issuer = this.state.issuer.did() + // we look up a proof that has our issuer as an audience + const result = util.first(store.findWithCapability(issuer, requiredCapability, issuer)) + if (result != null) { + const ucan = result.ucan + const ucanEncoded = token.encode(ucan) + return new Builder(this.state, { + ...this.defaultable, + capabilities: [ ...this.defaultable.capabilities, requiredCapability ], + proofs: this.defaultable.proofs.find(proof => token.encode(proof) === ucanEncoded) == null + ? [ ...this.defaultable.proofs, ucan ] + : this.defaultable.proofs + }) + } else { + throw new Error(`Couldn't add capability to UCAN. Couldn't find anything providing this capability in given store.`) + } } } - } - /** - * Build the UCAN body. This can be used if you want to sign the UCAN yourself afterwards. - */ - buildPayload(): State extends BuildableState ? UcanPayload : never - buildPayload(): UcanPayload { - if (!isBuildableState(this.state)) { - throw new Error(`Builder is missing one of the required properties before it can be built: issuer, audience and expiration.`) - } - return token.buildPayload({ - issuer: this.state.issuer.did(), - audience: this.state.audience, + /** + * Build the UCAN body. This can be used if you want to sign the UCAN yourself afterwards. + */ + buildPayload(): State extends BuildableState ? UcanPayload : never + buildPayload(): UcanPayload { + if (!isBuildableState(this.state)) { + throw new Error(`Builder is missing one of the required properties before it can be built: issuer, audience and expiration.`) + } + return token.buildPayload({ + issuer: this.state.issuer.did(), + audience: this.state.audience, - expiration: this.state.expiration, - notBefore: this.defaultable.notBefore, - addNonce: this.defaultable.addNonce, + expiration: this.state.expiration, + notBefore: this.defaultable.notBefore, + addNonce: this.defaultable.addNonce, - capabilities: this.defaultable.capabilities, - facts: this.defaultable.facts, - proofs: this.defaultable.proofs.map(proof => token.encode(proof)), - }) - } + capabilities: this.defaultable.capabilities, + facts: this.defaultable.facts, + proofs: this.defaultable.proofs.map(proof => token.encode(proof)), + }) + } - /** - * Finalize: Build and sign the UCAN. - * - * @throws If the builder hasn't yet been set an issuer, audience and expiration. - */ - async build(): Promise - async build(): Promise { - if (!isBuildableState(this.state)) { - throw new Error(`Builder is missing one of the required properties before it can be built: issuer, audience and expiration.`) + /** + * Finalize: Build and sign the UCAN. + * + * @throws If the builder hasn't yet been set an issuer, audience and expiration. + */ + async build(): Promise + async build(): Promise { + if (!isBuildableState(this.state)) { + throw new Error(`Builder is missing one of the required properties before it can be built: issuer, audience and expiration.`) + } + const payload = this.buildPayload() + return await token.signWithKeypair(plugins)(payload, this.state.issuer) } - const payload = this.buildPayload() - return await token.signWithKeypair(this.plugins)(payload, this.state.issuer) + } } + +export default mkBuilderClass \ No newline at end of file diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index b4b717a..4df5d2a 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -2,8 +2,8 @@ import Plugins from "./plugins.js" import * as token from "./token.js" import * as verifyLib from "./verify.js" import * as attenuation from "./attenuation.js" -import * as builder from "./builder.js" -import * as store from "./store.js" +import mkBuilderClass from "./builder.js" +import mkStoreClass from "./store.js" export * from "./attenuation.js" export * from "./builder.js" @@ -12,6 +12,7 @@ export * from "./token.js" export * from "./types.js" export * from "./verify.js" export * from "./plugins.js" +export * from "./util.js" export * as capability from "./capability/index.js" export * as ability from "./capability/ability.js" @@ -25,10 +26,9 @@ export const getPluginInjectedApi = (plugins: Plugins) => { const validate = token.validate(plugins) const validateProofs = token.validateProofs(plugins) const verify = verifyLib.verify(plugins) - const createBuilder = builder.createBuilder(plugins) - const storeFromTokens = store.storeFromTokens(plugins) - const emptyStore = store.emptyStore(plugins) const delegationChains = attenuation.delegationChains(plugins) + const Builder = mkBuilderClass(plugins) + const Store = mkStoreClass(plugins) return { build, @@ -37,9 +37,8 @@ export const getPluginInjectedApi = (plugins: Plugins) => { validate, validateProofs, verify, - createBuilder, - storeFromTokens, - emptyStore, delegationChains, + Builder, + Store } } \ No newline at end of file diff --git a/packages/core/src/store.ts b/packages/core/src/store.ts index 509fa0e..5e95a7c 100644 --- a/packages/core/src/store.ts +++ b/packages/core/src/store.ts @@ -1,104 +1,98 @@ import * as token from "./token.js" import Plugins from "./plugins.js" import { capabilityCanBeDelegated, DelegationSemantics, DelegationChain, delegationChains, rootIssuer } from "./attenuation.js" -import { Ucan } from "./types.js" +import { IndexByAudience, StoreI, Ucan } from "./types.js" import { Capability } from "./capability/index.js" +type StoreConstructor = new (knownSemantics: DelegationSemantics, index: IndexByAudience) => StoreI -export interface IndexByAudience { - [ audienceDID: string ]: Array<{ - processedUcan: Ucan - capabilities: DelegationChain[] - }> -} - -export const storeFromTokens = (plugins: Plugins) => - async ( - knownSemantics: DelegationSemantics, - tokens: Iterable | AsyncIterable, - ): Promise => { - const store = new Store(plugins, knownSemantics, {}) - for await (const encodedUcan of tokens) { - const ucan = await token.validate(plugins)(encodedUcan) - await store.add(ucan) - } - return store -} - -export const emptyStore = (plugins: Plugins) => - (knownSemantics: DelegationSemantics) => { - return new Store(plugins, knownSemantics, {}) -} - -export class Store { +const mkStoreClass = (plugins: Plugins): StoreConstructor => { + return class Store implements StoreI{ - private plugins: Plugins - private index: IndexByAudience - private knownSemantics: DelegationSemantics + private index: IndexByAudience + private knownSemantics: DelegationSemantics - constructor(plugins: Plugins, knownSemantics: DelegationSemantics, index: IndexByAudience) { - this.plugins = plugins - this.index = index - this.knownSemantics = knownSemantics - } + constructor(knownSemantics: DelegationSemantics, index: IndexByAudience) { + this.index = index + this.knownSemantics = knownSemantics + } - async add(ucan: Ucan): Promise { - const audience = ucan.payload.aud - const byAudience = this.index[ audience ] ?? [] - const encoded = token.encode(ucan) - - if (byAudience.find(stored => token.encode(stored.processedUcan) === encoded) != null) { - return + static empty(knownSemantics: DelegationSemantics): Store { + return new Store(knownSemantics, {}) } - const chains = [] - for await (const delegationChain of delegationChains(this.plugins)(this.knownSemantics, ucan)) { - if (delegationChain instanceof Error) { - console.warn(`Delegation chain error while storing UCAN:`, delegationChain) - continue + static async fromTokens( + knownSemantics: DelegationSemantics, + tokens: Iterable | AsyncIterable, + ): Promise { + const store = Store.empty(knownSemantics) + for await (const encodedUcan of tokens) { + const ucan = await token.validate(plugins)(encodedUcan) + await store.add(ucan) } - chains.push(delegationChain) + return store } - // Also do this *after* the all awaits to prevent races. - if (byAudience.find(stored => token.encode(stored.processedUcan) === encoded) != null) { - return - } + async add(ucan: Ucan): Promise { + const audience = ucan.payload.aud + const byAudience = this.index[ audience ] ?? [] + const encoded = token.encode(ucan) + + if (byAudience.find(stored => token.encode(stored.processedUcan) === encoded) != null) { + return + } - byAudience.push({ - processedUcan: ucan, - capabilities: chains - }) - this.index[ audience ] = byAudience - } + const chains = [] + for await (const delegationChain of delegationChains(plugins)(this.knownSemantics, ucan)) { + if (delegationChain instanceof Error) { + console.warn(`Delegation chain error while storing UCAN:`, delegationChain) + continue + } + chains.push(delegationChain) + } - getByAudience(audience: string): Ucan[] { - return (this.index[ audience ] ?? []).map(elem => elem.processedUcan) - } + // Also do this *after* the all awaits to prevent races. + if (byAudience.find(stored => token.encode(stored.processedUcan) === encoded) != null) { + return + } - findByAudience(audience: string, predicate: (ucan: Ucan) => boolean): Ucan | null { - return this.index[ audience ]?.find(elem => predicate(elem.processedUcan))?.processedUcan ?? null - } + byAudience.push({ + processedUcan: ucan, + capabilities: chains + }) + this.index[ audience ] = byAudience + } - *findWithCapability( - audience: string, - requiredCapability: Capability, - requiredIssuer: string, - ): Iterable { - const cache = this.index[ audience ] + getByAudience(audience: string): Ucan[] { + return (this.index[ audience ] ?? []).map(elem => elem.processedUcan) + } - if (cache == null) { - return + findByAudience(audience: string, predicate: (ucan: Ucan) => boolean): Ucan | null { + return this.index[ audience ]?.find(elem => predicate(elem.processedUcan))?.processedUcan ?? null } - for (const cacheElement of cache) { - for (const delegationChain of cacheElement.capabilities) { - if (capabilityCanBeDelegated(this.knownSemantics, requiredCapability, delegationChain) - && rootIssuer(delegationChain) === requiredIssuer) { - yield delegationChain + *findWithCapability( + audience: string, + requiredCapability: Capability, + requiredIssuer: string, + ): Iterable { + const cache = this.index[ audience ] + + if (cache == null) { + return + } + + for (const cacheElement of cache) { + for (const delegationChain of cacheElement.capabilities) { + if (capabilityCanBeDelegated(this.knownSemantics, requiredCapability, delegationChain) + && rootIssuer(delegationChain) === requiredIssuer) { + yield delegationChain + } } } } - } + } } + +export default mkStoreClass \ No newline at end of file diff --git a/packages/core/src/types.ts b/packages/core/src/types.ts index f0f85dd..63044a5 100644 --- a/packages/core/src/types.ts +++ b/packages/core/src/types.ts @@ -3,6 +3,7 @@ import { SemVer } from "./semver.js" import { SupportedEncodings } from "uint8arrays/util/bases.js" // @IMPORT import { Capability, isCapability, isEncodedCapability } from "./capability/index.js" import * as util from "./util.js" +import { DelegationChain, DelegationSemantics } from "./attenuation.js" // 💎 @@ -75,6 +76,72 @@ export interface DidableKey extends Didable, Keypair {} export type Encodings = SupportedEncodings +// STORE + + +export interface IndexByAudience { + [ audienceDID: string ]: Array<{ + processedUcan: Ucan + capabilities: DelegationChain[] + }> +} + +export interface StoreI { + add(ucan: Ucan): Promise + getByAudience(audience: string): Ucan[] + findByAudience(audience: string, predicate: (ucan: Ucan) => boolean): Ucan | null + findWithCapability( + audience: string, + requiredCapability: Capability, + requiredIssuer: string, + ): Iterable +} + +// BUILDER + +export interface BuildableState { + issuer: DidableKey + audience: string + expiration: number +} + + +export interface DefaultableState { + capabilities: Capability[] + facts: Fact[] + proofs: Ucan[] + addNonce: boolean + notBefore?: number +} + +// the state neccessary for being able to lookup fitting capabilities in the UCAN store +export interface CapabilityLookupCapableState { + issuer: Keypair + expiration: number +} + +export interface BuilderI> { + issuedBy(issuer: DidableKey): BuilderI + toAudience(audience: string): BuilderI + withLifetimeInSeconds(seconds: number): BuilderI + withExpiration(expiration: number): BuilderI + withNotBefore(notBeforeTimestamp: number): BuilderI + withFact(fact: Fact): BuilderI + withFact(fact: Fact, ...facts: Fact[]): BuilderI + withFact(fact: Fact, ...facts: Fact[]): BuilderI + withNonce(): BuilderI + claimCapability(capability: Capability): BuilderI + claimCapability(capability: Capability, ...capabilities: Capability[]): BuilderI + claimCapability(capability: Capability, ...capabilities: Capability[]): BuilderI + delegateCapability(requiredCapability: Capability, store: StoreI): State extends CapabilityLookupCapableState ? BuilderI : never + delegateCapability(requiredCapability: Capability, proof: DelegationChain, semantics: DelegationSemantics): State extends CapabilityLookupCapableState ? BuilderI : never + delegateCapability(requiredCapability: Capability, storeOrProof: StoreI | DelegationChain, semantics?: DelegationSemantics): BuilderI + buildPayload(): State extends BuildableState ? UcanPayload : never + buildPayload(): UcanPayload + build(): Promise + build(): Promise +} + // TYPE CHECKS @@ -102,4 +169,4 @@ export function isUcanPayload(obj: unknown): obj is UcanPayload { && util.hasProp(obj, "att") && Array.isArray(obj.att) && obj.att.every(a => isCapability(a) || isEncodedCapability(a)) && (!util.hasProp(obj, "fct") || Array.isArray(obj.fct) && obj.fct.every(util.isRecord)) && util.hasProp(obj, "prf") && Array.isArray(obj.prf) && obj.prf.every(str => typeof str === "string") -} +} \ No newline at end of file From 0e2d5502576eeb1e524b866e1539a2b35e4aa0c9 Mon Sep 17 00:00:00 2001 From: dholms Date: Mon, 11 Jul 2022 15:30:19 -0500 Subject: [PATCH 21/25] moved tests to ucans --- packages/core/package.json | 1 - packages/core/src/builder.ts | 6 +- packages/core/src/capability/index.ts | 2 +- packages/core/src/index.ts | 2 + packages/core/src/store.ts | 6 +- packages/core/tests/lib.ts | 17 - .../jest.config.ts | 0 .../{plugins => default-plugins}/package.json | 2 +- .../src/default-plugins.ts | 0 .../src/ed25519/crypto.ts | 0 .../src/ed25519/keypair.ts | 0 .../src/ed25519/plugin.ts | 0 .../{plugins => default-plugins}/src/index.ts | 0 .../src/p256/crypto.ts | 0 .../src/p256/keypair.ts | 0 .../src/p256/plugin.ts | 0 .../src/prefixes.ts | 0 .../src/rsa/crypto.ts | 0 .../src/rsa/keypair.ts | 0 .../src/rsa/plugin.ts | 0 .../{plugins => default-plugins}/src/types.ts | 0 .../{plugins => default-plugins}/src/util.ts | 0 .../tests/ecdsa.test.ts | 0 .../tests/ed25519.test.ts | 0 .../tests/rsa.test.ts | 0 .../tsconfig.eslint.json | 0 .../tsconfig.json | 3 +- packages/ucans/jest.config.ts | 3 + packages/ucans/package.json | 2 +- packages/ucans/src/index.ts | 11 +- .../{core => ucans}/tests/attenuation.test.ts | 4 +- .../{core => ucans}/tests/builder.test.ts | 24 +- .../{core => ucans}/tests/capability/email.ts | 19 +- .../tests/capability/wnfs.test.ts | 7 +- .../{core => ucans}/tests/capability/wnfs.ts | 24 +- .../tests/compatibility.test.ts | 2 +- packages/{core => ucans}/tests/fixtures.ts | 2 +- packages/{core => ucans}/tests/store.test.ts | 35 +- packages/{core => ucans}/tests/token.test.ts | 2 +- packages/{core => ucans}/tests/verify.test.ts | 16 +- packages/ucans/tsconfig.json | 3 +- yarn.lock | 473 +++++++++--------- 42 files changed, 335 insertions(+), 331 deletions(-) delete mode 100644 packages/core/tests/lib.ts rename packages/{plugins => default-plugins}/jest.config.ts (100%) rename packages/{plugins => default-plugins}/package.json (98%) rename packages/{plugins => default-plugins}/src/default-plugins.ts (100%) rename packages/{plugins => default-plugins}/src/ed25519/crypto.ts (100%) rename packages/{plugins => default-plugins}/src/ed25519/keypair.ts (100%) rename packages/{plugins => default-plugins}/src/ed25519/plugin.ts (100%) rename packages/{plugins => default-plugins}/src/index.ts (100%) rename packages/{plugins => default-plugins}/src/p256/crypto.ts (100%) rename packages/{plugins => default-plugins}/src/p256/keypair.ts (100%) rename packages/{plugins => default-plugins}/src/p256/plugin.ts (100%) rename packages/{plugins => default-plugins}/src/prefixes.ts (100%) rename packages/{plugins => default-plugins}/src/rsa/crypto.ts (100%) rename packages/{plugins => default-plugins}/src/rsa/keypair.ts (100%) rename packages/{plugins => default-plugins}/src/rsa/plugin.ts (100%) rename packages/{plugins => default-plugins}/src/types.ts (100%) rename packages/{plugins => default-plugins}/src/util.ts (100%) rename packages/{plugins => default-plugins}/tests/ecdsa.test.ts (100%) rename packages/{plugins => default-plugins}/tests/ed25519.test.ts (100%) rename packages/{plugins => default-plugins}/tests/rsa.test.ts (100%) rename packages/{plugins => default-plugins}/tsconfig.eslint.json (100%) rename packages/{plugins => default-plugins}/tsconfig.json (73%) create mode 100644 packages/ucans/jest.config.ts rename packages/{core => ucans}/tests/attenuation.test.ts (98%) rename packages/{core => ucans}/tests/builder.test.ts (89%) rename packages/{core => ucans}/tests/capability/email.ts (78%) rename packages/{core => ucans}/tests/capability/wnfs.test.ts (98%) rename packages/{core => ucans}/tests/capability/wnfs.ts (90%) rename packages/{core => ucans}/tests/compatibility.test.ts (99%) rename packages/{core => ucans}/tests/fixtures.ts (93%) rename packages/{core => ucans}/tests/store.test.ts (75%) rename packages/{core => ucans}/tests/token.test.ts (99%) rename packages/{core => ucans}/tests/verify.test.ts (94%) diff --git a/packages/core/package.json b/packages/core/package.json index 34ca981..8e53887 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -72,7 +72,6 @@ "@types/node": "^17.0.23", "@typescript-eslint/eslint-plugin": "^5.18.0", "@typescript-eslint/parser": "^5.18.0", - "@ucans/plugins": "*", "copyfiles": "^2.4.1", "eslint": "^8.12.0", "fast-check": "^2.24.0", diff --git a/packages/core/src/builder.ts b/packages/core/src/builder.ts index 0510053..0b121b3 100644 --- a/packages/core/src/builder.ts +++ b/packages/core/src/builder.ts @@ -21,7 +21,11 @@ function isCapabilityLookupCapableState(obj: unknown): obj is CapabilityLookupCa && util.hasProp(obj, "expiration") && typeof obj.expiration === "number" } -type BuilderConstructor = new >(state: State, defaultable: DefaultableState) => BuilderI +// type BuilderConstructor = new >(state: State, defaultable: DefaultableState) => BuilderI +type BuilderConstructor = { + new >(state: State, defaultable: DefaultableState): BuilderI + create(): BuilderI> +} /** * A builder API for UCANs. diff --git a/packages/core/src/capability/index.ts b/packages/core/src/capability/index.ts index b35504d..b315878 100644 --- a/packages/core/src/capability/index.ts +++ b/packages/core/src/capability/index.ts @@ -10,7 +10,7 @@ import { Superuser } from "./super-user.js" // RE-EXPORTS -export { ability, resourcePointer } +export { ability, resourcePointer, Ability, isAbility } diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 4df5d2a..694e36e 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -17,6 +17,8 @@ export * from "./util.js" export * as capability from "./capability/index.js" export * as ability from "./capability/ability.js" +export { ResourcePointer, isResourcePointer } from "./capability/resource-pointer.js" +export { Ability, isAbility, Superuser, SUPERUSER } from "./capability/ability.js" export { Capability, EncodedCapability, isCapability } from "./capability/index.js" export const getPluginInjectedApi = (plugins: Plugins) => { diff --git a/packages/core/src/store.ts b/packages/core/src/store.ts index 5e95a7c..1d62170 100644 --- a/packages/core/src/store.ts +++ b/packages/core/src/store.ts @@ -4,7 +4,11 @@ import { capabilityCanBeDelegated, DelegationSemantics, DelegationChain, delegat import { IndexByAudience, StoreI, Ucan } from "./types.js" import { Capability } from "./capability/index.js" -type StoreConstructor = new (knownSemantics: DelegationSemantics, index: IndexByAudience) => StoreI +type StoreConstructor = { + new (knownSemantics: DelegationSemantics, index: IndexByAudience): StoreI + empty(knownSemantics: DelegationSemantics): StoreI + fromTokens(knownSemantics: DelegationSemantics, tokens: Iterable | AsyncIterable): Promise +} const mkStoreClass = (plugins: Plugins): StoreConstructor => { return class Store implements StoreI{ diff --git a/packages/core/tests/lib.ts b/packages/core/tests/lib.ts deleted file mode 100644 index 950bfb2..0000000 --- a/packages/core/tests/lib.ts +++ /dev/null @@ -1,17 +0,0 @@ -import * as core from "../src" -import * as plugins from "@ucans/plugins" - -export * from "../src" - -const injected = core.getPluginInjectedApi(plugins.defaults) - -export const build = injected.build -export const sign = injected.sign -export const signWithKeypair = injected.signWithKeypair -export const validate = injected.validate -export const validateProofs = injected.validateProofs -export const verify = injected.verify -export const createBuilder = injected.createBuilder -export const storeFromTokens = injected.storeFromTokens -export const emptyStore = injected.emptyStore -export const delegationChains = injected.delegationChains \ No newline at end of file diff --git a/packages/plugins/jest.config.ts b/packages/default-plugins/jest.config.ts similarity index 100% rename from packages/plugins/jest.config.ts rename to packages/default-plugins/jest.config.ts diff --git a/packages/plugins/package.json b/packages/default-plugins/package.json similarity index 98% rename from packages/plugins/package.json rename to packages/default-plugins/package.json index ddd5fd1..189bafa 100644 --- a/packages/plugins/package.json +++ b/packages/default-plugins/package.json @@ -1,5 +1,5 @@ { - "name": "@ucans/plugins", + "name": "@ucans/default-plugins", "version": "0.9.1", "description": "Default UCAN plugin set", "author": "Daniel Holmgren ", diff --git a/packages/plugins/src/default-plugins.ts b/packages/default-plugins/src/default-plugins.ts similarity index 100% rename from packages/plugins/src/default-plugins.ts rename to packages/default-plugins/src/default-plugins.ts diff --git a/packages/plugins/src/ed25519/crypto.ts b/packages/default-plugins/src/ed25519/crypto.ts similarity index 100% rename from packages/plugins/src/ed25519/crypto.ts rename to packages/default-plugins/src/ed25519/crypto.ts diff --git a/packages/plugins/src/ed25519/keypair.ts b/packages/default-plugins/src/ed25519/keypair.ts similarity index 100% rename from packages/plugins/src/ed25519/keypair.ts rename to packages/default-plugins/src/ed25519/keypair.ts diff --git a/packages/plugins/src/ed25519/plugin.ts b/packages/default-plugins/src/ed25519/plugin.ts similarity index 100% rename from packages/plugins/src/ed25519/plugin.ts rename to packages/default-plugins/src/ed25519/plugin.ts diff --git a/packages/plugins/src/index.ts b/packages/default-plugins/src/index.ts similarity index 100% rename from packages/plugins/src/index.ts rename to packages/default-plugins/src/index.ts diff --git a/packages/plugins/src/p256/crypto.ts b/packages/default-plugins/src/p256/crypto.ts similarity index 100% rename from packages/plugins/src/p256/crypto.ts rename to packages/default-plugins/src/p256/crypto.ts diff --git a/packages/plugins/src/p256/keypair.ts b/packages/default-plugins/src/p256/keypair.ts similarity index 100% rename from packages/plugins/src/p256/keypair.ts rename to packages/default-plugins/src/p256/keypair.ts diff --git a/packages/plugins/src/p256/plugin.ts b/packages/default-plugins/src/p256/plugin.ts similarity index 100% rename from packages/plugins/src/p256/plugin.ts rename to packages/default-plugins/src/p256/plugin.ts diff --git a/packages/plugins/src/prefixes.ts b/packages/default-plugins/src/prefixes.ts similarity index 100% rename from packages/plugins/src/prefixes.ts rename to packages/default-plugins/src/prefixes.ts diff --git a/packages/plugins/src/rsa/crypto.ts b/packages/default-plugins/src/rsa/crypto.ts similarity index 100% rename from packages/plugins/src/rsa/crypto.ts rename to packages/default-plugins/src/rsa/crypto.ts diff --git a/packages/plugins/src/rsa/keypair.ts b/packages/default-plugins/src/rsa/keypair.ts similarity index 100% rename from packages/plugins/src/rsa/keypair.ts rename to packages/default-plugins/src/rsa/keypair.ts diff --git a/packages/plugins/src/rsa/plugin.ts b/packages/default-plugins/src/rsa/plugin.ts similarity index 100% rename from packages/plugins/src/rsa/plugin.ts rename to packages/default-plugins/src/rsa/plugin.ts diff --git a/packages/plugins/src/types.ts b/packages/default-plugins/src/types.ts similarity index 100% rename from packages/plugins/src/types.ts rename to packages/default-plugins/src/types.ts diff --git a/packages/plugins/src/util.ts b/packages/default-plugins/src/util.ts similarity index 100% rename from packages/plugins/src/util.ts rename to packages/default-plugins/src/util.ts diff --git a/packages/plugins/tests/ecdsa.test.ts b/packages/default-plugins/tests/ecdsa.test.ts similarity index 100% rename from packages/plugins/tests/ecdsa.test.ts rename to packages/default-plugins/tests/ecdsa.test.ts diff --git a/packages/plugins/tests/ed25519.test.ts b/packages/default-plugins/tests/ed25519.test.ts similarity index 100% rename from packages/plugins/tests/ed25519.test.ts rename to packages/default-plugins/tests/ed25519.test.ts diff --git a/packages/plugins/tests/rsa.test.ts b/packages/default-plugins/tests/rsa.test.ts similarity index 100% rename from packages/plugins/tests/rsa.test.ts rename to packages/default-plugins/tests/rsa.test.ts diff --git a/packages/plugins/tsconfig.eslint.json b/packages/default-plugins/tsconfig.eslint.json similarity index 100% rename from packages/plugins/tsconfig.eslint.json rename to packages/default-plugins/tsconfig.eslint.json diff --git a/packages/plugins/tsconfig.json b/packages/default-plugins/tsconfig.json similarity index 73% rename from packages/plugins/tsconfig.json rename to packages/default-plugins/tsconfig.json index b59f12b..c60fdc6 100644 --- a/packages/plugins/tsconfig.json +++ b/packages/default-plugins/tsconfig.json @@ -4,7 +4,8 @@ "strict": true, "moduleResolution": "node", "esModuleInterop": true, - "noFallthroughCasesInSwitch": true + "noFallthroughCasesInSwitch": true, + "allowJs": true }, "include": [ "src/**/*" diff --git a/packages/ucans/jest.config.ts b/packages/ucans/jest.config.ts new file mode 100644 index 0000000..66af5b0 --- /dev/null +++ b/packages/ucans/jest.config.ts @@ -0,0 +1,3 @@ +import baseConfig from "../../jest.config" + +export default baseConfig diff --git a/packages/ucans/package.json b/packages/ucans/package.json index 298d19e..67cc0c0 100644 --- a/packages/ucans/package.json +++ b/packages/ucans/package.json @@ -66,7 +66,7 @@ ], "dependencies": { "@ucans/core": "*", - "@ucans/plugins": "*" + "@ucans/default-plugins": "*" }, "devDependencies": { "@types/jest": "^27.4.1", diff --git a/packages/ucans/src/index.ts b/packages/ucans/src/index.ts index 6bc6799..35cda7e 100644 --- a/packages/ucans/src/index.ts +++ b/packages/ucans/src/index.ts @@ -1,8 +1,8 @@ -import * as plugins from "@ucans/plugins" +import * as plugins from "@ucans/default-plugins" import * as core from "@ucans/core" export * from "@ucans/core" -export * from "@ucans/plugins" +export * from "@ucans/default-plugins" const injected = core.getPluginInjectedApi(plugins.defaults) @@ -12,7 +12,6 @@ export const signWithKeypair = injected.signWithKeypair export const validate = injected.validate export const validateProofs = injected.validateProofs export const verify = injected.verify -export const createBuilder = injected.createBuilder -export const storeFromTokens = injected.storeFromTokens -export const emptyStore = injected.emptyStore -export const delegationChains = injected.delegationChains \ No newline at end of file +export const Builder = injected.Builder +export const Store = injected.Store +export const delegationChains = injected.delegationChains diff --git a/packages/core/tests/attenuation.test.ts b/packages/ucans/tests/attenuation.test.ts similarity index 98% rename from packages/core/tests/attenuation.test.ts rename to packages/ucans/tests/attenuation.test.ts index bbf2736..c529595 100644 --- a/packages/core/tests/attenuation.test.ts +++ b/packages/ucans/tests/attenuation.test.ts @@ -1,8 +1,8 @@ import { emailCapabilities, emailCapability } from "./capability/email" import { alice, bob, mallory } from "./fixtures" -import { all } from "../src/util" -import * as ucans from "./lib" +import { all } from "../src" +import * as ucans from "../src" describe("attenuation.emailCapabilities", () => { diff --git a/packages/core/tests/builder.test.ts b/packages/ucans/tests/builder.test.ts similarity index 89% rename from packages/core/tests/builder.test.ts rename to packages/ucans/tests/builder.test.ts index 0ae84d9..9070bc3 100644 --- a/packages/core/tests/builder.test.ts +++ b/packages/ucans/tests/builder.test.ts @@ -2,8 +2,8 @@ import { emailCapability } from "./capability/email" import { wnfsCapability, wnfsPublicSemantics } from "./capability/wnfs" import { EMAIL_SEMANTICS } from "./capability/email" import { alice, bob, mallory } from "./fixtures" -import { first } from "../src/util" -import * as ucans from "./lib" +import * as ucans from "../src" +import { first } from "../src" describe("Builder", () => { @@ -16,7 +16,7 @@ describe("Builder", () => { const expiration = Math.floor(Date.now() / 1000) + 30 const notBefore = Math.floor(Date.now() / 1000) - 30 - const ucan = await ucans.createBuilder() + const ucan = await ucans.Builder.create() .issuedBy(alice) .toAudience(bob.did()) .withExpiration(expiration) @@ -36,7 +36,7 @@ describe("Builder", () => { }) it("builds with lifetimeInSeconds", async () => { - const payload = ucans.createBuilder() + const payload = ucans.Builder.create() .issuedBy(alice) .toAudience(bob.did()) .withLifetimeInSeconds(300) @@ -46,7 +46,7 @@ describe("Builder", () => { }) it("prevents duplicate proofs", async () => { - const ucan = await ucans.createBuilder() + const ucan = await ucans.Builder.create() .issuedBy(alice) .toAudience(bob.did()) .withLifetimeInSeconds(30) @@ -63,7 +63,7 @@ describe("Builder", () => { throw publicCapability } - const payload = ucans.createBuilder() + const payload = ucans.Builder.create() .issuedBy(bob) .toAudience(mallory.did()) .withLifetimeInSeconds(30) @@ -76,26 +76,26 @@ describe("Builder", () => { it("throws when it's not ready to be built", () => { expect(() => { - ucans.createBuilder() + ucans.Builder.create() .buildPayload() }).toThrow() // issuer missing expect(() => { - ucans.createBuilder() + ucans.Builder.create() .toAudience(bob.did()) .withLifetimeInSeconds(1) .buildPayload() }).toThrow() // audience missing expect(() => { - ucans.createBuilder() + ucans.Builder.create() .issuedBy(alice) .withLifetimeInSeconds(1) .buildPayload() }).toThrow() // expiration missing expect(() => { - ucans.createBuilder() + ucans.Builder.create() .issuedBy(alice) .toAudience(bob.did()) .buildPayload() @@ -103,7 +103,7 @@ describe("Builder", () => { }) it("throws when trying to delegate unproven capabilities", async () => { - const ucan = await ucans.createBuilder() + const ucan = await ucans.Builder.create() .issuedBy(alice) .toAudience(bob.did()) .withLifetimeInSeconds(30) @@ -121,7 +121,7 @@ describe("Builder", () => { } expect(() => { - ucans.createBuilder() + ucans.Builder.create() .issuedBy(bob) .toAudience(mallory.did()) .withLifetimeInSeconds(30) diff --git a/packages/core/tests/capability/email.ts b/packages/ucans/tests/capability/email.ts similarity index 78% rename from packages/core/tests/capability/email.ts rename to packages/ucans/tests/capability/email.ts index f305c38..3537589 100644 --- a/packages/core/tests/capability/email.ts +++ b/packages/ucans/tests/capability/email.ts @@ -1,10 +1,11 @@ -import { Ucan } from "../../src/types" -import { DelegationSemantics } from "../../src/attenuation" -import { Ability } from "../../src/capability/ability" -import { Capability } from "../../src/capability" -import { SUPERUSER } from "../../src/capability/super-user" -import { ResourcePointer } from "../../src/capability/resource-pointer" -import * as ucans from "../lib" +import * as ucans from "../../src" +import { + Ucan, + DelegationSemantics, + Ability, + Capability, + ResourcePointer +} from "../../src" // 🌸 @@ -35,10 +36,10 @@ export const EMAIL_SEMANTICS: DelegationSemantics = { }, canDelegateAbility(parentAbility, ability) { - if (parentAbility === SUPERUSER) { + if (parentAbility === ucans.SUPERUSER) { return true } - if (ability === SUPERUSER) { + if (ability === ucans.SUPERUSER) { return false } return parentAbility.namespace === "msg" diff --git a/packages/core/tests/capability/wnfs.test.ts b/packages/ucans/tests/capability/wnfs.test.ts similarity index 98% rename from packages/core/tests/capability/wnfs.test.ts rename to packages/ucans/tests/capability/wnfs.test.ts index 68daeae..61d030a 100644 --- a/packages/core/tests/capability/wnfs.test.ts +++ b/packages/ucans/tests/capability/wnfs.test.ts @@ -1,9 +1,8 @@ -import { Capability } from "../../src/capability" import { wnfsCapability, wnfsPrivateCapabilities, wnfsPublicCapabilities } from "./wnfs" - import { alice, bob, mallory } from "../fixtures" -import { all } from "../../src/util" -import * as ucans from "../lib" + +import * as ucans from "../../src" +import { Capability, all } from "../../src" describe("wnfs public capability", () => { diff --git a/packages/core/tests/capability/wnfs.ts b/packages/ucans/tests/capability/wnfs.ts similarity index 90% rename from packages/core/tests/capability/wnfs.ts rename to packages/ucans/tests/capability/wnfs.ts index 5013818..a0e390e 100644 --- a/packages/core/tests/capability/wnfs.ts +++ b/packages/ucans/tests/capability/wnfs.ts @@ -1,10 +1,14 @@ -import { Ability, isAbility } from "../../src/capability/ability" -import { Capability } from "../../src/capability" -import { DelegationSemantics, DelegatedCapability, DelegatedOwnership, rootIssuer } from "../../src/attenuation" -import { SUPERUSER } from "../../src/capability/super-user" -import { Ucan } from "../../src/types" -import { ResourcePointer } from "../../src/capability/resource-pointer" -import * as ucans from "../lib" +import * as ucans from "../../src" +import { + Ucan, + Capability, + DelegationSemantics, + DelegatedCapability, + DelegatedOwnership, + Ability, + ResourcePointer, + rootIssuer +} from "../../src" export const WNFS_ABILITY_LEVELS = { @@ -24,15 +28,15 @@ export function isWnfsCap(cap: Capability): boolean { } export function isWnfsAbility(ability: unknown): ability is WnfsAbility { - if (!isAbility(ability)) return false - if (ability === SUPERUSER) return true + if (!ucans.isAbility(ability)) return false + if (ability === ucans.SUPERUSER) return true const abilitySegment = ability.segments[ 0 ] const isWnfsAbilitySegment = !!abilitySegment && WNFS_ABILITIES.includes(abilitySegment) return isWnfsAbilitySegment && ability.namespace.toLowerCase() === "wnfs" } export function wnfsAbilityFromAbility(ability: Ability): WnfsAbility | null { - if (ability === SUPERUSER) return "SUPER_USER" + if (ability === ucans.SUPERUSER) return "SUPER_USER" if (isWnfsAbility(ability)) return ability.segments[ 0 ] as WnfsAbility return null } diff --git a/packages/core/tests/compatibility.test.ts b/packages/ucans/tests/compatibility.test.ts similarity index 99% rename from packages/core/tests/compatibility.test.ts rename to packages/ucans/tests/compatibility.test.ts index 6ad7e3a..1ef419f 100644 --- a/packages/core/tests/compatibility.test.ts +++ b/packages/ucans/tests/compatibility.test.ts @@ -1,5 +1,5 @@ import * as uint8arrays from "uint8arrays" -import * as ucans from "./lib" +import * as ucans from "../src" const oldUcan = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsInVhdiI6IjEuMC4wIn0.eyJhdWQiOiJkaWQ6a2V5OnoxM1YzU29nMllhVUtoZEdDbWd4OVVadVcxbzFTaEZKWWM2RHZHWWU3TlR0Njg5Tm9MMXRrZUd3NGMydGFQa2dBdWloUjh0cmg2azg2VHRVaTNIR2ZrNEh1NDg3czNiTWY4V1MzWjJoU3VwRktiNmhnV3VwajFIRzhheUxRdDFmeWJSdThjTGdBMkNKanFRYm16YzRFOEFKU0tKeDNndVFYa2F4c3R2Um5RRGN1eDFkZzhVR1BRS3haN2lLeUFKWkFuQlcyWXJUM2o0TVQxdTJNcWZQWG9RYU01WFZQMk04clBFN0FCSEREOXdMbWlKdjkzUUFDRFR5MllnZkVSS3JualNWaTdFb3RNOFR3NHg3M1pNUXJEQnZRRW01Zm9tTWZVaTZVSmJUTmVaaldDTUJQYllNbXRKUDZQZlRpaWZYZG0zdXprVFg5NnExUkVFOExodkU2Rzg2cUR0Wjg5MzdFYUdXdXFpNkRHVDFvc2FRMUVnR3NFN3Jac2JSdDFLNnRXeTZpYktlNTlKZWtnTWFlNW9XNER2IiwiZXhwIjozMjc0NDE5MTQyMywiZmN0IjpbXSwiaXNzIjoiZGlkOmtleTp6MTNWM1NvZzJZYVVLaGRHQ21neDlVWnVXMW8xU2hGSlljNkR2R1llN05UdDY4OU5vTDJWanZBR2JXdTFrdmZWUWFyVTVWMXBTUnNjOWFwR2h2dDdaODJmUWg1QWE1NW41Zm0zZGs2SnFuTXczZGU4WG91dWZUV2Z1eHpEVkhrSFNGV0sxOW1SWWI4d205d1VwZkxtUWl4QVdtMndFWVZqU2dENEd6YzhVUDlDSjFxMkY4ZXlpVXViMThGbld4Y2djUWhqdXB3OTNxUlMzWDlXUDViemlSYjE4TTZ0Vm8zaUJ4ZUozb2lrRTNaa3RScEtTZDlkcHU5WWNXZFhoeDZDQmY5NTZ1UXhkTDZoTkppNmVMbmZ1eFY2NEhpZU1rZFVoTTJSeThRd3lqZjQ4ZnZWMVhFVU1zeEM5YWFjNEtCcGJONDJHR3U4UmFkRDU3cjZuMWFOc2IyTjU3RkNOYnFIMXVLdHhNTmVHZHJ2QWlUUGRzVjJBRmppczJvN243ajhMNW41YmJ4TFl4VThNVHB3QVphdFpkSiIsIm5iZiI6MTY0MDE5MTQ1NywicHJmIjoiZXlKaGJHY2lPaUpTVXpJMU5pSXNJblI1Y0NJNklrcFhWQ0lzSW5WaGRpSTZJakV1TUM0d0luMC5leUpoZFdRaU9pSmthV1E2YTJWNU9ub3hNMVl6VTI5bk1sbGhWVXRvWkVkRGJXZDRPVlZhZFZjeGJ6RlRhRVpLV1dNMlJIWkhXV1UzVGxSME5qZzVUbTlNTWxacWRrRkhZbGQxTVd0MlpsWlJZWEpWTlZZeGNGTlNjMk01WVhCSGFIWjBOMW80TW1aUmFEVkJZVFUxYmpWbWJUTmthelpLY1c1TmR6TmtaVGhZYjNWMVpsUlhablY0ZWtSV1NHdElVMFpYU3pFNWJWSlpZamgzYlRsM1ZYQm1URzFSYVhoQlYyMHlkMFZaVm1wVFowUTBSM3BqT0ZWUU9VTktNWEV5UmpobGVXbFZkV0l4T0VadVYzaGpaMk5SYUdwMWNIYzVNM0ZTVXpOWU9WZFFOV0o2YVZKaU1UaE5OblJXYnpOcFFuaGxTak52YVd0Rk0xcHJkRkp3UzFOa09XUndkVGxaWTFka1dHaDROa05DWmprMU5uVlJlR1JNTm1oT1NtazJaVXh1Wm5WNFZqWTBTR2xsVFd0a1ZXaE5NbEo1T0ZGM2VXcG1ORGhtZGxZeFdFVlZUWE40UXpsaFlXTTBTMEp3WWs0ME1rZEhkVGhTWVdSRU5UZHlObTR4WVU1ellqSk9OVGRHUTA1aWNVZ3hkVXQwZUUxT1pVZGtjblpCYVZSUVpITldNa0ZHYW1sek1tODNiamRxT0V3MWJqVmlZbmhNV1hoVk9FMVVjSGRCV21GMFdtUktJaXdpWlhod0lqb3pNamMwTkRFNU1UUXlNeXdpWm1OMElqcGJYU3dpYVhOeklqb2laR2xrT210bGVUcDZNVE5XTTFOdlp6SlpZVlZMYUdSSFEyMW5lRGxWV25WWE1XOHhVMmhHU2xsak5rUjJSMWxsTjA1VWREWTRPVTV2VERKaE5VcE9hMlI0VmpabWJYVm9WbU5SWkRkSVIycHhkRXBRYVc1WlZWQTRRMUp4Y21veVkyVm5hVTFyT1RKUlNIazJRbWRXT1hveVVGQnJWMkZZU0RkUlRsQmlRekphZEUxNWFXbGFjWGRLUkVOd05sZG9VbkZVUzJodVFtaENUbWQ1WkRkTFJuUTNjRkkyTkhCa1ZIQjZUbXRNUlZKNGFHNTNUVUZqZURKcVJGZFlOelpDVG5SS04xUTFWVXQ0TTIxcWRHWTBaak0wWjJwVGRUaHJkME5UY0V0alFuQTRWV2RwU0hkdllVSkhkREUxVkZjNVUzQlNXVkoxYUZKdk1tdEljVFZ5Y0ROTmRFSnFSa2QyVUdZeVRsTlpZbUUzTmxoSGJYcFhlVEZyZUZOelEySTVUSGhqTW5welEwdG1lSEF5ZUd0VVFqWmtPVVJDUlVwVE5sUnhXbFo1WkhKU05GWmFNVkE1ZFhJeGRGcHBlbk5qYWtWd1kzVlViV1EzV0VRemRYSjZVelpqY0RSdU1sZHdSbFZNYjNsMk5tOW5ibWxaZEVOSGFUVlVlbWxEY2pKT1FWRjNWMEZYY25CMldVMWllbVEyVmt0a2RUVmpaekZZUWxoTVZFNWhUQ0lzSW01aVppSTZNVFkwTURFNU1UTTJNeXdpY0hSaklqb2lVMVZRUlZKZlZWTkZVaUlzSW5Kell5STZJaW9pZlEuQ0k5SjlOLVhUZUxQNEM5WTktUl9TcEE1aE80dHdpNUQxNFpTR2lwUzdjNS1jTlJWTVItc285Z0JZMlQzSFNaTHFmQ2xyMEtlQVJicFk2TFBwSm1NRGQ1ODdvck1TVVRnMndqN043eUNVeksxSWhOazhQMkQ3RGVlSHNxQ1lsTVotdXpjMHBSbnFJb3dPTWl6MVFkbHZXaTZ0UHNxZkZVYnl4bEx1bXRHdjV1a1hqc1FZcmYzdko3aU5DMkJibWotMGhTV25wNTNBN01TQTllLWFXVGpLUWEwSkpXVVVhWG5XS19CNjRaa3NyTWRXdW5mVFNuSE9lR2o3MFRuSXhieVcxbFhodk5pcnhIUV90ZVlKZ2xIZTRBbldEQXdUa2dnaVotdkp0WUhsYnVwQkt4S1YtNm9OMTlXS3dUT3U3QnpPX2QyUHAtWVVyY1RSSS1KZ0F2NUpnIiwicHRjIjoiU1VQRVJfVVNFUiIsInJzYyI6IioifQ.CRLB4gBBHhnsbfUhLALiCfo6mHnHSlEUczyZsWhh9TNjv9UxdgvsSWQsehGIT4XR0jQeYZo2OhasEVaF-Gtt_qqtUQIrducKngd0qzmpfjVbicsQPKVdJjlcTwm9dqhLSEtL195El0oucLzYdqMEZMf-txEmyhCd_Q8CaNExhAnwN32v1salnO6vrAw33ZJID7ZaFmBleoGUXBHQwnkv9_m_P6Fh-UGIKjaOuNmBkGXGn-4irm-eXrne2OPZCoPjhiaf0xTONu4ROrQQYykG8CppvsSXeiylOFY11Ot0sdAlHGSlyZk1_chJ3ud17K9S-CKWK9NtqiMNcUdQGFnNQQ" diff --git a/packages/core/tests/fixtures.ts b/packages/ucans/tests/fixtures.ts similarity index 93% rename from packages/core/tests/fixtures.ts rename to packages/ucans/tests/fixtures.ts index b34bfcc..73348ca 100644 --- a/packages/core/tests/fixtures.ts +++ b/packages/ucans/tests/fixtures.ts @@ -1,4 +1,4 @@ -import { EdKeypair } from "@ucans/plugins" +import { EdKeypair } from "@ucans/default-plugins" /** did:key:z6Mkk89bC3JrVqKie71YEcc5M1SMVxuCgNx6zLZ8SYJsxALi */ export const alice = EdKeypair.fromSecretKey("U+bzp2GaFQHso587iSFWPSeCzbSfn/CbNHEz7ilKRZ1UQMmMS7qq4UhTzKn3X9Nj/4xgrwa+UqhMOeo4Ki8JUw==") diff --git a/packages/core/tests/store.test.ts b/packages/ucans/tests/store.test.ts similarity index 75% rename from packages/core/tests/store.test.ts rename to packages/ucans/tests/store.test.ts index 3ebe1f7..7c8fa36 100644 --- a/packages/core/tests/store.test.ts +++ b/packages/ucans/tests/store.test.ts @@ -1,13 +1,12 @@ import { alice, bob, mallory } from "./fixtures" import { wnfsCapability, wnfsPublicSemantics } from "./capability/wnfs" -import { Ucan } from "../src/types" -import { all } from "../src/util" -import * as ucans from "./lib" +import * as ucans from "../src" +import { Ucan, all } from "../src" describe("Store.add", () => { it("makes added items retrievable with findByAudience", async () => { - const ucan = await ucans.createBuilder() + const ucan = await ucans.Builder.create() .issuedBy(alice) .toAudience(bob.did()) .withLifetimeInSeconds(30) @@ -15,39 +14,39 @@ describe("Store.add", () => { const encoded = ucans.encode(ucan) - const store = await ucans.storeFromTokens(ucans.equalCanDelegate, []) + const store = await ucans.Store.empty(ucans.equalCanDelegate) await store.add(ucan) expect(encodeOrNull(store.findByAudience(ucan.payload.aud, find => ucans.encode(find) === encoded))).toEqual(encoded) }) it("makes added items retrievable with findByAudience among multiple others", async () => { - const ucan = await ucans.createBuilder() + const ucan = await ucans.Builder.create() .issuedBy(alice) .toAudience(bob.did()) .withLifetimeInSeconds(30) .build() - const ucan2 = await ucans.createBuilder() + const ucan2 = await ucans.Builder.create() .issuedBy(alice) .toAudience(mallory.did()) .withLifetimeInSeconds(30) .build() const encoded = ucans.encode(ucan) - const store = await ucans.storeFromTokens(ucans.equalCanDelegate, []) + const store = await ucans.Store.empty(ucans.equalCanDelegate) await store.add(ucan2) await store.add(ucan) expect(encodeOrNull(store.findByAudience(ucan.payload.aud, find => ucans.encode(find) === encoded))).toEqual(encoded) }) it("doesn't add items twice", async () => { - const ucan = await ucans.createBuilder() + const ucan = await ucans.Builder.create() .issuedBy(alice) .toAudience(bob.did()) .withLifetimeInSeconds(30) .build() - const store = await ucans.storeFromTokens(ucans.equalCanDelegate, []) + const store = await ucans.Store.empty(ucans.equalCanDelegate) await store.add(ucan) await store.add(ucan) expect(store.getByAudience(ucan.payload.aud)).toEqual([ ucan ]) @@ -58,19 +57,19 @@ describe("Store.add", () => { describe("Store.findByAudience", () => { it("only returns ucans with given audience", async () => { - const ucanBob = await ucans.createBuilder() + const ucanBob = await ucans.Builder.create() .issuedBy(alice) .toAudience(bob.did()) .withLifetimeInSeconds(30) .build() - const ucanAlice = await ucans.createBuilder() + const ucanAlice = await ucans.Builder.create() .issuedBy(bob) .toAudience(alice.did()) .withLifetimeInSeconds(30) .build() - const store = await ucans.storeFromTokens(ucans.equalCanDelegate, [ ucanBob, ucanAlice ].map(ucan => ucans.encode(ucan))) + const store = await ucans.Store.fromTokens(ucans.equalCanDelegate, [ ucanBob, ucanAlice ].map(ucan => ucans.encode(ucan))) expect(store.findByAudience(mallory.did(), () => true)).toEqual(null) expect(encodeOrNull(store.findByAudience(bob.did(), () => true))).toEqual(ucans.encode(ucanBob)) expect(encodeOrNull(store.findByAudience(alice.did(), () => true))).toEqual(ucans.encode(ucanAlice)) @@ -81,14 +80,14 @@ describe("Store.findByAudience", () => { describe("Store.findWithCapability", () => { it("finds ucans with more capabilities than the given", async () => { - const ucan = await ucans.createBuilder() + const ucan = await ucans.Builder.create() .issuedBy(alice) .toAudience(bob.did()) .withLifetimeInSeconds(30) .claimCapability(wnfsCapability("alice.fission.name/public/", "SUPER_USER")) .build() - const store = await ucans.storeFromTokens(wnfsPublicSemantics, [ ucans.encode(ucan) ]) + const store = await ucans.Store.fromTokens(wnfsPublicSemantics, [ ucans.encode(ucan) ]) const results = all(store.findWithCapability( bob.did(), @@ -104,20 +103,20 @@ describe("Store.findWithCapability", () => { }) it("reports an error if the capability can't be found with given audience", async () => { - const ucanBob = await ucans.createBuilder() + const ucanBob = await ucans.Builder.create() .issuedBy(alice) .toAudience(bob.did()) .withLifetimeInSeconds(30) .claimCapability(wnfsCapability("alice.fission.name/public/", "SUPER_USER")) .build() - const ucanAlice = await ucans.createBuilder() + const ucanAlice = await ucans.Builder.create() .issuedBy(alice) .toAudience(bob.did()) .withLifetimeInSeconds(30) .build() - const store = await ucans.storeFromTokens(wnfsPublicSemantics, [ ucans.encode(ucanAlice), ucans.encode(ucanBob) ]) + const store = await ucans.Store.fromTokens(wnfsPublicSemantics, [ ucans.encode(ucanAlice), ucans.encode(ucanBob) ]) const results = all(store.findWithCapability( alice.did(), diff --git a/packages/core/tests/token.test.ts b/packages/ucans/tests/token.test.ts similarity index 99% rename from packages/core/tests/token.test.ts rename to packages/ucans/tests/token.test.ts index 68495c8..7062879 100644 --- a/packages/core/tests/token.test.ts +++ b/packages/ucans/tests/token.test.ts @@ -1,6 +1,6 @@ import * as uint8arrays from "uint8arrays" import { alice, bob } from "./fixtures" -import * as ucans from "./lib" +import * as ucans from "../src" // COMPOSING diff --git a/packages/core/tests/verify.test.ts b/packages/ucans/tests/verify.test.ts similarity index 94% rename from packages/core/tests/verify.test.ts rename to packages/ucans/tests/verify.test.ts index f21ba57..cc78603 100644 --- a/packages/core/tests/verify.test.ts +++ b/packages/ucans/tests/verify.test.ts @@ -1,6 +1,6 @@ import { emailCapability } from "./capability/email" import { alice, bob, mallory } from "./fixtures" -import * as ucans from "./lib" +import * as ucans from "../src" describe("verify", () => { @@ -59,7 +59,7 @@ describe("verify", () => { requiredCapabilities: [ { capability: { ...emailCapability("alice@email.com"), - can: ucans.ability.SUPERUSER, + can: ucans.SUPERUSER, }, rootIssuer: alice.did() } ] @@ -121,7 +121,7 @@ describe("verify", () => { const ucan = await ucans.build({ issuer: bob, audience: mallory.did(), - capabilities: [ ucans.capability.prf(ucans.ability.SUPERUSER, ucans.ability.REDELEGATE) ], + capabilities: [ ucans.capability.prf(ucans.SUPERUSER, ucans.ability.REDELEGATE) ], proofs: [ ucans.encode(leafUcan) ] }) @@ -215,7 +215,7 @@ describe("verify", () => { const ucan = await ucans.build({ issuer: bob, audience: mallory.did(), - capabilities: [ ucans.capability.prf(ucans.ability.SUPERUSER, ucans.ability.REDELEGATE) ], + capabilities: [ ucans.capability.prf(ucans.SUPERUSER, ucans.ability.REDELEGATE) ], proofs: [ ucans.encode(leafUcan) ] }) @@ -234,7 +234,7 @@ describe("verify", () => { const leafUcan = await ucans.build({ issuer: alice, audience: bob.did(), - capabilities: [ ucans.capability.my(ucans.ability.SUPERUSER) ] + capabilities: [ ucans.capability.my(ucans.SUPERUSER) ] }) const ucan = await ucans.build({ @@ -263,13 +263,13 @@ describe("verify", () => { const leafUcan = await ucans.build({ issuer: alice, audience: bob.did(), - capabilities: [ ucans.capability.my(ucans.ability.SUPERUSER) ] + capabilities: [ ucans.capability.my(ucans.SUPERUSER) ] }) const middleUcan = await ucans.build({ issuer: bob, audience: mallory.did(), - capabilities: [ ucans.capability.as(alice.did(), ucans.ability.SUPERUSER) ], + capabilities: [ ucans.capability.as(alice.did(), ucans.SUPERUSER) ], proofs: [ ucans.encode(leafUcan) ] }) @@ -324,7 +324,7 @@ describe("verify", () => { const leafUcan = await ucans.build({ issuer: alice, audience: bob.did(), - capabilities: [ ucans.capability.as(bob.did(), ucans.ability.SUPERUSER) ] + capabilities: [ ucans.capability.as(bob.did(), ucans.SUPERUSER) ] }) const ucan = await ucans.build({ diff --git a/packages/ucans/tsconfig.json b/packages/ucans/tsconfig.json index b59f12b..c60fdc6 100644 --- a/packages/ucans/tsconfig.json +++ b/packages/ucans/tsconfig.json @@ -4,7 +4,8 @@ "strict": true, "moduleResolution": "node", "esModuleInterop": true, - "noFallthroughCasesInSwitch": true + "noFallthroughCasesInSwitch": true, + "allowJs": true }, "include": [ "src/**/*" diff --git a/yarn.lock b/yarn.lock index e46a095..c461f2b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -10,150 +10,150 @@ "@jridgewell/gen-mapping" "^0.1.0" "@jridgewell/trace-mapping" "^0.3.9" -"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.12.13", "@babel/code-frame@^7.16.7": - version "7.16.7" - resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.16.7.tgz#44416b6bd7624b998f5b1af5d470856c40138789" - integrity sha512-iAXqUn8IIeBTNd72xsFlgaXHkMBMt6y4HJp1tIaK465CWLT/fG1aqB7ykr95gHHmlBdGbFeWWfyB4NJJ0nmeIg== +"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.12.13", "@babel/code-frame@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.18.6.tgz#3b25d38c89600baa2dcc219edfa88a74eb2c427a" + integrity sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q== dependencies: - "@babel/highlight" "^7.16.7" + "@babel/highlight" "^7.18.6" -"@babel/compat-data@^7.17.10": - version "7.18.5" - resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.18.5.tgz#acac0c839e317038c73137fbb6ef71a1d6238471" - integrity sha512-BxhE40PVCBxVEJsSBhB6UWyAuqJRxGsAw8BdHMJ3AKGydcwuWW4kOO3HmqBQAdcq/OP+/DlTVxLvsCzRTnZuGg== +"@babel/compat-data@^7.18.6": + version "7.18.8" + resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.18.8.tgz#2483f565faca607b8535590e84e7de323f27764d" + integrity sha512-HSmX4WZPPK3FUxYp7g2T6EyO8j96HlZJlxmKPSh6KAcqwyDrfx7hKjXpAW/0FhFfTJsR0Yt4lAjLI2coMptIHQ== "@babel/core@^7.1.0", "@babel/core@^7.12.3", "@babel/core@^7.7.2", "@babel/core@^7.8.0": - version "7.18.5" - resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.18.5.tgz#c597fa680e58d571c28dda9827669c78cdd7f000" - integrity sha512-MGY8vg3DxMnctw0LdvSEojOsumc70g0t18gNyUdAZqB1Rpd1Bqo/svHGvt+UJ6JcGX+DIekGFDxxIWofBxLCnQ== + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.18.6.tgz#54a107a3c298aee3fe5e1947a6464b9b6faca03d" + integrity sha512-cQbWBpxcbbs/IUredIPkHiAGULLV8iwgNRMFzvbhEXISp4f3rUUXE5+TIw6KwUWUR3DwyI6gmBRnmAtYaWehwQ== dependencies: "@ampproject/remapping" "^2.1.0" - "@babel/code-frame" "^7.16.7" - "@babel/generator" "^7.18.2" - "@babel/helper-compilation-targets" "^7.18.2" - "@babel/helper-module-transforms" "^7.18.0" - "@babel/helpers" "^7.18.2" - "@babel/parser" "^7.18.5" - "@babel/template" "^7.16.7" - "@babel/traverse" "^7.18.5" - "@babel/types" "^7.18.4" + "@babel/code-frame" "^7.18.6" + "@babel/generator" "^7.18.6" + "@babel/helper-compilation-targets" "^7.18.6" + "@babel/helper-module-transforms" "^7.18.6" + "@babel/helpers" "^7.18.6" + "@babel/parser" "^7.18.6" + "@babel/template" "^7.18.6" + "@babel/traverse" "^7.18.6" + "@babel/types" "^7.18.6" convert-source-map "^1.7.0" debug "^4.1.0" gensync "^1.0.0-beta.2" json5 "^2.2.1" semver "^6.3.0" -"@babel/generator@^7.18.2", "@babel/generator@^7.7.2": - version "7.18.2" - resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.18.2.tgz#33873d6f89b21efe2da63fe554460f3df1c5880d" - integrity sha512-W1lG5vUwFvfMd8HVXqdfbuG7RuaSrTCCD8cl8fP8wOivdbtbIg2Db3IWUcgvfxKbbn6ZBGYRW/Zk1MIwK49mgw== +"@babel/generator@^7.18.6", "@babel/generator@^7.18.7", "@babel/generator@^7.7.2": + version "7.18.7" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.18.7.tgz#2aa78da3c05aadfc82dbac16c99552fc802284bd" + integrity sha512-shck+7VLlY72a2w9c3zYWuE1pwOKEiQHV7GTUbSnhyl5eu3i04t30tBY82ZRWrDfo3gkakCFtevExnxbkf2a3A== dependencies: - "@babel/types" "^7.18.2" - "@jridgewell/gen-mapping" "^0.3.0" + "@babel/types" "^7.18.7" + "@jridgewell/gen-mapping" "^0.3.2" jsesc "^2.5.1" -"@babel/helper-compilation-targets@^7.18.2": - version "7.18.2" - resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.18.2.tgz#67a85a10cbd5fc7f1457fec2e7f45441dc6c754b" - integrity sha512-s1jnPotJS9uQnzFtiZVBUxe67CuBa679oWFHpxYYnTpRL/1ffhyX44R9uYiXoa/pLXcY9H2moJta0iaanlk/rQ== +"@babel/helper-compilation-targets@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.18.6.tgz#18d35bfb9f83b1293c22c55b3d576c1315b6ed96" + integrity sha512-vFjbfhNCzqdeAtZflUFrG5YIFqGTqsctrtkZ1D/NB0mDW9TwW3GmmUepYY4G9wCET5rY5ugz4OGTcLd614IzQg== dependencies: - "@babel/compat-data" "^7.17.10" - "@babel/helper-validator-option" "^7.16.7" + "@babel/compat-data" "^7.18.6" + "@babel/helper-validator-option" "^7.18.6" browserslist "^4.20.2" semver "^6.3.0" -"@babel/helper-environment-visitor@^7.16.7", "@babel/helper-environment-visitor@^7.18.2": - version "7.18.2" - resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.2.tgz#8a6d2dedb53f6bf248e31b4baf38739ee4a637bd" - integrity sha512-14GQKWkX9oJzPiQQ7/J36FTXcD4kSp8egKjO9nINlSKiHITRA9q/R74qu8S9xlc/b/yjsJItQUeeh3xnGN0voQ== - -"@babel/helper-function-name@^7.17.9": - version "7.17.9" - resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.17.9.tgz#136fcd54bc1da82fcb47565cf16fd8e444b1ff12" - integrity sha512-7cRisGlVtiVqZ0MW0/yFB4atgpGLWEHUVYnb448hZK4x+vih0YO5UoS11XIYtZYqHd0dIPMdUSv8q5K4LdMnIg== - dependencies: - "@babel/template" "^7.16.7" - "@babel/types" "^7.17.0" - -"@babel/helper-hoist-variables@^7.16.7": - version "7.16.7" - resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.16.7.tgz#86bcb19a77a509c7b77d0e22323ef588fa58c246" - integrity sha512-m04d/0Op34H5v7pbZw6pSKP7weA6lsMvfiIAMeIvkY/R4xQtBSMFEigu9QTZ2qB/9l22vsxtM8a+Q8CzD255fg== - dependencies: - "@babel/types" "^7.16.7" - -"@babel/helper-module-imports@^7.16.7": - version "7.16.7" - resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.16.7.tgz#25612a8091a999704461c8a222d0efec5d091437" - integrity sha512-LVtS6TqjJHFc+nYeITRo6VLXve70xmq7wPhWTqDJusJEgGmkAACWwMiTNrvfoQo6hEhFwAIixNkvB0jPXDL8Wg== - dependencies: - "@babel/types" "^7.16.7" - -"@babel/helper-module-transforms@^7.18.0": - version "7.18.0" - resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.18.0.tgz#baf05dec7a5875fb9235bd34ca18bad4e21221cd" - integrity sha512-kclUYSUBIjlvnzN2++K9f2qzYKFgjmnmjwL4zlmU5f8ZtzgWe8s0rUPSTGy2HmK4P8T52MQsS+HTQAgZd3dMEA== - dependencies: - "@babel/helper-environment-visitor" "^7.16.7" - "@babel/helper-module-imports" "^7.16.7" - "@babel/helper-simple-access" "^7.17.7" - "@babel/helper-split-export-declaration" "^7.16.7" - "@babel/helper-validator-identifier" "^7.16.7" - "@babel/template" "^7.16.7" - "@babel/traverse" "^7.18.0" - "@babel/types" "^7.18.0" - -"@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.10.4", "@babel/helper-plugin-utils@^7.12.13", "@babel/helper-plugin-utils@^7.14.5", "@babel/helper-plugin-utils@^7.17.12", "@babel/helper-plugin-utils@^7.8.0": - version "7.17.12" - resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.17.12.tgz#86c2347da5acbf5583ba0a10aed4c9bf9da9cf96" - integrity sha512-JDkf04mqtN3y4iAbO1hv9U2ARpPyPL1zqyWs/2WG1pgSq9llHFjStX5jdxb84himgJm+8Ng+x0oiWF/nw/XQKA== - -"@babel/helper-simple-access@^7.17.7": - version "7.18.2" - resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.18.2.tgz#4dc473c2169ac3a1c9f4a51cfcd091d1c36fcff9" - integrity sha512-7LIrjYzndorDY88MycupkpQLKS1AFfsVRm2k/9PtKScSy5tZq0McZTj+DiMRynboZfIqOKvo03pmhTaUgiD6fQ== - dependencies: - "@babel/types" "^7.18.2" - -"@babel/helper-split-export-declaration@^7.16.7": - version "7.16.7" - resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.16.7.tgz#0b648c0c42da9d3920d85ad585f2778620b8726b" - integrity sha512-xbWoy/PFoxSWazIToT9Sif+jJTlrMcndIsaOKvTA6u7QEo7ilkRZpjew18/W3c7nm8fXdUDXh02VXTbZ0pGDNw== - dependencies: - "@babel/types" "^7.16.7" - -"@babel/helper-validator-identifier@^7.16.7": - version "7.16.7" - resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.16.7.tgz#e8c602438c4a8195751243da9031d1607d247cad" - integrity sha512-hsEnFemeiW4D08A5gUAZxLBTXpZ39P+a+DGDsHw1yxqyQ/jzFEnxf5uTEGp+3bzAbNOxU1paTgYS4ECU/IgfDw== - -"@babel/helper-validator-option@^7.16.7": - version "7.16.7" - resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.16.7.tgz#b203ce62ce5fe153899b617c08957de860de4d23" - integrity sha512-TRtenOuRUVo9oIQGPC5G9DgK4743cdxvtOw0weQNpZXaS16SCBi5MNjZF8vba3ETURjZpTbVn7Vvcf2eAwFozQ== - -"@babel/helpers@^7.18.2": - version "7.18.2" - resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.18.2.tgz#970d74f0deadc3f5a938bfa250738eb4ac889384" - integrity sha512-j+d+u5xT5utcQSzrh9p+PaJX94h++KN+ng9b9WEJq7pkUPAd61FGqhjuUEdfknb3E/uDBb7ruwEeKkIxNJPIrg== - dependencies: - "@babel/template" "^7.16.7" - "@babel/traverse" "^7.18.2" - "@babel/types" "^7.18.2" - -"@babel/highlight@^7.16.7": - version "7.17.12" - resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.17.12.tgz#257de56ee5afbd20451ac0a75686b6b404257351" - integrity sha512-7yykMVF3hfZY2jsHZEEgLc+3x4o1O+fYyULu11GynEUQNwB6lua+IIQn1FiJxNucd5UlyJryrwsOh8PL9Sn8Qg== - dependencies: - "@babel/helper-validator-identifier" "^7.16.7" +"@babel/helper-environment-visitor@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.6.tgz#b7eee2b5b9d70602e59d1a6cad7dd24de7ca6cd7" + integrity sha512-8n6gSfn2baOY+qlp+VSzsosjCVGFqWKmDF0cCWOybh52Dw3SEyoWR1KrhMJASjLwIEkkAufZ0xvr+SxLHSpy2Q== + +"@babel/helper-function-name@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.18.6.tgz#8334fecb0afba66e6d87a7e8c6bb7fed79926b83" + integrity sha512-0mWMxV1aC97dhjCah5U5Ua7668r5ZmSC2DLfH2EZnf9c3/dHZKiFa5pRLMH5tjSl471tY6496ZWk/kjNONBxhw== + dependencies: + "@babel/template" "^7.18.6" + "@babel/types" "^7.18.6" + +"@babel/helper-hoist-variables@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.18.6.tgz#d4d2c8fb4baeaa5c68b99cc8245c56554f926678" + integrity sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q== + dependencies: + "@babel/types" "^7.18.6" + +"@babel/helper-module-imports@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.18.6.tgz#1e3ebdbbd08aad1437b428c50204db13c5a3ca6e" + integrity sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA== + dependencies: + "@babel/types" "^7.18.6" + +"@babel/helper-module-transforms@^7.18.6": + version "7.18.8" + resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.18.8.tgz#4f8408afead0188cfa48672f9d0e5787b61778c8" + integrity sha512-che3jvZwIcZxrwh63VfnFTUzcAM9v/lznYkkRxIBGMPt1SudOKHAEec0SIRCfiuIzTcF7VGj/CaTT6gY4eWxvA== + dependencies: + "@babel/helper-environment-visitor" "^7.18.6" + "@babel/helper-module-imports" "^7.18.6" + "@babel/helper-simple-access" "^7.18.6" + "@babel/helper-split-export-declaration" "^7.18.6" + "@babel/helper-validator-identifier" "^7.18.6" + "@babel/template" "^7.18.6" + "@babel/traverse" "^7.18.8" + "@babel/types" "^7.18.8" + +"@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.10.4", "@babel/helper-plugin-utils@^7.12.13", "@babel/helper-plugin-utils@^7.14.5", "@babel/helper-plugin-utils@^7.18.6", "@babel/helper-plugin-utils@^7.8.0": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.18.6.tgz#9448974dd4fb1d80fefe72e8a0af37809cd30d6d" + integrity sha512-gvZnm1YAAxh13eJdkb9EWHBnF3eAub3XTLCZEehHT2kWxiKVRL64+ae5Y6Ivne0mVHmMYKT+xWgZO+gQhuLUBg== + +"@babel/helper-simple-access@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.18.6.tgz#d6d8f51f4ac2978068df934b569f08f29788c7ea" + integrity sha512-iNpIgTgyAvDQpDj76POqg+YEt8fPxx3yaNBg3S30dxNKm2SWfYhD0TGrK/Eu9wHpUW63VQU894TsTg+GLbUa1g== + dependencies: + "@babel/types" "^7.18.6" + +"@babel/helper-split-export-declaration@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.18.6.tgz#7367949bc75b20c6d5a5d4a97bba2824ae8ef075" + integrity sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA== + dependencies: + "@babel/types" "^7.18.6" + +"@babel/helper-validator-identifier@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.18.6.tgz#9c97e30d31b2b8c72a1d08984f2ca9b574d7a076" + integrity sha512-MmetCkz9ej86nJQV+sFCxoGGrUbU3q02kgLciwkrt9QqEB7cP39oKEY0PakknEO0Gu20SskMRi+AYZ3b1TpN9g== + +"@babel/helper-validator-option@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.18.6.tgz#bf0d2b5a509b1f336099e4ff36e1a63aa5db4db8" + integrity sha512-XO7gESt5ouv/LRJdrVjkShckw6STTaB7l9BrpBaAHDeF5YZT+01PCwmR0SJHnkW6i8OwW/EVWRShfi4j2x+KQw== + +"@babel/helpers@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.18.6.tgz#4c966140eaa1fcaa3d5a8c09d7db61077d4debfd" + integrity sha512-vzSiiqbQOghPngUYt/zWGvK3LAsPhz55vc9XNN0xAl2gV4ieShI2OQli5duxWHD+72PZPTKAcfcZDE1Cwc5zsQ== + dependencies: + "@babel/template" "^7.18.6" + "@babel/traverse" "^7.18.6" + "@babel/types" "^7.18.6" + +"@babel/highlight@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.18.6.tgz#81158601e93e2563795adcbfbdf5d64be3f2ecdf" + integrity sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g== + dependencies: + "@babel/helper-validator-identifier" "^7.18.6" chalk "^2.0.0" js-tokens "^4.0.0" -"@babel/parser@^7.1.0", "@babel/parser@^7.14.7", "@babel/parser@^7.16.7", "@babel/parser@^7.18.5": - version "7.18.5" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.18.5.tgz#337062363436a893a2d22faa60be5bb37091c83c" - integrity sha512-YZWVaglMiplo7v8f1oMQ5ZPQr0vn7HPeZXxXWsxXJRjGVrzUFn9OxFQl1sb5wzfootjA/yChhW84BV+383FSOw== +"@babel/parser@^7.1.0", "@babel/parser@^7.14.7", "@babel/parser@^7.18.6", "@babel/parser@^7.18.8": + version "7.18.8" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.18.8.tgz#822146080ac9c62dac0823bb3489622e0bc1cbdf" + integrity sha512-RSKRfYX20dyH+elbJK2uqAkVyucL+xXzhqlMD5/ZXx+dAAwpyB7HsvnHe/ZUGOF+xLr5Wx9/JoXVTj6BQE2/oA== "@babel/plugin-syntax-async-generators@^7.8.4": version "7.8.4" @@ -240,43 +240,43 @@ "@babel/helper-plugin-utils" "^7.14.5" "@babel/plugin-syntax-typescript@^7.7.2": - version "7.17.12" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.17.12.tgz#b54fc3be6de734a56b87508f99d6428b5b605a7b" - integrity sha512-TYY0SXFiO31YXtNg3HtFwNJHjLsAyIIhAhNWkQ5whPPS7HWUFlg9z0Ta4qAQNjQbP1wsSt/oKkmZ/4/WWdMUpw== - dependencies: - "@babel/helper-plugin-utils" "^7.17.12" - -"@babel/template@^7.16.7", "@babel/template@^7.3.3": - version "7.16.7" - resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.16.7.tgz#8d126c8701fde4d66b264b3eba3d96f07666d155" - integrity sha512-I8j/x8kHUrbYRTUxXrrMbfCa7jxkE7tZre39x3kjr9hvI82cK1FfqLygotcWN5kdPGWcLdWMHpSBavse5tWw3w== - dependencies: - "@babel/code-frame" "^7.16.7" - "@babel/parser" "^7.16.7" - "@babel/types" "^7.16.7" - -"@babel/traverse@^7.18.0", "@babel/traverse@^7.18.2", "@babel/traverse@^7.18.5", "@babel/traverse@^7.7.2": - version "7.18.5" - resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.18.5.tgz#94a8195ad9642801837988ab77f36e992d9a20cd" - integrity sha512-aKXj1KT66sBj0vVzk6rEeAO6Z9aiiQ68wfDgge3nHhA/my6xMM/7HGQUNumKZaoa2qUPQ5whJG9aAifsxUKfLA== - dependencies: - "@babel/code-frame" "^7.16.7" - "@babel/generator" "^7.18.2" - "@babel/helper-environment-visitor" "^7.18.2" - "@babel/helper-function-name" "^7.17.9" - "@babel/helper-hoist-variables" "^7.16.7" - "@babel/helper-split-export-declaration" "^7.16.7" - "@babel/parser" "^7.18.5" - "@babel/types" "^7.18.4" + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.18.6.tgz#1c09cd25795c7c2b8a4ba9ae49394576d4133285" + integrity sha512-mAWAuq4rvOepWCBid55JuRNvpTNf2UGVgoz4JV0fXEKolsVZDzsa4NqCef758WZJj/GDu0gVGItjKFiClTAmZA== + dependencies: + "@babel/helper-plugin-utils" "^7.18.6" + +"@babel/template@^7.18.6", "@babel/template@^7.3.3": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.18.6.tgz#1283f4993e00b929d6e2d3c72fdc9168a2977a31" + integrity sha512-JoDWzPe+wgBsTTgdnIma3iHNFC7YVJoPssVBDjiHfNlyt4YcunDtcDOUmfVDfCK5MfdsaIoX9PkijPhjH3nYUw== + dependencies: + "@babel/code-frame" "^7.18.6" + "@babel/parser" "^7.18.6" + "@babel/types" "^7.18.6" + +"@babel/traverse@^7.18.6", "@babel/traverse@^7.18.8", "@babel/traverse@^7.7.2": + version "7.18.8" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.18.8.tgz#f095e62ab46abf1da35e5a2011f43aee72d8d5b0" + integrity sha512-UNg/AcSySJYR/+mIcJQDCv00T+AqRO7j/ZEJLzpaYtgM48rMg5MnkJgyNqkzo88+p4tfRvZJCEiwwfG6h4jkRg== + dependencies: + "@babel/code-frame" "^7.18.6" + "@babel/generator" "^7.18.7" + "@babel/helper-environment-visitor" "^7.18.6" + "@babel/helper-function-name" "^7.18.6" + "@babel/helper-hoist-variables" "^7.18.6" + "@babel/helper-split-export-declaration" "^7.18.6" + "@babel/parser" "^7.18.8" + "@babel/types" "^7.18.8" debug "^4.1.0" globals "^11.1.0" -"@babel/types@^7.0.0", "@babel/types@^7.16.7", "@babel/types@^7.17.0", "@babel/types@^7.18.0", "@babel/types@^7.18.2", "@babel/types@^7.18.4", "@babel/types@^7.3.0", "@babel/types@^7.3.3": - version "7.18.4" - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.18.4.tgz#27eae9b9fd18e9dccc3f9d6ad051336f307be354" - integrity sha512-ThN1mBcMq5pG/Vm2IcBmPPfyPXbd8S02rS+OBIDENdufvqC7Z/jHPCv9IcP01277aKtDI8g/2XysBN4hA8niiw== +"@babel/types@^7.0.0", "@babel/types@^7.18.6", "@babel/types@^7.18.7", "@babel/types@^7.18.8", "@babel/types@^7.3.0", "@babel/types@^7.3.3": + version "7.18.8" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.18.8.tgz#c5af199951bf41ba4a6a9a6d0d8ad722b30cd42f" + integrity sha512-qwpdsmraq0aJ3osLJRApsc2ouSJCdnMeZwB0DhbtHAtRpZNZCdlbRnHIgcRKzdE1g0iOGg644fzjOBcdOz9cPw== dependencies: - "@babel/helper-validator-identifier" "^7.16.7" + "@babel/helper-validator-identifier" "^7.18.6" to-fast-properties "^2.0.0" "@bcoe/v8-coverage@^0.2.3": @@ -513,7 +513,7 @@ "@jridgewell/set-array" "^1.0.0" "@jridgewell/sourcemap-codec" "^1.4.10" -"@jridgewell/gen-mapping@^0.3.0": +"@jridgewell/gen-mapping@^0.3.2": version "0.3.2" resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz#c1aedc61e853f2bb9f5dfe6d4442d3b565b253b9" integrity sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A== @@ -523,9 +523,9 @@ "@jridgewell/trace-mapping" "^0.3.9" "@jridgewell/resolve-uri@^3.0.3": - version "3.0.8" - resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.0.8.tgz#687cc2bbf243f4e9a868ecf2262318e2658873a1" - integrity sha512-YK5G9LaddzGbcucK4c8h5tWFmMPBvRZ/uyWmN1/SbBdIvqGUdWGkJ5BAaccgs6XbzVLsqbPJrBSFwKv3kT9i7w== + version "3.1.0" + resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz#2203b118c157721addfe69d47b70465463066d78" + integrity sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w== "@jridgewell/set-array@^1.0.0", "@jridgewell/set-array@^1.0.1": version "1.1.2" @@ -734,9 +734,9 @@ integrity sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ== "@types/node@*": - version "18.0.0" - resolved "https://registry.yarnpkg.com/@types/node/-/node-18.0.0.tgz#67c7b724e1bcdd7a8821ce0d5ee184d3b4dd525a" - integrity sha512-cHlGmko4gWLVI27cGJntjs/Sj8th9aYwplmZFwmmgYQQvL5NUsgVJG7OddLvNfLqYS31KFN0s3qlaD9qCaxACA== + version "18.0.3" + resolved "https://registry.yarnpkg.com/@types/node/-/node-18.0.3.tgz#463fc47f13ec0688a33aec75d078a0541a447199" + integrity sha512-HzNRZtp4eepNitP+BD6k2L6DROIDG4Q0fm4x+dwfsr6LGmROENnok75VGw40628xf+iR24WeMFcHuuBDUAzzsQ== "@types/node@^17.0.23": version "17.0.45" @@ -766,13 +766,13 @@ "@types/yargs-parser" "*" "@typescript-eslint/eslint-plugin@^5.18.0": - version "5.30.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.30.0.tgz#524a11e15c09701733033c96943ecf33f55d9ca1" - integrity sha512-lvhRJ2pGe2V9MEU46ELTdiHgiAFZPKtLhiU5wlnaYpMc2+c1R8fh8i80ZAa665drvjHKUJyRRGg3gEm1If54ow== + version "5.30.6" + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.30.6.tgz#9c6017b6c1d04894141b4a87816388967f64c359" + integrity sha512-J4zYMIhgrx4MgnZrSDD7sEnQp7FmhKNOaqaOpaoQ/SfdMfRB/0yvK74hTnvH+VQxndZynqs5/Hn4t+2/j9bADg== dependencies: - "@typescript-eslint/scope-manager" "5.30.0" - "@typescript-eslint/type-utils" "5.30.0" - "@typescript-eslint/utils" "5.30.0" + "@typescript-eslint/scope-manager" "5.30.6" + "@typescript-eslint/type-utils" "5.30.6" + "@typescript-eslint/utils" "5.30.6" debug "^4.3.4" functional-red-black-tree "^1.0.1" ignore "^5.2.0" @@ -781,70 +781,75 @@ tsutils "^3.21.0" "@typescript-eslint/parser@^5.18.0": - version "5.30.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.30.0.tgz#a2184fb5f8ef2bf1db0ae61a43907e2e32aa1b8f" - integrity sha512-2oYYUws5o2liX6SrFQ5RB88+PuRymaM2EU02/9Ppoyu70vllPnHVO7ioxDdq/ypXHA277R04SVjxvwI8HmZpzA== + version "5.30.6" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.30.6.tgz#add440db038fa9d777e4ebdaf66da9e7fb7abe92" + integrity sha512-gfF9lZjT0p2ZSdxO70Xbw8w9sPPJGfAdjK7WikEjB3fcUI/yr9maUVEdqigBjKincUYNKOmf7QBMiTf719kbrA== dependencies: - "@typescript-eslint/scope-manager" "5.30.0" - "@typescript-eslint/types" "5.30.0" - "@typescript-eslint/typescript-estree" "5.30.0" + "@typescript-eslint/scope-manager" "5.30.6" + "@typescript-eslint/types" "5.30.6" + "@typescript-eslint/typescript-estree" "5.30.6" debug "^4.3.4" -"@typescript-eslint/scope-manager@5.30.0": - version "5.30.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.30.0.tgz#bf585ee801ab4ad84db2f840174e171a6bb002c7" - integrity sha512-3TZxvlQcK5fhTBw5solQucWSJvonXf5yua5nx8OqK94hxdrT7/6W3/CS42MLd/f1BmlmmbGEgQcTHHCktUX5bQ== +"@typescript-eslint/scope-manager@5.30.6": + version "5.30.6" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.30.6.tgz#ce1b49ff5ce47f55518d63dbe8fc9181ddbd1a33" + integrity sha512-Hkq5PhLgtVoW1obkqYH0i4iELctEKixkhWLPTYs55doGUKCASvkjOXOd/pisVeLdO24ZX9D6yymJ/twqpJiG3g== dependencies: - "@typescript-eslint/types" "5.30.0" - "@typescript-eslint/visitor-keys" "5.30.0" + "@typescript-eslint/types" "5.30.6" + "@typescript-eslint/visitor-keys" "5.30.6" -"@typescript-eslint/type-utils@5.30.0": - version "5.30.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-5.30.0.tgz#98f3af926a5099153f092d4dad87148df21fbaae" - integrity sha512-GF8JZbZqSS+azehzlv/lmQQ3EU3VfWYzCczdZjJRxSEeXDQkqFhCBgFhallLDbPwQOEQ4MHpiPfkjKk7zlmeNg== +"@typescript-eslint/type-utils@5.30.6": + version "5.30.6" + resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-5.30.6.tgz#a64aa9acbe609ab77f09f53434a6af2b9685f3af" + integrity sha512-GFVVzs2j0QPpM+NTDMXtNmJKlF842lkZKDSanIxf+ArJsGeZUIaeT4jGg+gAgHt7AcQSFwW7htzF/rbAh2jaVA== dependencies: - "@typescript-eslint/utils" "5.30.0" + "@typescript-eslint/utils" "5.30.6" debug "^4.3.4" tsutils "^3.21.0" -"@typescript-eslint/types@5.30.0": - version "5.30.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.30.0.tgz#db7d81d585a3da3801432a9c1d2fafbff125e110" - integrity sha512-vfqcBrsRNWw/LBXyncMF/KrUTYYzzygCSsVqlZ1qGu1QtGs6vMkt3US0VNSQ05grXi5Yadp3qv5XZdYLjpp8ag== +"@typescript-eslint/types@5.30.6": + version "5.30.6" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.30.6.tgz#86369d0a7af8c67024115ac1da3e8fb2d38907e1" + integrity sha512-HdnP8HioL1F7CwVmT4RaaMX57RrfqsOMclZc08wGMiDYJBsLGBM7JwXM4cZJmbWLzIR/pXg1kkrBBVpxTOwfUg== -"@typescript-eslint/typescript-estree@5.30.0": - version "5.30.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.30.0.tgz#4565ee8a6d2ac368996e20b2344ea0eab1a8f0bb" - integrity sha512-hDEawogreZB4n1zoqcrrtg/wPyyiCxmhPLpZ6kmWfKF5M5G0clRLaEexpuWr31fZ42F96SlD/5xCt1bT5Qm4Nw== +"@typescript-eslint/typescript-estree@5.30.6": + version "5.30.6" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.30.6.tgz#a84a0d6a486f9b54042da1de3d671a2c9f14484e" + integrity sha512-Z7TgPoeYUm06smfEfYF0RBkpF8csMyVnqQbLYiGgmUSTaSXTP57bt8f0UFXstbGxKIreTwQCujtaH0LY9w9B+A== dependencies: - "@typescript-eslint/types" "5.30.0" - "@typescript-eslint/visitor-keys" "5.30.0" + "@typescript-eslint/types" "5.30.6" + "@typescript-eslint/visitor-keys" "5.30.6" debug "^4.3.4" globby "^11.1.0" is-glob "^4.0.3" semver "^7.3.7" tsutils "^3.21.0" -"@typescript-eslint/utils@5.30.0": - version "5.30.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.30.0.tgz#1dac771fead5eab40d31860716de219356f5f754" - integrity sha512-0bIgOgZflLKIcZsWvfklsaQTM3ZUbmtH0rJ1hKyV3raoUYyeZwcjQ8ZUJTzS7KnhNcsVT1Rxs7zeeMHEhGlltw== +"@typescript-eslint/utils@5.30.6": + version "5.30.6" + resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.30.6.tgz#1de2da14f678e7d187daa6f2e4cdb558ed0609dc" + integrity sha512-xFBLc/esUbLOJLk9jKv0E9gD/OH966M40aY9jJ8GiqpSkP2xOV908cokJqqhVd85WoIvHVHYXxSFE4cCSDzVvA== dependencies: "@types/json-schema" "^7.0.9" - "@typescript-eslint/scope-manager" "5.30.0" - "@typescript-eslint/types" "5.30.0" - "@typescript-eslint/typescript-estree" "5.30.0" + "@typescript-eslint/scope-manager" "5.30.6" + "@typescript-eslint/types" "5.30.6" + "@typescript-eslint/typescript-estree" "5.30.6" eslint-scope "^5.1.1" eslint-utils "^3.0.0" -"@typescript-eslint/visitor-keys@5.30.0": - version "5.30.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.30.0.tgz#07721d23daca2ec4c2da7f1e660d41cd78bacac3" - integrity sha512-6WcIeRk2DQ3pHKxU1Ni0qMXJkjO/zLjBymlYBy/53qxe7yjEFSvzKLDToJjURUhSl2Fzhkl4SMXQoETauF74cw== +"@typescript-eslint/visitor-keys@5.30.6": + version "5.30.6" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.30.6.tgz#94dd10bb481c8083378d24de1742a14b38a2678c" + integrity sha512-41OiCjdL2mCaSDi2SvYbzFLlqqlm5v1ZW9Ym55wXKL/Rx6OOB1IbuFGo71Fj6Xy90gJDFTlgOS+vbmtGHPTQQA== dependencies: - "@typescript-eslint/types" "5.30.0" + "@typescript-eslint/types" "5.30.6" eslint-visitor-keys "^3.3.0" +"@ucans/core@*": + version "0.0.1-alpha2" + dependencies: + uint8arrays "^3.0.0" + abab@^2.0.3, abab@^2.0.5: version "2.0.6" resolved "https://registry.yarnpkg.com/abab/-/abab-2.0.6.tgz#41b80f2c871d19686216b82309231cfd3cb3d291" @@ -1058,14 +1063,14 @@ browser-process-hrtime@^1.0.0: integrity sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow== browserslist@^4.20.2: - version "4.21.0" - resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.21.0.tgz#7ab19572361a140ecd1e023e2c1ed95edda0cefe" - integrity sha512-UQxE0DIhRB5z/zDz9iA03BOfxaN2+GQdBYH/2WrSIWEUrnpzTPJbhqt+umq6r3acaPRTW1FNTkrcp0PXgtFkvA== + version "4.21.1" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.21.1.tgz#c9b9b0a54c7607e8dc3e01a0d311727188011a00" + integrity sha512-Nq8MFCSrnJXSc88yliwlzQe3qNe3VntIjhsArW9IJOEPSHNx23FalwApUVbzAWABLhYJJ7y8AynWI/XM8OdfjQ== dependencies: - caniuse-lite "^1.0.30001358" - electron-to-chromium "^1.4.164" + caniuse-lite "^1.0.30001359" + electron-to-chromium "^1.4.172" node-releases "^2.0.5" - update-browserslist-db "^1.0.0" + update-browserslist-db "^1.0.4" bs-logger@0.x: version "0.2.6" @@ -1101,10 +1106,10 @@ camelcase@^6.2.0: resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.3.0.tgz#5685b95eb209ac9c0c177467778c9c84df58ba9a" integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA== -caniuse-lite@^1.0.30001358: - version "1.0.30001359" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001359.tgz#a1c1cbe1c2da9e689638813618b4219acbd4925e" - integrity sha512-Xln/BAsPzEuiVLgJ2/45IaqD9jShtk3Y33anKb4+yLwQzws3+v6odKfpgES/cDEaZMLzSChpIGdbOYtH9MyuHw== +caniuse-lite@^1.0.30001359: + version "1.0.30001364" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001364.tgz#1e118f0e933ed2b79f8d461796b8ce45398014a0" + integrity sha512-9O0xzV3wVyX0SlegIQ6knz+okhBB5pE0PC40MNdwcipjwpxoUEHL24uJ+gG42cgklPjfO5ZjZPme9FTSN3QT2Q== chalk@^2.0.0: version "2.4.2" @@ -1326,10 +1331,10 @@ domexception@^2.0.1: dependencies: webidl-conversions "^5.0.0" -electron-to-chromium@^1.4.164: - version "1.4.170" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.170.tgz#0415fc489402e09bfbe1f0c99bbf4d73f31d48d4" - integrity sha512-rZ8PZLhK4ORPjFqLp9aqC4/S1j4qWFsPPz13xmWdrbBkU/LlxMcok+f+6f8YnQ57MiZwKtOaW15biZZsY5Igvw== +electron-to-chromium@^1.4.172: + version "1.4.185" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.185.tgz#3432d7944f1c5fe20664bb45d9cced2151405ce2" + integrity sha512-9kV/isoOGpKkBt04yYNaSWIBn3187Q5VZRtoReq8oz5NY/A4XmU6cAoqgQlDp7kKJCZMRjWZ8nsQyxfpFHvfyw== emittery@^0.8.1: version "0.8.1" @@ -1414,9 +1419,9 @@ eslint-visitor-keys@^3.3.0: integrity sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA== eslint@^8.12.0: - version "8.18.0" - resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.18.0.tgz#78d565d16c993d0b73968c523c0446b13da784fd" - integrity sha512-As1EfFMVk7Xc6/CvhssHUjsAQSkpfXvUGMFC3ce8JDe6WvqCgRrLOBQbVpsBFr1X1V+RACOadnzVvcUS5ni2bA== + version "8.19.0" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.19.0.tgz#7342a3cbc4fbc5c106a1eefe0fd0b50b6b1a7d28" + integrity sha512-SXOPj3x9VKvPe81TjjUJCYlV4oJjQw68Uek+AM0X4p+33dj2HY5bpTZOgnQHcG2eAm1mtCU9uNMnJi7exU/kYw== dependencies: "@eslint/eslintrc" "^1.3.0" "@humanwhocodes/config-array" "^0.9.2" @@ -1690,9 +1695,9 @@ globals@^11.1.0: integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA== globals@^13.15.0: - version "13.15.0" - resolved "https://registry.yarnpkg.com/globals/-/globals-13.15.0.tgz#38113218c907d2f7e98658af246cef8b77e90bac" - integrity sha512-bpzcOlgDhMG070Av0Vy5Owklpv1I6+j96GhUI7Rh7IzDCKLzboflLrrfqMu8NquDbiR4EOQk7XzJwqVJxicxog== + version "13.16.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-13.16.0.tgz#9be4aca28f311aaeb974ea54978ebbb5e35ce46a" + integrity sha512-A1lrQfpNF+McdPOnnFqY3kSN0AFTy485bTi1bkLk4mVPODIUEcSfhHgRqA+QdXPksrSTTztYXx37NFV+GpGk3Q== dependencies: type-fest "^0.20.2" @@ -2551,9 +2556,9 @@ node-int64@^0.4.0: integrity sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw== node-releases@^2.0.5: - version "2.0.5" - resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.5.tgz#280ed5bc3eba0d96ce44897d8aee478bfb3d9666" - integrity sha512-U9h1NLROZTq9uE1SNffn6WuPDg8icmi3ns4rEl/oTfIle4iLjTliCzgTsbaIFMq/Xn078/lfY/BL0GWZ+psK4Q== + version "2.0.6" + resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.6.tgz#8a7088c63a55e493845683ebf3c828d8c51c5503" + integrity sha512-PiVXnNuFm5+iYkLBNeq5211hvO38y63T0i2KKh2KnUs3RpzJ+JtODFjkD8yjLwnDkTYF1eKXheUwdssR+NRZdg== noms@0.0.0: version "0.0.0" @@ -2744,9 +2749,9 @@ prompts@^2.0.1: sisteransi "^1.0.5" psl@^1.1.33: - version "1.8.0" - resolved "https://registry.yarnpkg.com/psl/-/psl-1.8.0.tgz#9326f8bcfb013adcc005fdff056acce020e51c24" - integrity sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ== + version "1.9.0" + resolved "https://registry.yarnpkg.com/psl/-/psl-1.9.0.tgz#d0df2a137f00794565fcaf3b2c00cd09f8d5a5a7" + integrity sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag== punycode@^2.1.0, punycode@^2.1.1: version "2.1.1" @@ -3198,7 +3203,7 @@ untildify@^4.0.0: resolved "https://registry.yarnpkg.com/untildify/-/untildify-4.0.0.tgz#2bc947b953652487e4600949fb091e3ae8cd919b" integrity sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw== -update-browserslist-db@^1.0.0: +update-browserslist-db@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.0.4.tgz#dbfc5a789caa26b1db8990796c2c8ebbce304824" integrity sha512-jnmO2BEGUjsMOe/Fg9u0oczOe/ppIDZPebzccl1yDWGLFP16Pa1/RM5wEoKYPG2zstNcDuAStejyxsOuKINdGA== From 8e94b30350dea70c53564f6ae7115ff3db71aa3d Mon Sep 17 00:00:00 2001 From: dholms Date: Thu, 21 Jul 2022 11:44:41 -0500 Subject: [PATCH 22/25] bump dependencies --- yarn.lock | 294 +++++++++++++++++++++++++++--------------------------- 1 file changed, 147 insertions(+), 147 deletions(-) diff --git a/yarn.lock b/yarn.lock index c461f2b..7284840 100644 --- a/yarn.lock +++ b/yarn.lock @@ -17,63 +17,63 @@ dependencies: "@babel/highlight" "^7.18.6" -"@babel/compat-data@^7.18.6": +"@babel/compat-data@^7.18.8": version "7.18.8" resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.18.8.tgz#2483f565faca607b8535590e84e7de323f27764d" integrity sha512-HSmX4WZPPK3FUxYp7g2T6EyO8j96HlZJlxmKPSh6KAcqwyDrfx7hKjXpAW/0FhFfTJsR0Yt4lAjLI2coMptIHQ== "@babel/core@^7.1.0", "@babel/core@^7.12.3", "@babel/core@^7.7.2", "@babel/core@^7.8.0": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.18.6.tgz#54a107a3c298aee3fe5e1947a6464b9b6faca03d" - integrity sha512-cQbWBpxcbbs/IUredIPkHiAGULLV8iwgNRMFzvbhEXISp4f3rUUXE5+TIw6KwUWUR3DwyI6gmBRnmAtYaWehwQ== + version "7.18.9" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.18.9.tgz#805461f967c77ff46c74ca0460ccf4fe933ddd59" + integrity sha512-1LIb1eL8APMy91/IMW+31ckrfBM4yCoLaVzoDhZUKSM4cu1L1nIidyxkCgzPAgrC5WEz36IPEr/eSeSF9pIn+g== dependencies: "@ampproject/remapping" "^2.1.0" "@babel/code-frame" "^7.18.6" - "@babel/generator" "^7.18.6" - "@babel/helper-compilation-targets" "^7.18.6" - "@babel/helper-module-transforms" "^7.18.6" - "@babel/helpers" "^7.18.6" - "@babel/parser" "^7.18.6" + "@babel/generator" "^7.18.9" + "@babel/helper-compilation-targets" "^7.18.9" + "@babel/helper-module-transforms" "^7.18.9" + "@babel/helpers" "^7.18.9" + "@babel/parser" "^7.18.9" "@babel/template" "^7.18.6" - "@babel/traverse" "^7.18.6" - "@babel/types" "^7.18.6" + "@babel/traverse" "^7.18.9" + "@babel/types" "^7.18.9" convert-source-map "^1.7.0" debug "^4.1.0" gensync "^1.0.0-beta.2" json5 "^2.2.1" semver "^6.3.0" -"@babel/generator@^7.18.6", "@babel/generator@^7.18.7", "@babel/generator@^7.7.2": - version "7.18.7" - resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.18.7.tgz#2aa78da3c05aadfc82dbac16c99552fc802284bd" - integrity sha512-shck+7VLlY72a2w9c3zYWuE1pwOKEiQHV7GTUbSnhyl5eu3i04t30tBY82ZRWrDfo3gkakCFtevExnxbkf2a3A== +"@babel/generator@^7.18.9", "@babel/generator@^7.7.2": + version "7.18.9" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.18.9.tgz#68337e9ea8044d6ddc690fb29acae39359cca0a5" + integrity sha512-wt5Naw6lJrL1/SGkipMiFxJjtyczUWTP38deiP1PO60HsBjDeKk08CGC3S8iVuvf0FmTdgKwU1KIXzSKL1G0Ug== dependencies: - "@babel/types" "^7.18.7" + "@babel/types" "^7.18.9" "@jridgewell/gen-mapping" "^0.3.2" jsesc "^2.5.1" -"@babel/helper-compilation-targets@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.18.6.tgz#18d35bfb9f83b1293c22c55b3d576c1315b6ed96" - integrity sha512-vFjbfhNCzqdeAtZflUFrG5YIFqGTqsctrtkZ1D/NB0mDW9TwW3GmmUepYY4G9wCET5rY5ugz4OGTcLd614IzQg== +"@babel/helper-compilation-targets@^7.18.9": + version "7.18.9" + resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.18.9.tgz#69e64f57b524cde3e5ff6cc5a9f4a387ee5563bf" + integrity sha512-tzLCyVmqUiFlcFoAPLA/gL9TeYrF61VLNtb+hvkuVaB5SUjW7jcfrglBIX1vUIoT7CLP3bBlIMeyEsIl2eFQNg== dependencies: - "@babel/compat-data" "^7.18.6" + "@babel/compat-data" "^7.18.8" "@babel/helper-validator-option" "^7.18.6" browserslist "^4.20.2" semver "^6.3.0" -"@babel/helper-environment-visitor@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.6.tgz#b7eee2b5b9d70602e59d1a6cad7dd24de7ca6cd7" - integrity sha512-8n6gSfn2baOY+qlp+VSzsosjCVGFqWKmDF0cCWOybh52Dw3SEyoWR1KrhMJASjLwIEkkAufZ0xvr+SxLHSpy2Q== +"@babel/helper-environment-visitor@^7.18.9": + version "7.18.9" + resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.9.tgz#0c0cee9b35d2ca190478756865bb3528422f51be" + integrity sha512-3r/aACDJ3fhQ/EVgFy0hpj8oHyHpQc+LPtJoY9SzTThAsStm4Ptegq92vqKoE3vD706ZVFWITnMnxucw+S9Ipg== -"@babel/helper-function-name@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.18.6.tgz#8334fecb0afba66e6d87a7e8c6bb7fed79926b83" - integrity sha512-0mWMxV1aC97dhjCah5U5Ua7668r5ZmSC2DLfH2EZnf9c3/dHZKiFa5pRLMH5tjSl471tY6496ZWk/kjNONBxhw== +"@babel/helper-function-name@^7.18.9": + version "7.18.9" + resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.18.9.tgz#940e6084a55dee867d33b4e487da2676365e86b0" + integrity sha512-fJgWlZt7nxGksJS9a0XdSaI4XvpExnNIgRP+rVefWh5U7BL8pPuir6SJUmFKRfjWQ51OtWSzwOxhaH/EBWWc0A== dependencies: "@babel/template" "^7.18.6" - "@babel/types" "^7.18.6" + "@babel/types" "^7.18.9" "@babel/helper-hoist-variables@^7.18.6": version "7.18.6" @@ -89,24 +89,24 @@ dependencies: "@babel/types" "^7.18.6" -"@babel/helper-module-transforms@^7.18.6": - version "7.18.8" - resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.18.8.tgz#4f8408afead0188cfa48672f9d0e5787b61778c8" - integrity sha512-che3jvZwIcZxrwh63VfnFTUzcAM9v/lznYkkRxIBGMPt1SudOKHAEec0SIRCfiuIzTcF7VGj/CaTT6gY4eWxvA== +"@babel/helper-module-transforms@^7.18.9": + version "7.18.9" + resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.18.9.tgz#5a1079c005135ed627442df31a42887e80fcb712" + integrity sha512-KYNqY0ICwfv19b31XzvmI/mfcylOzbLtowkw+mfvGPAQ3kfCnMLYbED3YecL5tPd8nAYFQFAd6JHp2LxZk/J1g== dependencies: - "@babel/helper-environment-visitor" "^7.18.6" + "@babel/helper-environment-visitor" "^7.18.9" "@babel/helper-module-imports" "^7.18.6" "@babel/helper-simple-access" "^7.18.6" "@babel/helper-split-export-declaration" "^7.18.6" "@babel/helper-validator-identifier" "^7.18.6" "@babel/template" "^7.18.6" - "@babel/traverse" "^7.18.8" - "@babel/types" "^7.18.8" + "@babel/traverse" "^7.18.9" + "@babel/types" "^7.18.9" "@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.10.4", "@babel/helper-plugin-utils@^7.12.13", "@babel/helper-plugin-utils@^7.14.5", "@babel/helper-plugin-utils@^7.18.6", "@babel/helper-plugin-utils@^7.8.0": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.18.6.tgz#9448974dd4fb1d80fefe72e8a0af37809cd30d6d" - integrity sha512-gvZnm1YAAxh13eJdkb9EWHBnF3eAub3XTLCZEehHT2kWxiKVRL64+ae5Y6Ivne0mVHmMYKT+xWgZO+gQhuLUBg== + version "7.18.9" + resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.18.9.tgz#4b8aea3b069d8cb8a72cdfe28ddf5ceca695ef2f" + integrity sha512-aBXPT3bmtLryXaoJLyYPXPlSD4p1ld9aYeR+sJNOZjJJGiOpb+fKfh3NkcCu7J54nUJwCERPBExCCpyCOHnu/w== "@babel/helper-simple-access@^7.18.6": version "7.18.6" @@ -132,14 +132,14 @@ resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.18.6.tgz#bf0d2b5a509b1f336099e4ff36e1a63aa5db4db8" integrity sha512-XO7gESt5ouv/LRJdrVjkShckw6STTaB7l9BrpBaAHDeF5YZT+01PCwmR0SJHnkW6i8OwW/EVWRShfi4j2x+KQw== -"@babel/helpers@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.18.6.tgz#4c966140eaa1fcaa3d5a8c09d7db61077d4debfd" - integrity sha512-vzSiiqbQOghPngUYt/zWGvK3LAsPhz55vc9XNN0xAl2gV4ieShI2OQli5duxWHD+72PZPTKAcfcZDE1Cwc5zsQ== +"@babel/helpers@^7.18.9": + version "7.18.9" + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.18.9.tgz#4bef3b893f253a1eced04516824ede94dcfe7ff9" + integrity sha512-Jf5a+rbrLoR4eNdUmnFu8cN5eNJT6qdTdOg5IHIzq87WwyRw9PwguLFOWYgktN/60IP4fgDUawJvs7PjQIzELQ== dependencies: "@babel/template" "^7.18.6" - "@babel/traverse" "^7.18.6" - "@babel/types" "^7.18.6" + "@babel/traverse" "^7.18.9" + "@babel/types" "^7.18.9" "@babel/highlight@^7.18.6": version "7.18.6" @@ -150,10 +150,10 @@ chalk "^2.0.0" js-tokens "^4.0.0" -"@babel/parser@^7.1.0", "@babel/parser@^7.14.7", "@babel/parser@^7.18.6", "@babel/parser@^7.18.8": - version "7.18.8" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.18.8.tgz#822146080ac9c62dac0823bb3489622e0bc1cbdf" - integrity sha512-RSKRfYX20dyH+elbJK2uqAkVyucL+xXzhqlMD5/ZXx+dAAwpyB7HsvnHe/ZUGOF+xLr5Wx9/JoXVTj6BQE2/oA== +"@babel/parser@^7.1.0", "@babel/parser@^7.14.7", "@babel/parser@^7.18.6", "@babel/parser@^7.18.9": + version "7.18.9" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.18.9.tgz#f2dde0c682ccc264a9a8595efd030a5cc8fd2539" + integrity sha512-9uJveS9eY9DJ0t64YbIBZICtJy8a5QrDEVdiLCG97fVLpDTpGX7t8mMSb6OWw6Lrnjqj4O8zwjELX3dhoMgiBg== "@babel/plugin-syntax-async-generators@^7.8.4": version "7.8.4" @@ -255,26 +255,26 @@ "@babel/parser" "^7.18.6" "@babel/types" "^7.18.6" -"@babel/traverse@^7.18.6", "@babel/traverse@^7.18.8", "@babel/traverse@^7.7.2": - version "7.18.8" - resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.18.8.tgz#f095e62ab46abf1da35e5a2011f43aee72d8d5b0" - integrity sha512-UNg/AcSySJYR/+mIcJQDCv00T+AqRO7j/ZEJLzpaYtgM48rMg5MnkJgyNqkzo88+p4tfRvZJCEiwwfG6h4jkRg== +"@babel/traverse@^7.18.9", "@babel/traverse@^7.7.2": + version "7.18.9" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.18.9.tgz#deeff3e8f1bad9786874cb2feda7a2d77a904f98" + integrity sha512-LcPAnujXGwBgv3/WHv01pHtb2tihcyW1XuL9wd7jqh1Z8AQkTd+QVjMrMijrln0T7ED3UXLIy36P9Ao7W75rYg== dependencies: "@babel/code-frame" "^7.18.6" - "@babel/generator" "^7.18.7" - "@babel/helper-environment-visitor" "^7.18.6" - "@babel/helper-function-name" "^7.18.6" + "@babel/generator" "^7.18.9" + "@babel/helper-environment-visitor" "^7.18.9" + "@babel/helper-function-name" "^7.18.9" "@babel/helper-hoist-variables" "^7.18.6" "@babel/helper-split-export-declaration" "^7.18.6" - "@babel/parser" "^7.18.8" - "@babel/types" "^7.18.8" + "@babel/parser" "^7.18.9" + "@babel/types" "^7.18.9" debug "^4.1.0" globals "^11.1.0" -"@babel/types@^7.0.0", "@babel/types@^7.18.6", "@babel/types@^7.18.7", "@babel/types@^7.18.8", "@babel/types@^7.3.0", "@babel/types@^7.3.3": - version "7.18.8" - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.18.8.tgz#c5af199951bf41ba4a6a9a6d0d8ad722b30cd42f" - integrity sha512-qwpdsmraq0aJ3osLJRApsc2ouSJCdnMeZwB0DhbtHAtRpZNZCdlbRnHIgcRKzdE1g0iOGg644fzjOBcdOz9cPw== +"@babel/types@^7.0.0", "@babel/types@^7.18.6", "@babel/types@^7.18.9", "@babel/types@^7.3.0", "@babel/types@^7.3.3": + version "7.18.9" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.18.9.tgz#7148d64ba133d8d73a41b3172ac4b83a1452205f" + integrity sha512-WwMLAg2MvJmt/rKEVQBBhIVffMmnilX4oe0sRe7iPOHIGsqpruFHHdrfj4O1CMMtgMtCU4oPafZjDPCRgO57Wg== dependencies: "@babel/helper-validator-identifier" "^7.18.6" to-fast-properties "^2.0.0" @@ -734,9 +734,9 @@ integrity sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ== "@types/node@*": - version "18.0.3" - resolved "https://registry.yarnpkg.com/@types/node/-/node-18.0.3.tgz#463fc47f13ec0688a33aec75d078a0541a447199" - integrity sha512-HzNRZtp4eepNitP+BD6k2L6DROIDG4Q0fm4x+dwfsr6LGmROENnok75VGw40628xf+iR24WeMFcHuuBDUAzzsQ== + version "18.0.6" + resolved "https://registry.yarnpkg.com/@types/node/-/node-18.0.6.tgz#0ba49ac517ad69abe7a1508bc9b3a5483df9d5d7" + integrity sha512-/xUq6H2aQm261exT6iZTMifUySEt4GR5KX8eYyY+C4MSNPqSh9oNIP7tz2GLKTlFaiBbgZNxffoR3CVRG+cljw== "@types/node@^17.0.23": version "17.0.45" @@ -766,13 +766,13 @@ "@types/yargs-parser" "*" "@typescript-eslint/eslint-plugin@^5.18.0": - version "5.30.6" - resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.30.6.tgz#9c6017b6c1d04894141b4a87816388967f64c359" - integrity sha512-J4zYMIhgrx4MgnZrSDD7sEnQp7FmhKNOaqaOpaoQ/SfdMfRB/0yvK74hTnvH+VQxndZynqs5/Hn4t+2/j9bADg== + version "5.30.7" + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.30.7.tgz#1621dabc1ae4084310e19e9efc80dfdbb97e7493" + integrity sha512-l4L6Do+tfeM2OK0GJsU7TUcM/1oN/N25xHm3Jb4z3OiDU4Lj8dIuxX9LpVMS9riSXQs42D1ieX7b85/r16H9Fw== dependencies: - "@typescript-eslint/scope-manager" "5.30.6" - "@typescript-eslint/type-utils" "5.30.6" - "@typescript-eslint/utils" "5.30.6" + "@typescript-eslint/scope-manager" "5.30.7" + "@typescript-eslint/type-utils" "5.30.7" + "@typescript-eslint/utils" "5.30.7" debug "^4.3.4" functional-red-black-tree "^1.0.1" ignore "^5.2.0" @@ -781,68 +781,68 @@ tsutils "^3.21.0" "@typescript-eslint/parser@^5.18.0": - version "5.30.6" - resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.30.6.tgz#add440db038fa9d777e4ebdaf66da9e7fb7abe92" - integrity sha512-gfF9lZjT0p2ZSdxO70Xbw8w9sPPJGfAdjK7WikEjB3fcUI/yr9maUVEdqigBjKincUYNKOmf7QBMiTf719kbrA== + version "5.30.7" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.30.7.tgz#99d09729392aec9e64b1de45cd63cb81a4ddd980" + integrity sha512-Rg5xwznHWWSy7v2o0cdho6n+xLhK2gntImp0rJroVVFkcYFYQ8C8UJTSuTw/3CnExBmPjycjmUJkxVmjXsld6A== dependencies: - "@typescript-eslint/scope-manager" "5.30.6" - "@typescript-eslint/types" "5.30.6" - "@typescript-eslint/typescript-estree" "5.30.6" + "@typescript-eslint/scope-manager" "5.30.7" + "@typescript-eslint/types" "5.30.7" + "@typescript-eslint/typescript-estree" "5.30.7" debug "^4.3.4" -"@typescript-eslint/scope-manager@5.30.6": - version "5.30.6" - resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.30.6.tgz#ce1b49ff5ce47f55518d63dbe8fc9181ddbd1a33" - integrity sha512-Hkq5PhLgtVoW1obkqYH0i4iELctEKixkhWLPTYs55doGUKCASvkjOXOd/pisVeLdO24ZX9D6yymJ/twqpJiG3g== +"@typescript-eslint/scope-manager@5.30.7": + version "5.30.7" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.30.7.tgz#8269a931ef1e5ae68b5eb80743cc515c4ffe3dd7" + integrity sha512-7BM1bwvdF1UUvt+b9smhqdc/eniOnCKxQT/kj3oXtj3LqnTWCAM0qHRHfyzCzhEfWX0zrW7KqXXeE4DlchZBKw== dependencies: - "@typescript-eslint/types" "5.30.6" - "@typescript-eslint/visitor-keys" "5.30.6" + "@typescript-eslint/types" "5.30.7" + "@typescript-eslint/visitor-keys" "5.30.7" -"@typescript-eslint/type-utils@5.30.6": - version "5.30.6" - resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-5.30.6.tgz#a64aa9acbe609ab77f09f53434a6af2b9685f3af" - integrity sha512-GFVVzs2j0QPpM+NTDMXtNmJKlF842lkZKDSanIxf+ArJsGeZUIaeT4jGg+gAgHt7AcQSFwW7htzF/rbAh2jaVA== +"@typescript-eslint/type-utils@5.30.7": + version "5.30.7" + resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-5.30.7.tgz#5693dc3db6f313f302764282d614cfdbc8a9fcfd" + integrity sha512-nD5qAE2aJX/YLyKMvOU5jvJyku4QN5XBVsoTynFrjQZaDgDV6i7QHFiYCx10wvn7hFvfuqIRNBtsgaLe0DbWhw== dependencies: - "@typescript-eslint/utils" "5.30.6" + "@typescript-eslint/utils" "5.30.7" debug "^4.3.4" tsutils "^3.21.0" -"@typescript-eslint/types@5.30.6": - version "5.30.6" - resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.30.6.tgz#86369d0a7af8c67024115ac1da3e8fb2d38907e1" - integrity sha512-HdnP8HioL1F7CwVmT4RaaMX57RrfqsOMclZc08wGMiDYJBsLGBM7JwXM4cZJmbWLzIR/pXg1kkrBBVpxTOwfUg== +"@typescript-eslint/types@5.30.7": + version "5.30.7" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.30.7.tgz#18331487cc92d0f1fb1a6f580c8ec832528079d0" + integrity sha512-ocVkETUs82+U+HowkovV6uxf1AnVRKCmDRNUBUUo46/5SQv1owC/EBFkiu4MOHeZqhKz2ktZ3kvJJ1uFqQ8QPg== -"@typescript-eslint/typescript-estree@5.30.6": - version "5.30.6" - resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.30.6.tgz#a84a0d6a486f9b54042da1de3d671a2c9f14484e" - integrity sha512-Z7TgPoeYUm06smfEfYF0RBkpF8csMyVnqQbLYiGgmUSTaSXTP57bt8f0UFXstbGxKIreTwQCujtaH0LY9w9B+A== +"@typescript-eslint/typescript-estree@5.30.7": + version "5.30.7" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.30.7.tgz#05da9f1b281985bfedcf62349847f8d168eecc07" + integrity sha512-tNslqXI1ZdmXXrHER83TJ8OTYl4epUzJC0aj2i4DMDT4iU+UqLT3EJeGQvJ17BMbm31x5scSwo3hPM0nqQ1AEA== dependencies: - "@typescript-eslint/types" "5.30.6" - "@typescript-eslint/visitor-keys" "5.30.6" + "@typescript-eslint/types" "5.30.7" + "@typescript-eslint/visitor-keys" "5.30.7" debug "^4.3.4" globby "^11.1.0" is-glob "^4.0.3" semver "^7.3.7" tsutils "^3.21.0" -"@typescript-eslint/utils@5.30.6": - version "5.30.6" - resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.30.6.tgz#1de2da14f678e7d187daa6f2e4cdb558ed0609dc" - integrity sha512-xFBLc/esUbLOJLk9jKv0E9gD/OH966M40aY9jJ8GiqpSkP2xOV908cokJqqhVd85WoIvHVHYXxSFE4cCSDzVvA== +"@typescript-eslint/utils@5.30.7": + version "5.30.7" + resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.30.7.tgz#7135be070349e9f7caa262b0ca59dc96123351bb" + integrity sha512-Z3pHdbFw+ftZiGUnm1GZhkJgVqsDL5CYW2yj+TB2mfXDFOMqtbzQi2dNJIyPqPbx9mv2kUxS1gU+r2gKlKi1rQ== dependencies: "@types/json-schema" "^7.0.9" - "@typescript-eslint/scope-manager" "5.30.6" - "@typescript-eslint/types" "5.30.6" - "@typescript-eslint/typescript-estree" "5.30.6" + "@typescript-eslint/scope-manager" "5.30.7" + "@typescript-eslint/types" "5.30.7" + "@typescript-eslint/typescript-estree" "5.30.7" eslint-scope "^5.1.1" eslint-utils "^3.0.0" -"@typescript-eslint/visitor-keys@5.30.6": - version "5.30.6" - resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.30.6.tgz#94dd10bb481c8083378d24de1742a14b38a2678c" - integrity sha512-41OiCjdL2mCaSDi2SvYbzFLlqqlm5v1ZW9Ym55wXKL/Rx6OOB1IbuFGo71Fj6Xy90gJDFTlgOS+vbmtGHPTQQA== +"@typescript-eslint/visitor-keys@5.30.7": + version "5.30.7" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.30.7.tgz#c093abae75b4fd822bfbad9fc337f38a7a14909a" + integrity sha512-KrRXf8nnjvcpxDFOKej4xkD7657+PClJs5cJVSG7NNoCNnjEdc46juNAQt7AyuWctuCgs6mVRc1xGctEqrjxWw== dependencies: - "@typescript-eslint/types" "5.30.6" + "@typescript-eslint/types" "5.30.7" eslint-visitor-keys "^3.3.0" "@ucans/core@*": @@ -884,9 +884,9 @@ acorn@^7.1.1: integrity sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A== acorn@^8.2.4, acorn@^8.4.1, acorn@^8.7.1: - version "8.7.1" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.7.1.tgz#0197122c843d1bf6d0a5e83220a788f278f63c30" - integrity sha512-Xx54uLJQZ19lKygFXOWsscKUbsBZW0CPykPhVQdhIeIwrbPmJzqeASDInc8nKBnp/JT6igTs82qPXz069H8I/A== + version "8.8.0" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.8.0.tgz#88c0187620435c7f6015803f5539dae05a9dbea8" + integrity sha512-QOxyigPVrpZ2GXT+PFyZTl6TtOFc5egxHIP9IlQ+RbupQuX4RkT/Bee4/kQuC02Xkzg84JcT7oLYtDIQxp+v7w== agent-base@6: version "6.0.2" @@ -1063,13 +1063,13 @@ browser-process-hrtime@^1.0.0: integrity sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow== browserslist@^4.20.2: - version "4.21.1" - resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.21.1.tgz#c9b9b0a54c7607e8dc3e01a0d311727188011a00" - integrity sha512-Nq8MFCSrnJXSc88yliwlzQe3qNe3VntIjhsArW9IJOEPSHNx23FalwApUVbzAWABLhYJJ7y8AynWI/XM8OdfjQ== + version "4.21.2" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.21.2.tgz#59a400757465535954946a400b841ed37e2b4ecf" + integrity sha512-MonuOgAtUB46uP5CezYbRaYKBNt2LxP0yX+Pmj4LkcDFGkn9Cbpi83d9sCjwQDErXsIJSzY5oKGDbgOlF/LPAA== dependencies: - caniuse-lite "^1.0.30001359" - electron-to-chromium "^1.4.172" - node-releases "^2.0.5" + caniuse-lite "^1.0.30001366" + electron-to-chromium "^1.4.188" + node-releases "^2.0.6" update-browserslist-db "^1.0.4" bs-logger@0.x: @@ -1106,10 +1106,10 @@ camelcase@^6.2.0: resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.3.0.tgz#5685b95eb209ac9c0c177467778c9c84df58ba9a" integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA== -caniuse-lite@^1.0.30001359: - version "1.0.30001364" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001364.tgz#1e118f0e933ed2b79f8d461796b8ce45398014a0" - integrity sha512-9O0xzV3wVyX0SlegIQ6knz+okhBB5pE0PC40MNdwcipjwpxoUEHL24uJ+gG42cgklPjfO5ZjZPme9FTSN3QT2Q== +caniuse-lite@^1.0.30001366: + version "1.0.30001368" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001368.tgz#c5c06381c6051cd863c45021475434e81936f713" + integrity sha512-wgfRYa9DenEomLG/SdWgQxpIyvdtH3NW8Vq+tB6AwR9e56iOIcu1im5F/wNdDf04XlKHXqIx4N8Jo0PemeBenQ== chalk@^2.0.0: version "2.4.2" @@ -1331,10 +1331,10 @@ domexception@^2.0.1: dependencies: webidl-conversions "^5.0.0" -electron-to-chromium@^1.4.172: - version "1.4.185" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.185.tgz#3432d7944f1c5fe20664bb45d9cced2151405ce2" - integrity sha512-9kV/isoOGpKkBt04yYNaSWIBn3187Q5VZRtoReq8oz5NY/A4XmU6cAoqgQlDp7kKJCZMRjWZ8nsQyxfpFHvfyw== +electron-to-chromium@^1.4.188: + version "1.4.196" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.196.tgz#e18cdc5c1c2c2ebf78da237d0c374cc3b244d4cb" + integrity sha512-uxMa/Dt7PQsLBVXwH+t6JvpHJnrsYBaxWKi/J6HE+/nBtoHENhwBoNkgkm226/Kfxeg0z1eMQLBRPPKcDH8xWA== emittery@^0.8.1: version "0.8.1" @@ -1419,9 +1419,9 @@ eslint-visitor-keys@^3.3.0: integrity sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA== eslint@^8.12.0: - version "8.19.0" - resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.19.0.tgz#7342a3cbc4fbc5c106a1eefe0fd0b50b6b1a7d28" - integrity sha512-SXOPj3x9VKvPe81TjjUJCYlV4oJjQw68Uek+AM0X4p+33dj2HY5bpTZOgnQHcG2eAm1mtCU9uNMnJi7exU/kYw== + version "8.20.0" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.20.0.tgz#048ac56aa18529967da8354a478be4ec0a2bc81b" + integrity sha512-d4ixhz5SKCa1D6SCPrivP7yYVi7nyD6A4vs6HIAul9ujBzcEmZVM3/0NN/yu5nKhmO1wjp5xQ46iRfmDGlOviA== dependencies: "@eslint/eslintrc" "^1.3.0" "@humanwhocodes/config-array" "^0.9.2" @@ -1695,9 +1695,9 @@ globals@^11.1.0: integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA== globals@^13.15.0: - version "13.16.0" - resolved "https://registry.yarnpkg.com/globals/-/globals-13.16.0.tgz#9be4aca28f311aaeb974ea54978ebbb5e35ce46a" - integrity sha512-A1lrQfpNF+McdPOnnFqY3kSN0AFTy485bTi1bkLk4mVPODIUEcSfhHgRqA+QdXPksrSTTztYXx37NFV+GpGk3Q== + version "13.17.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-13.17.0.tgz#902eb1e680a41da93945adbdcb5a9f361ba69bd4" + integrity sha512-1C+6nQRb1GwGMKm2dH/E7enFAMxGTmGI7/dEdhy/DNelv85w9B72t3uc5frtMNXIbzrarJJ/lTCjcaZwbLJmyw== dependencies: type-fest "^0.20.2" @@ -1919,9 +1919,9 @@ istanbul-lib-source-maps@^4.0.0: source-map "^0.6.1" istanbul-reports@^3.1.3: - version "3.1.4" - resolved "https://registry.yarnpkg.com/istanbul-reports/-/istanbul-reports-3.1.4.tgz#1b6f068ecbc6c331040aab5741991273e609e40c" - integrity sha512-r1/DshN4KSE7xWEknZLLLLDn5CJybV3nw01VTkp6D5jzLuELlcbudfj/eSQFvrKsJuTVCGnePO7ho82Nw9zzfw== + version "3.1.5" + resolved "https://registry.yarnpkg.com/istanbul-reports/-/istanbul-reports-3.1.5.tgz#cc9a6ab25cb25659810e4785ed9d9fb742578bae" + integrity sha512-nUsEMa9pBt/NOHqbcbeJEgqIlY/K7rVWUX6Lql2orY5e9roQOthbR3vtY4zzf2orPELg80fnxxk9zUyPlgwD1w== dependencies: html-escaper "^2.0.0" istanbul-lib-report "^3.0.0" @@ -2555,7 +2555,7 @@ node-int64@^0.4.0: resolved "https://registry.yarnpkg.com/node-int64/-/node-int64-0.4.0.tgz#87a9065cdb355d3182d8f94ce11188b825c68a3b" integrity sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw== -node-releases@^2.0.5: +node-releases@^2.0.6: version "2.0.6" resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.6.tgz#8a7088c63a55e493845683ebf3c828d8c51c5503" integrity sha512-PiVXnNuFm5+iYkLBNeq5211hvO38y63T0i2KKh2KnUs3RpzJ+JtODFjkD8yjLwnDkTYF1eKXheUwdssR+NRZdg== @@ -3115,9 +3115,9 @@ ts-jest@^27.1.4: yargs-parser "20.x" ts-node@^10.8.2: - version "10.8.2" - resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-10.8.2.tgz#3185b75228cef116bf82ffe8762594f54b2a23f2" - integrity sha512-LYdGnoGddf1D6v8REPtIH+5iq/gTDuZqv2/UJUU7tKjuEU8xVZorBM+buCGNjj+pGEud+sOoM4CX3/YzINpENA== + version "10.9.1" + resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-10.9.1.tgz#e73de9102958af9e1f0b168a6ff320e25adcff4b" + integrity sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw== dependencies: "@cspotcode/source-map-support" "^0.8.0" "@tsconfig/node10" "^1.0.7" @@ -3204,9 +3204,9 @@ untildify@^4.0.0: integrity sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw== update-browserslist-db@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.0.4.tgz#dbfc5a789caa26b1db8990796c2c8ebbce304824" - integrity sha512-jnmO2BEGUjsMOe/Fg9u0oczOe/ppIDZPebzccl1yDWGLFP16Pa1/RM5wEoKYPG2zstNcDuAStejyxsOuKINdGA== + version "1.0.5" + resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.0.5.tgz#be06a5eedd62f107b7c19eb5bcefb194411abf38" + integrity sha512-dteFFpCyvuDdr9S/ff1ISkKt/9YZxKjI9WlRR99c180GaztJtRa/fn18FdxGVKVsnPY7/a/FDN68mcvUmP4U7Q== dependencies: escalade "^3.1.1" picocolors "^1.0.0" @@ -3331,9 +3331,9 @@ write-file-atomic@^3.0.0: typedarray-to-buffer "^3.1.5" ws@^7.4.6: - version "7.5.8" - resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.8.tgz#ac2729881ab9e7cbaf8787fe3469a48c5c7f636a" - integrity sha512-ri1Id1WinAX5Jqn9HejiGb8crfRio0Qgu8+MtL36rlTA6RLsMdWt1Az/19A2Qij6uSHUMphEFaTKa4WG+UNHNw== + version "7.5.9" + resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.9.tgz#54fa7db29f4c7cec68b1ddd3a89de099942bb591" + integrity sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q== xml-name-validator@^3.0.0: version "3.0.0" From f3997fe9dcaac771a25f123aa5cca2eb2e9eb2b6 Mon Sep 17 00:00:00 2001 From: dholms Date: Thu, 21 Jul 2022 11:51:15 -0500 Subject: [PATCH 23/25] no alpha version --- packages/core/package.json | 2 +- yarn.lock | 5 ----- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/packages/core/package.json b/packages/core/package.json index 8e53887..9b5dec3 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,6 +1,6 @@ { "name": "@ucans/core", - "version": "0.0.1-alpha2", + "version": "0.9.1", "description": "Core UCAN implementation", "author": "Daniel Holmgren ", "repository": { diff --git a/yarn.lock b/yarn.lock index 7284840..9812576 100644 --- a/yarn.lock +++ b/yarn.lock @@ -845,11 +845,6 @@ "@typescript-eslint/types" "5.30.7" eslint-visitor-keys "^3.3.0" -"@ucans/core@*": - version "0.0.1-alpha2" - dependencies: - uint8arrays "^3.0.0" - abab@^2.0.3, abab@^2.0.5: version "2.0.6" resolved "https://registry.yarnpkg.com/abab/-/abab-2.0.6.tgz#41b80f2c871d19686216b82309231cfd3cb3d291" From 26352bb98b81d43698f9759ca3f7549a3c6fb3e3 Mon Sep 17 00:00:00 2001 From: dholms Date: Thu, 21 Jul 2022 12:05:00 -0500 Subject: [PATCH 24/25] workspace cmds --- .github/workflows/main.yml | 5 ++++- package.json | 5 +++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index c6d839e..58cbc1a 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -46,7 +46,10 @@ jobs: - name: Install Dependencies run: yarn install --frozen-lockfile --network-concurrency 1 - - name: Build & Test + - name: Build + run: yarn build + + - name: Test run: yarn test # Run lint after testing for more info diff --git a/package.json b/package.json index d8a1bd8..269d17b 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,11 @@ "engines": { "node": ">=15" }, + "scripts": { + "build": "yarn workspace @ucans/core build && yarn workspace @ucans/default-plugins build && yarn workspace @ucans/ucans build", + "test": "yarn workspace @ucans/core test && yarn workspace @ucans/default-plugins test && yarn workspace @ucans/ucans test", + "lint": "yarn workspace @ucans/core lint && yarn workspace @ucans/default-plugins lint && yarn workspace @ucans/ucans lint" + }, "private": true, "workspaces": [ "packages/*" From 61f44172a00772413a3c09d7599a9beb777b16d2 Mon Sep 17 00:00:00 2001 From: dholms Date: Fri, 22 Jul 2022 11:08:41 -0500 Subject: [PATCH 25/25] update version --- package.json | 1 - packages/core/package.json | 2 +- packages/default-plugins/package.json | 2 +- packages/ucans/package.json | 2 +- 4 files changed, 3 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index 269d17b..46656b5 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,5 @@ { "name": "ucans", - "version": "0.9.1", "description": "Typescript implementation of UCANs", "author": "Daniel Holmgren ", "repository": { diff --git a/packages/core/package.json b/packages/core/package.json index 9b5dec3..9b53967 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,6 +1,6 @@ { "name": "@ucans/core", - "version": "0.9.1", + "version": "0.11.0", "description": "Core UCAN implementation", "author": "Daniel Holmgren ", "repository": { diff --git a/packages/default-plugins/package.json b/packages/default-plugins/package.json index 189bafa..c39b4dc 100644 --- a/packages/default-plugins/package.json +++ b/packages/default-plugins/package.json @@ -1,6 +1,6 @@ { "name": "@ucans/default-plugins", - "version": "0.9.1", + "version": "0.11.0", "description": "Default UCAN plugin set", "author": "Daniel Holmgren ", "repository": { diff --git a/packages/ucans/package.json b/packages/ucans/package.json index 67cc0c0..b704f41 100644 --- a/packages/ucans/package.json +++ b/packages/ucans/package.json @@ -1,6 +1,6 @@ { "name": "@ucans/ucans", - "version": "0.9.1", + "version": "0.11.0", "description": "Typescript implementation of UCANs with default plugins", "author": "Daniel Holmgren ", "repository": {