@tinycloud/sdk-core 2.11.0 → 2.11.1-beta.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{unified-BlXXf3l7.d.cts → credential-admission-C_cO1QqB.d.cts} +353 -1
- package/dist/{unified-HWqC-h7O.d.ts → credential-admission-cvHe-HUC.d.ts} +353 -1
- package/dist/delegations/index.cjs +10 -10
- package/dist/delegations/index.cjs.map +1 -1
- package/dist/delegations/index.d.cts +2 -2
- package/dist/delegations/index.d.ts +2 -2
- package/dist/delegations/index.js +10 -10
- package/dist/delegations/index.js.map +1 -1
- package/dist/index.cjs +534 -167
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +6 -242
- package/dist/index.d.ts +6 -242
- package/dist/index.js +536 -169
- package/dist/index.js.map +1 -1
- package/dist/policy/index.cjs +420 -14
- package/dist/policy/index.cjs.map +1 -1
- package/dist/policy/index.d.cts +2 -2
- package/dist/policy/index.d.ts +2 -2
- package/dist/policy/index.js +420 -14
- package/dist/policy/index.js.map +1 -1
- package/dist/{recipient-types.schema-CieclU3Z.d.cts → recipient-types.schema-G8NEKRoo.d.cts} +8 -8
- package/dist/{recipient-types.schema-CieclU3Z.d.ts → recipient-types.schema-G8NEKRoo.d.ts} +8 -8
- package/package.json +1 -1
package/dist/policy/index.cjs
CHANGED
|
@@ -33,13 +33,18 @@ __export(policy_exports, {
|
|
|
33
33
|
OWNER_NODE_ENDPOINT_SCHEMA: () => OWNER_NODE_ENDPOINT_SCHEMA,
|
|
34
34
|
POLICY_CAPABILITY_V1_DOMAIN: () => POLICY_CAPABILITY_V1_DOMAIN,
|
|
35
35
|
POLICY_CAPABILITY_V1_SCHEMA: () => POLICY_CAPABILITY_V1_SCHEMA,
|
|
36
|
+
POLICY_CREDENTIAL_REQUIREMENT_V1_TYPE: () => POLICY_CREDENTIAL_REQUIREMENT_V1_TYPE,
|
|
36
37
|
POLICY_ENFORCEMENT_DELEGATION_V2_SCHEMA: () => POLICY_ENFORCEMENT_DELEGATION_V2_SCHEMA,
|
|
37
38
|
POLICY_ENGINE_RECORD_SCHEMA: () => POLICY_ENGINE_RECORD_SCHEMA,
|
|
39
|
+
POLICY_PRESENTATION_V3_DOMAIN: () => POLICY_PRESENTATION_V3_DOMAIN,
|
|
40
|
+
POLICY_PRESENTATION_V3_SCHEMA: () => POLICY_PRESENTATION_V3_SCHEMA,
|
|
38
41
|
POLICY_SCHEMA: () => POLICY_SCHEMA,
|
|
39
42
|
POLICY_SESSION_UCAN_V1_PROFILE: () => POLICY_SESSION_UCAN_V1_PROFILE,
|
|
40
43
|
POLICY_STATUS_SCHEMA: () => POLICY_STATUS_SCHEMA,
|
|
41
44
|
POLICY_V1_DOMAIN: () => POLICY_V1_DOMAIN,
|
|
42
45
|
POLICY_V1_SCHEMA: () => POLICY_V1_SCHEMA,
|
|
46
|
+
POLICY_V2_DOMAIN: () => POLICY_V2_DOMAIN,
|
|
47
|
+
POLICY_V2_SCHEMA: () => POLICY_V2_SCHEMA,
|
|
43
48
|
POLICY_VERSION_V0: () => POLICY_VERSION_V0,
|
|
44
49
|
PolicyAuthoringError: () => PolicyAuthoringError,
|
|
45
50
|
PolicyCapabilityError: () => PolicyCapabilityError,
|
|
@@ -59,6 +64,8 @@ __export(policy_exports, {
|
|
|
59
64
|
TRANSCRIPT_SHARE_BOOTSTRAP_SCHEMA: () => TRANSCRIPT_SHARE_BOOTSTRAP_SCHEMA,
|
|
60
65
|
UnsupportedSignatureSuiteError: () => UnsupportedSignatureSuiteError,
|
|
61
66
|
W3C_VC_CREDENTIAL_VERIFIER: () => W3C_VC_CREDENTIAL_VERIFIER,
|
|
67
|
+
admitPolicyCredentialV3: () => admitPolicyCredentialV3,
|
|
68
|
+
buildPolicyCredentialPresentationV3: () => buildPolicyCredentialPresentationV3,
|
|
62
69
|
canonicalizePolicyCapability: () => canonicalizePolicyCapability,
|
|
63
70
|
canonicalizeSignedObjectUnsigned: () => canonicalizeSignedObjectUnsigned,
|
|
64
71
|
canonicalizeUnifiedPolicyCapability: () => canonicalizeUnifiedPolicyCapability,
|
|
@@ -88,6 +95,7 @@ __export(policy_exports, {
|
|
|
88
95
|
policyCidFromCanonicalBytes: () => policyCidFromCanonicalBytes,
|
|
89
96
|
policyDigestHex: () => policyDigestHex,
|
|
90
97
|
policyIdForDigestHex: () => policyIdForDigestHex,
|
|
98
|
+
policyV2DigestHex: () => policyV2DigestHex,
|
|
91
99
|
projectUnifiedPolicyCapabilities: () => projectUnifiedPolicyCapabilities,
|
|
92
100
|
projectUnifiedPolicyCapability: () => projectUnifiedPolicyCapability,
|
|
93
101
|
renewPolicyRootStatusV3: () => renewPolicyRootStatusV3,
|
|
@@ -95,12 +103,14 @@ __export(policy_exports, {
|
|
|
95
103
|
revokePolicyRootV3: () => revokePolicyRootV3,
|
|
96
104
|
serializeJcsJson: () => serialize,
|
|
97
105
|
signCompactUcanAuthorization: () => signCompactUcanAuthorization,
|
|
106
|
+
signCompactUcanRootAuthorization: () => signCompactUcanRootAuthorization,
|
|
98
107
|
signedObjectIdFor: () => signedObjectIdFor,
|
|
99
108
|
toSignedObjectError: () => toSignedObjectError,
|
|
100
109
|
unifiedNativeProjectionHashHex: () => unifiedNativeProjectionHashHex,
|
|
101
110
|
unifiedPolicyCapabilityContains: () => unifiedPolicyCapabilityContains,
|
|
102
111
|
unifiedPolicyCapabilityDigestHex: () => unifiedPolicyCapabilityDigestHex,
|
|
103
112
|
unifiedPolicyCapabilityFromNative: () => unifiedPolicyCapabilityFromNative,
|
|
113
|
+
validatePolicyCredentialRequirementV1: () => validatePolicyCredentialRequirementV1,
|
|
104
114
|
validatePolicyEngineRecordSigned: () => validatePolicyEngineRecordSigned,
|
|
105
115
|
validatePolicyEngineRecordSignedShape: () => validatePolicyEngineRecordSignedShape,
|
|
106
116
|
validatePolicyEngineRecordUnsigned: () => validatePolicyEngineRecordUnsigned,
|
|
@@ -110,6 +120,7 @@ __export(policy_exports, {
|
|
|
110
120
|
validatePolicyStatusSignedShape: () => validatePolicyStatusSignedShape,
|
|
111
121
|
validatePolicyStatusUnsigned: () => validatePolicyStatusUnsigned,
|
|
112
122
|
validatePolicyUnsigned: () => validatePolicyUnsigned,
|
|
123
|
+
validateUnifiedPolicyV2: () => validateUnifiedPolicyV2,
|
|
113
124
|
verifyPolicy: () => verifyPolicy,
|
|
114
125
|
verifyPolicyEngineRecord: () => verifyPolicyEngineRecord,
|
|
115
126
|
verifyPolicyEngineRecordForRequester: () => verifyPolicyEngineRecordForRequester,
|
|
@@ -2778,13 +2789,13 @@ function base64UrlDecode(value) {
|
|
|
2778
2789
|
return decoded;
|
|
2779
2790
|
}
|
|
2780
2791
|
function hexToBytes(value) {
|
|
2781
|
-
const
|
|
2782
|
-
if (
|
|
2792
|
+
const hex3 = value.slice(2);
|
|
2793
|
+
if (hex3.length % 2 !== 0 || !/^[0-9a-fA-F]*$/.test(hex3)) {
|
|
2783
2794
|
throw new SignatureMaterialError("hex signature must have even length");
|
|
2784
2795
|
}
|
|
2785
|
-
const output = new Uint8Array(
|
|
2796
|
+
const output = new Uint8Array(hex3.length / 2);
|
|
2786
2797
|
for (let index = 0; index < output.length; index++) {
|
|
2787
|
-
output[index] = Number.parseInt(
|
|
2798
|
+
output[index] = Number.parseInt(hex3.slice(index * 2, index * 2 + 2), 16);
|
|
2788
2799
|
}
|
|
2789
2800
|
return output;
|
|
2790
2801
|
}
|
|
@@ -3540,11 +3551,15 @@ function nonEmptyString(value, label) {
|
|
|
3540
3551
|
return value;
|
|
3541
3552
|
}
|
|
3542
3553
|
function parseKvResource(resource) {
|
|
3543
|
-
const
|
|
3544
|
-
|
|
3554
|
+
const marker = resource.indexOf("/kv/");
|
|
3555
|
+
const encodedSpace = marker < 1 ? "" : resource.slice(0, marker);
|
|
3556
|
+
const legacy = encodedSpace.startsWith("tinycloud://");
|
|
3557
|
+
const space = legacy ? encodedSpace.slice("tinycloud://".length) : encodedSpace;
|
|
3558
|
+
const path = marker < 1 ? "" : resource.slice(marker + 4);
|
|
3559
|
+
if (marker < 1 || (legacy ? /[:/?#%]/.test(space) : !encodedSpace.startsWith("tinycloud:") || /[/?#%]/.test(encodedSpace)) || path.startsWith("/") || path.endsWith("/") || path.includes("//") || path.split("/").some((part) => part === "." || part === ".." || part.length === 0)) {
|
|
3545
3560
|
throw new Error("KV resource is not canonical");
|
|
3546
3561
|
}
|
|
3547
|
-
return { space
|
|
3562
|
+
return { space, path };
|
|
3548
3563
|
}
|
|
3549
3564
|
function parseEncryptionResource(resource) {
|
|
3550
3565
|
if (!resource.startsWith("urn:tinycloud:encryption:")) throw new Error("encryption capability is invalid");
|
|
@@ -3689,7 +3704,7 @@ function unifiedPolicyCapabilityFromNative(input) {
|
|
|
3689
3704
|
throw new Error("native capability is not supported");
|
|
3690
3705
|
const caveat = object(value.caveat, "native.caveat");
|
|
3691
3706
|
exactKeys(caveat, ["type", "kind", "value"], "native selector caveat");
|
|
3692
|
-
if (caveat.type !== "xyz.tinycloud.resource/selector" || caveat.kind !== "exact" && caveat.kind !== "prefix" || caveat.value !== `tinycloud://${value.space}/kv/${value.path}`)
|
|
3707
|
+
if (caveat.type !== "xyz.tinycloud.resource/selector" || caveat.kind !== "exact" && caveat.kind !== "prefix" || caveat.value !== `${value.space.startsWith("tinycloud:") ? value.space : `tinycloud://${value.space}`}/kv/${value.path}`)
|
|
3693
3708
|
throw new Error("native selector caveat does not match capability");
|
|
3694
3709
|
return normalizeUnifiedPolicyCapability({
|
|
3695
3710
|
kind: "kv",
|
|
@@ -3748,9 +3763,7 @@ var CompactPayloadSchema = import_zod.z.object({
|
|
|
3748
3763
|
nnc: import_zod.z.string().min(1),
|
|
3749
3764
|
prf: import_zod.z.array(import_zod.z.string().min(1))
|
|
3750
3765
|
}).strict();
|
|
3751
|
-
async function
|
|
3752
|
-
if (input.notBefore >= input.expiresAt || input.expiresAt - input.notBefore > 60)
|
|
3753
|
-
throw new Error("compact invocation lifetime must be between one and 60 seconds");
|
|
3766
|
+
async function signCompactUcan(input) {
|
|
3754
3767
|
const principal = input.issuerDid.split("#", 1)[0];
|
|
3755
3768
|
const didMaterial = principal.startsWith("did:key:") ? base58btc.decode(principal.slice("did:key:".length)) : new Uint8Array();
|
|
3756
3769
|
if (didMaterial.length !== 34 || didMaterial[0] !== 237 || didMaterial[1] !== 1)
|
|
@@ -3772,6 +3785,16 @@ async function signCompactUcanAuthorization(input) {
|
|
|
3772
3785
|
if (signature.length !== 64) throw new Error("compact Authorization signature must be Ed25519");
|
|
3773
3786
|
return parseCompactUcanAuthorization(`${protectedSegment}.${payloadSegment}.${encodeBase64Url(signature)}`);
|
|
3774
3787
|
}
|
|
3788
|
+
async function signCompactUcanAuthorization(input) {
|
|
3789
|
+
if (input.notBefore >= input.expiresAt || input.expiresAt - input.notBefore > 60)
|
|
3790
|
+
throw new Error("compact invocation lifetime must be between one and 60 seconds");
|
|
3791
|
+
return signCompactUcan(input);
|
|
3792
|
+
}
|
|
3793
|
+
async function signCompactUcanRootAuthorization(input) {
|
|
3794
|
+
if (input.notBefore >= input.expiresAt || input.expiresAt - input.notBefore > 31 * 24 * 60 * 60)
|
|
3795
|
+
throw new Error("compact policy root lifetime must be between one second and 31 days");
|
|
3796
|
+
return signCompactUcan({ ...input, proofs: [] });
|
|
3797
|
+
}
|
|
3775
3798
|
function createCompactPolicyInvocation(input) {
|
|
3776
3799
|
const session = parsePolicySessionUcan(input.sessionAuthorization);
|
|
3777
3800
|
if (session.cid !== input.sessionCid || session.aud !== input.recipientDid)
|
|
@@ -3918,14 +3941,14 @@ function decodeBase64Url(value) {
|
|
|
3918
3941
|
if (!/^[A-Za-z0-9_-]+$/.test(value) || value.length % 4 === 1)
|
|
3919
3942
|
throw new Error("value is not canonical base64url");
|
|
3920
3943
|
const normalized = value.replace(/-/g, "+").replace(/_/g, "/");
|
|
3921
|
-
const binary = typeof
|
|
3944
|
+
const binary = typeof atob === "function" ? atob(normalized.padEnd(Math.ceil(normalized.length / 4) * 4, "=")) : Buffer.from(normalized, "base64").toString("binary");
|
|
3922
3945
|
const bytes = Uint8Array.from(binary, (character) => character.charCodeAt(0));
|
|
3923
|
-
const encoded = typeof
|
|
3946
|
+
const encoded = typeof btoa === "function" ? btoa(String.fromCharCode(...bytes)).replace(/=/g, "").replace(/\+/g, "-").replace(/\//g, "_") : Buffer.from(bytes).toString("base64url");
|
|
3924
3947
|
if (encoded !== value) throw new Error("value is not canonical base64url");
|
|
3925
3948
|
return bytes;
|
|
3926
3949
|
}
|
|
3927
3950
|
function encodeBase64Url(value) {
|
|
3928
|
-
return typeof
|
|
3951
|
+
return typeof btoa === "function" ? btoa(String.fromCharCode(...value)).replace(/=/g, "").replace(/\+/g, "-").replace(/\//g, "_") : Buffer.from(value).toString("base64url");
|
|
3929
3952
|
}
|
|
3930
3953
|
function equalBytes(left, right) {
|
|
3931
3954
|
return left.length === right.length && left.every((byte, index) => byte === right[index]);
|
|
@@ -3949,4 +3972,387 @@ function base32Lower(bytes) {
|
|
|
3949
3972
|
if (bits > 0) output += alphabet2[buffer << 5 - bits & 31];
|
|
3950
3973
|
return output;
|
|
3951
3974
|
}
|
|
3975
|
+
|
|
3976
|
+
// src/policy/credential-admission.ts
|
|
3977
|
+
var import_ed255193 = require("@noble/curves/ed25519");
|
|
3978
|
+
var import_sha2562 = require("@noble/hashes/sha256");
|
|
3979
|
+
|
|
3980
|
+
// src/credentials/digest.ts
|
|
3981
|
+
function encodeBase64Url2(bytes) {
|
|
3982
|
+
if (typeof btoa === "function") {
|
|
3983
|
+
let binary = "";
|
|
3984
|
+
for (const byte of bytes) binary += String.fromCharCode(byte);
|
|
3985
|
+
return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/g, "");
|
|
3986
|
+
}
|
|
3987
|
+
return Buffer.from(bytes).toString("base64url");
|
|
3988
|
+
}
|
|
3989
|
+
async function sha256Base64Url(value) {
|
|
3990
|
+
const bytes = typeof value === "string" ? new TextEncoder().encode(value) : value;
|
|
3991
|
+
return encodeBase64Url2(new Uint8Array(await crypto.subtle.digest("SHA-256", bytes)));
|
|
3992
|
+
}
|
|
3993
|
+
async function canonicalDigest(value) {
|
|
3994
|
+
return sha256Base64Url(jcsCanonicalize(value));
|
|
3995
|
+
}
|
|
3996
|
+
function isDigest(value) {
|
|
3997
|
+
return typeof value === "string" && /^[A-Za-z0-9_-]{43}$/.test(value);
|
|
3998
|
+
}
|
|
3999
|
+
|
|
4000
|
+
// src/credentials/errors.ts
|
|
4001
|
+
var RECOVERABLE = /* @__PURE__ */ new Set([
|
|
4002
|
+
"CANCELED",
|
|
4003
|
+
"ISSUER_UNREADY",
|
|
4004
|
+
"OFFLINE",
|
|
4005
|
+
"POPUP_BLOCKED",
|
|
4006
|
+
"REQUEST_EXPIRED",
|
|
4007
|
+
"SIGNATURE_REJECTED",
|
|
4008
|
+
"UNSUPPORTED_PROFILE",
|
|
4009
|
+
"UNSUPPORTED_VERSION",
|
|
4010
|
+
"VERIFIED_NOT_SAVED"
|
|
4011
|
+
]);
|
|
4012
|
+
var CredentialError = class extends Error {
|
|
4013
|
+
constructor(code2, message, details = {}) {
|
|
4014
|
+
super(message);
|
|
4015
|
+
this.code = code2;
|
|
4016
|
+
this.details = details;
|
|
4017
|
+
this.name = "CredentialError";
|
|
4018
|
+
this.recoverable = RECOVERABLE.has(code2);
|
|
4019
|
+
}
|
|
4020
|
+
};
|
|
4021
|
+
|
|
4022
|
+
// src/credentials/requirement.ts
|
|
4023
|
+
var ID = /^[a-z0-9][a-z0-9._-]{0,127}(?:\/v1)?$/;
|
|
4024
|
+
function validateCredentialRequirement(value) {
|
|
4025
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) throw new CredentialError("UNSUPPORTED_PROFILE", "Credential requirement is invalid");
|
|
4026
|
+
const raw = value;
|
|
4027
|
+
if (Object.keys(raw).some((key) => !["type", "version", "profile", "credentialType", "claims", "maxAgeSeconds"].includes(key)) || raw.type !== "TinyCloudCredentialRequirement" || raw.version !== 1) throw new CredentialError("UNSUPPORTED_PROFILE", "Credential requirement version is unsupported");
|
|
4028
|
+
const pair = (candidate, label) => {
|
|
4029
|
+
if (typeof candidate !== "object" || candidate === null || Array.isArray(candidate)) throw new CredentialError("UNSUPPORTED_PROFILE", `${label} is invalid`);
|
|
4030
|
+
const item = candidate;
|
|
4031
|
+
if (Object.keys(item).length !== 2 || typeof item.id !== "string" || !ID.test(item.id) || item.version !== 1) throw new CredentialError("UNSUPPORTED_PROFILE", `${label} version is unsupported`);
|
|
4032
|
+
return { id: item.id, version: 1 };
|
|
4033
|
+
};
|
|
4034
|
+
if (typeof raw.claims !== "object" || raw.claims === null || Array.isArray(raw.claims)) throw new CredentialError("UNSUPPORTED_PROFILE", "Credential claims are invalid");
|
|
4035
|
+
const claims = {};
|
|
4036
|
+
for (const [key, value2] of Object.entries(raw.claims).sort(([a], [b]) => a.localeCompare(b))) {
|
|
4037
|
+
if (!ID.test(key) || typeof value2 !== "string" || value2.length === 0 || value2.length > 4096) throw new CredentialError("UNSUPPORTED_PROFILE", "Credential claim is invalid");
|
|
4038
|
+
claims[key] = value2;
|
|
4039
|
+
}
|
|
4040
|
+
if (raw.maxAgeSeconds !== void 0 && (!Number.isSafeInteger(raw.maxAgeSeconds) || raw.maxAgeSeconds < 1)) throw new CredentialError("UNSUPPORTED_PROFILE", "Credential freshness is invalid");
|
|
4041
|
+
return Object.freeze({ type: "TinyCloudCredentialRequirement", version: 1, profile: pair(raw.profile, "profile"), credentialType: pair(raw.credentialType, "credentialType"), claims: Object.freeze(claims), ...raw.maxAgeSeconds === void 0 ? {} : { maxAgeSeconds: raw.maxAgeSeconds } });
|
|
4042
|
+
}
|
|
4043
|
+
async function credentialRequirementDigest(requirement) {
|
|
4044
|
+
return canonicalDigest(validateCredentialRequirement(requirement));
|
|
4045
|
+
}
|
|
4046
|
+
|
|
4047
|
+
// src/policy/credential-admission.ts
|
|
4048
|
+
var POLICY_V2_SCHEMA = "xyz.tinycloud.policy/policy/v2";
|
|
4049
|
+
var POLICY_V2_DOMAIN = "xyz.tinycloud.policy/policy/v2\0";
|
|
4050
|
+
var POLICY_CREDENTIAL_REQUIREMENT_V1_TYPE = "TinyCloudPolicyCredentialRequirement";
|
|
4051
|
+
var POLICY_PRESENTATION_V3_SCHEMA = "xyz.tinycloud.policy/presentation/v3";
|
|
4052
|
+
var POLICY_PRESENTATION_V3_DOMAIN = "xyz.tinycloud.policy/Presentation/v3\0";
|
|
4053
|
+
function record(value, label) {
|
|
4054
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
4055
|
+
throw new Error(`${label} must be an object`);
|
|
4056
|
+
}
|
|
4057
|
+
return value;
|
|
4058
|
+
}
|
|
4059
|
+
function exactKeys2(value, keys, label) {
|
|
4060
|
+
const expected = [...keys].sort();
|
|
4061
|
+
const actual = Object.keys(value).sort();
|
|
4062
|
+
if (actual.length !== expected.length || actual.some((key, index) => key !== expected[index])) {
|
|
4063
|
+
throw new Error(`${label} contains an unknown or missing field`);
|
|
4064
|
+
}
|
|
4065
|
+
}
|
|
4066
|
+
function nonEmpty(value, label) {
|
|
4067
|
+
if (typeof value !== "string" || value.length === 0) {
|
|
4068
|
+
throw new Error(`${label} must be a non-empty string`);
|
|
4069
|
+
}
|
|
4070
|
+
return value;
|
|
4071
|
+
}
|
|
4072
|
+
function policyNodeOrigin(value) {
|
|
4073
|
+
let url;
|
|
4074
|
+
try {
|
|
4075
|
+
url = new URL(value);
|
|
4076
|
+
} catch {
|
|
4077
|
+
throw new Error("policy node origin is invalid");
|
|
4078
|
+
}
|
|
4079
|
+
if (url.protocol !== "https:" || url.username !== "" || url.password !== "" || url.pathname !== "/" || url.search !== "" || url.hash !== "") {
|
|
4080
|
+
throw new Error("policy node origin is invalid");
|
|
4081
|
+
}
|
|
4082
|
+
return url.origin;
|
|
4083
|
+
}
|
|
4084
|
+
function versionPair(value, label) {
|
|
4085
|
+
const item = record(value, label);
|
|
4086
|
+
exactKeys2(item, ["id", "version"], label);
|
|
4087
|
+
if (item.version !== 1) throw new Error(`${label} version is unsupported`);
|
|
4088
|
+
return Object.freeze({ id: nonEmpty(item.id, `${label}.id`), version: 1 });
|
|
4089
|
+
}
|
|
4090
|
+
function validatePolicyCredentialRequirementV1(value) {
|
|
4091
|
+
const item = record(value, "policy credential requirement");
|
|
4092
|
+
exactKeys2(
|
|
4093
|
+
item,
|
|
4094
|
+
[
|
|
4095
|
+
"type",
|
|
4096
|
+
"version",
|
|
4097
|
+
"requirementDigest",
|
|
4098
|
+
"descriptorDigest",
|
|
4099
|
+
"issuerDid",
|
|
4100
|
+
"issuerKid",
|
|
4101
|
+
"profile",
|
|
4102
|
+
"credentialType"
|
|
4103
|
+
],
|
|
4104
|
+
"policy credential requirement"
|
|
4105
|
+
);
|
|
4106
|
+
if (item.type !== POLICY_CREDENTIAL_REQUIREMENT_V1_TYPE || item.version !== 1 || !isDigest(item.requirementDigest) || !isDigest(item.descriptorDigest)) {
|
|
4107
|
+
throw new Error("policy credential requirement is invalid");
|
|
4108
|
+
}
|
|
4109
|
+
return Object.freeze({
|
|
4110
|
+
type: POLICY_CREDENTIAL_REQUIREMENT_V1_TYPE,
|
|
4111
|
+
version: 1,
|
|
4112
|
+
requirementDigest: item.requirementDigest,
|
|
4113
|
+
descriptorDigest: item.descriptorDigest,
|
|
4114
|
+
issuerDid: nonEmpty(item.issuerDid, "policy credential issuer"),
|
|
4115
|
+
issuerKid: nonEmpty(item.issuerKid, "policy credential issuer key"),
|
|
4116
|
+
profile: versionPair(item.profile, "policy credential profile"),
|
|
4117
|
+
credentialType: versionPair(
|
|
4118
|
+
item.credentialType,
|
|
4119
|
+
"policy credential type"
|
|
4120
|
+
)
|
|
4121
|
+
});
|
|
4122
|
+
}
|
|
4123
|
+
function hex2(bytes) {
|
|
4124
|
+
return [...bytes].map((byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
4125
|
+
}
|
|
4126
|
+
function policyV2DigestHex(input) {
|
|
4127
|
+
const canonicalPolicy = {
|
|
4128
|
+
...input,
|
|
4129
|
+
credentialRequirement: validatePolicyCredentialRequirementV1(
|
|
4130
|
+
input.credentialRequirement
|
|
4131
|
+
),
|
|
4132
|
+
capabilityCeiling: input.capabilityCeiling.map(
|
|
4133
|
+
normalizeUnifiedPolicyCapability
|
|
4134
|
+
)
|
|
4135
|
+
};
|
|
4136
|
+
return hex2(
|
|
4137
|
+
(0, import_sha2562.sha256)(
|
|
4138
|
+
new TextEncoder().encode(
|
|
4139
|
+
POLICY_V2_DOMAIN + jcsCanonicalize(canonicalPolicy)
|
|
4140
|
+
)
|
|
4141
|
+
)
|
|
4142
|
+
);
|
|
4143
|
+
}
|
|
4144
|
+
function date(value, label) {
|
|
4145
|
+
const text = nonEmpty(value, label);
|
|
4146
|
+
if (!Number.isFinite(Date.parse(text))) throw new Error(`${label} is invalid`);
|
|
4147
|
+
return text;
|
|
4148
|
+
}
|
|
4149
|
+
function rfc3339Seconds(value) {
|
|
4150
|
+
return new Date(Math.floor(value.getTime() / 1e3) * 1e3).toISOString().replace(/\.000Z$/, "Z");
|
|
4151
|
+
}
|
|
4152
|
+
function validateUnifiedPolicyV2(value, expectedPolicyCid) {
|
|
4153
|
+
const item = record(value, "Policy/v2");
|
|
4154
|
+
const allowed = [
|
|
4155
|
+
"schema",
|
|
4156
|
+
"policyId",
|
|
4157
|
+
"ownerDid",
|
|
4158
|
+
"createdAt",
|
|
4159
|
+
"contentSource",
|
|
4160
|
+
"capabilityCeiling",
|
|
4161
|
+
"credentialRequirement",
|
|
4162
|
+
"signature",
|
|
4163
|
+
...item.expiresAt === void 0 ? [] : ["expiresAt"]
|
|
4164
|
+
];
|
|
4165
|
+
exactKeys2(item, allowed, "Policy/v2");
|
|
4166
|
+
if (item.schema !== POLICY_V2_SCHEMA || !Array.isArray(item.capabilityCeiling)) {
|
|
4167
|
+
throw new Error("Policy/v2 is invalid");
|
|
4168
|
+
}
|
|
4169
|
+
const ownerDid = nonEmpty(item.ownerDid, "Policy/v2 ownerDid");
|
|
4170
|
+
const signature = record(item.signature, "Policy/v2 signature");
|
|
4171
|
+
exactKeys2(signature, ["suite", "signerDid", "value"], "Policy/v2 signature");
|
|
4172
|
+
if (signature.suite !== "Ed25519" || signature.signerDid !== ownerDid || typeof signature.value !== "string" || signature.value.length === 0) {
|
|
4173
|
+
throw new Error("Policy/v2 signature binding is invalid");
|
|
4174
|
+
}
|
|
4175
|
+
const policy = Object.freeze({
|
|
4176
|
+
schema: POLICY_V2_SCHEMA,
|
|
4177
|
+
policyId: nonEmpty(item.policyId, "Policy/v2 policyId"),
|
|
4178
|
+
ownerDid,
|
|
4179
|
+
createdAt: date(item.createdAt, "Policy/v2 createdAt"),
|
|
4180
|
+
...item.expiresAt === void 0 ? {} : { expiresAt: date(item.expiresAt, "Policy/v2 expiresAt") },
|
|
4181
|
+
contentSource: item.contentSource,
|
|
4182
|
+
capabilityCeiling: Object.freeze(
|
|
4183
|
+
item.capabilityCeiling.map(normalizeUnifiedPolicyCapability)
|
|
4184
|
+
),
|
|
4185
|
+
credentialRequirement: validatePolicyCredentialRequirementV1(
|
|
4186
|
+
item.credentialRequirement
|
|
4187
|
+
),
|
|
4188
|
+
signature: Object.freeze({
|
|
4189
|
+
suite: "Ed25519",
|
|
4190
|
+
signerDid: ownerDid,
|
|
4191
|
+
value: signature.value
|
|
4192
|
+
})
|
|
4193
|
+
});
|
|
4194
|
+
const unsigned = { ...policy };
|
|
4195
|
+
delete unsigned.policyId;
|
|
4196
|
+
delete unsigned.signature;
|
|
4197
|
+
const digestHex = policyV2DigestHex(unsigned);
|
|
4198
|
+
if (policy.policyId !== policyIdForDigestHex(digestHex)) {
|
|
4199
|
+
throw new Error("Policy/v2 policyId binding is invalid");
|
|
4200
|
+
}
|
|
4201
|
+
if (expectedPolicyCid !== void 0) {
|
|
4202
|
+
const cid = policyCidFromCanonicalBytes(
|
|
4203
|
+
new TextEncoder().encode(jcsCanonicalize(policy))
|
|
4204
|
+
);
|
|
4205
|
+
if (cid !== expectedPolicyCid) throw new Error("Policy/v2 CID binding is invalid");
|
|
4206
|
+
}
|
|
4207
|
+
return policy;
|
|
4208
|
+
}
|
|
4209
|
+
function didKeyBytes(did) {
|
|
4210
|
+
const principal = did.split("#", 1)[0];
|
|
4211
|
+
if (!principal.startsWith("did:key:")) throw new Error("holder DID must be did:key");
|
|
4212
|
+
const material = base58btc.decode(principal.slice("did:key:".length));
|
|
4213
|
+
if (material.length !== 34 || material[0] !== 237 || material[1] !== 1) {
|
|
4214
|
+
throw new Error("holder DID must contain an Ed25519 key");
|
|
4215
|
+
}
|
|
4216
|
+
return material.slice(2);
|
|
4217
|
+
}
|
|
4218
|
+
function envelopeFromVerified(verified) {
|
|
4219
|
+
const {
|
|
4220
|
+
verifiedAt: _verifiedAt,
|
|
4221
|
+
credentialDigest: _credentialDigest,
|
|
4222
|
+
statusCheckedAt: _statusCheckedAt,
|
|
4223
|
+
...envelope
|
|
4224
|
+
} = verified;
|
|
4225
|
+
return envelope;
|
|
4226
|
+
}
|
|
4227
|
+
async function buildPolicyCredentialPresentationV3(input) {
|
|
4228
|
+
const policy = validateUnifiedPolicyV2(input.policy, input.policyCid);
|
|
4229
|
+
const requirement = validateCredentialRequirement(input.requirement);
|
|
4230
|
+
const commitment = policy.credentialRequirement;
|
|
4231
|
+
const requirementDigest = await credentialRequirementDigest(requirement);
|
|
4232
|
+
const credentialDigest = await sha256Base64Url(input.credential.credential);
|
|
4233
|
+
if (requirementDigest !== commitment.requirementDigest || input.credential.descriptorDigest !== commitment.descriptorDigest || input.credential.credentialDigest !== credentialDigest || input.credential.issuerDid !== commitment.issuerDid || input.credential.issuerKid !== commitment.issuerKid || jcsCanonicalize(input.credential.profile) !== jcsCanonicalize(commitment.profile) || jcsCanonicalize(input.credential.credentialType) !== jcsCanonicalize(commitment.credentialType)) {
|
|
4234
|
+
throw new Error("credential does not match the signed Policy/v2 requirement");
|
|
4235
|
+
}
|
|
4236
|
+
const requestedCapabilities = input.requestedCapabilities.map(
|
|
4237
|
+
normalizeUnifiedPolicyCapability
|
|
4238
|
+
);
|
|
4239
|
+
if (requestedCapabilities.length === 0 || requestedCapabilities.some(
|
|
4240
|
+
(requested) => !policy.capabilityCeiling.some(
|
|
4241
|
+
(authority) => unifiedPolicyCapabilityContains(authority, requested)
|
|
4242
|
+
)
|
|
4243
|
+
)) {
|
|
4244
|
+
throw new Error("requested capabilities exceed the signed Policy/v2 ceiling");
|
|
4245
|
+
}
|
|
4246
|
+
const challenge = input.challenge;
|
|
4247
|
+
const holderDid = input.credential.holderDid;
|
|
4248
|
+
if (challenge.policyCid !== input.policyCid || challenge.recipientDid !== holderDid || input.credential.subjectDid !== holderDid || typeof challenge.nodeAudience !== "string" || challenge.nodeAudience.length === 0 || typeof challenge.expiresAt !== "string" || !Number.isFinite(Date.parse(challenge.expiresAt))) {
|
|
4249
|
+
throw new Error("policy challenge binding is invalid");
|
|
4250
|
+
}
|
|
4251
|
+
didKeyBytes(holderDid);
|
|
4252
|
+
const now = input.now ?? /* @__PURE__ */ new Date();
|
|
4253
|
+
const challengeExpiry = Date.parse(challenge.expiresAt);
|
|
4254
|
+
const credentialExpiry = Date.parse(input.credential.expiresAt);
|
|
4255
|
+
const expiresAtMs = Math.min(
|
|
4256
|
+
now.getTime() + 6e4,
|
|
4257
|
+
challengeExpiry,
|
|
4258
|
+
credentialExpiry
|
|
4259
|
+
);
|
|
4260
|
+
if (expiresAtMs <= now.getTime()) throw new Error("policy challenge is expired");
|
|
4261
|
+
const unsigned = Object.freeze({
|
|
4262
|
+
schema: POLICY_PRESENTATION_V3_SCHEMA,
|
|
4263
|
+
jti: input.jti ?? encodeBase64Url2(crypto.getRandomValues(new Uint8Array(16))),
|
|
4264
|
+
challengeId: challenge.challengeId,
|
|
4265
|
+
nonce: challenge.nonce,
|
|
4266
|
+
policyCid: input.policyCid,
|
|
4267
|
+
nodeAudience: challenge.nodeAudience,
|
|
4268
|
+
holderDid,
|
|
4269
|
+
subjectDid: input.credential.subjectDid,
|
|
4270
|
+
credentialSpaceOwnerDid: nonEmpty(
|
|
4271
|
+
input.credentialSpaceOwnerDid,
|
|
4272
|
+
"credential space owner DID"
|
|
4273
|
+
),
|
|
4274
|
+
credentialDigest,
|
|
4275
|
+
requirementDigest,
|
|
4276
|
+
descriptorDigest: input.credential.descriptorDigest,
|
|
4277
|
+
requestedCapabilities: Object.freeze(requestedCapabilities),
|
|
4278
|
+
issuedAt: rfc3339Seconds(now),
|
|
4279
|
+
expiresAt: rfc3339Seconds(new Date(expiresAtMs))
|
|
4280
|
+
});
|
|
4281
|
+
const digest2 = (0, import_sha2562.sha256)(
|
|
4282
|
+
new TextEncoder().encode(
|
|
4283
|
+
POLICY_PRESENTATION_V3_DOMAIN + jcsCanonicalize(unsigned)
|
|
4284
|
+
)
|
|
4285
|
+
);
|
|
4286
|
+
const signature = await input.sign(digest2);
|
|
4287
|
+
if (signature.length !== 64 || !import_ed255193.ed25519.verify(signature, digest2, didKeyBytes(holderDid))) {
|
|
4288
|
+
throw new Error("policy presentation signature is invalid");
|
|
4289
|
+
}
|
|
4290
|
+
return Object.freeze({
|
|
4291
|
+
...unsigned,
|
|
4292
|
+
signature: Object.freeze({
|
|
4293
|
+
suite: "Ed25519",
|
|
4294
|
+
signerDid: holderDid,
|
|
4295
|
+
value: encodeBase64Url2(signature)
|
|
4296
|
+
})
|
|
4297
|
+
});
|
|
4298
|
+
}
|
|
4299
|
+
async function admitPolicyCredentialV3(input) {
|
|
4300
|
+
const policy = validateUnifiedPolicyV2(input.policy, input.policyCid);
|
|
4301
|
+
const nodeOrigin = policyNodeOrigin(input.nodeOrigin);
|
|
4302
|
+
const accountAuthorizationCid = nonEmpty(
|
|
4303
|
+
input.accountAuthorizationCid,
|
|
4304
|
+
"account authorization CID"
|
|
4305
|
+
);
|
|
4306
|
+
const credentialSpaceId = nonEmpty(
|
|
4307
|
+
input.credentialSpaceId,
|
|
4308
|
+
"credential space ID"
|
|
4309
|
+
);
|
|
4310
|
+
const fetchFn = input.fetch ?? globalThis.fetch.bind(globalThis);
|
|
4311
|
+
const challenge = await requestPolicyChallengeV3({
|
|
4312
|
+
nodeOrigin,
|
|
4313
|
+
policyCid: input.policyCid,
|
|
4314
|
+
recipientDid: input.credential.holderDid,
|
|
4315
|
+
requestedCapabilities: input.requestedCapabilities,
|
|
4316
|
+
fetch: fetchFn
|
|
4317
|
+
});
|
|
4318
|
+
const presentation = await buildPolicyCredentialPresentationV3({
|
|
4319
|
+
...input,
|
|
4320
|
+
policy,
|
|
4321
|
+
challenge
|
|
4322
|
+
});
|
|
4323
|
+
const response = await fetchFn(
|
|
4324
|
+
new URL("/share/v3/policy/delegations", nodeOrigin),
|
|
4325
|
+
{
|
|
4326
|
+
method: "POST",
|
|
4327
|
+
redirect: "error",
|
|
4328
|
+
headers: { accept: "application/json", "content-type": "application/json" },
|
|
4329
|
+
body: JSON.stringify({
|
|
4330
|
+
policyCid: input.policyCid,
|
|
4331
|
+
challengeId: challenge.challengeId,
|
|
4332
|
+
nonce: challenge.nonce,
|
|
4333
|
+
requirement: validateCredentialRequirement(input.requirement),
|
|
4334
|
+
credential: envelopeFromVerified(input.credential),
|
|
4335
|
+
accountAuthorizationCid,
|
|
4336
|
+
credentialSpaceId,
|
|
4337
|
+
presentation
|
|
4338
|
+
})
|
|
4339
|
+
}
|
|
4340
|
+
);
|
|
4341
|
+
if (!response.ok) throw new Error(`policy delegation rejected (${response.status})`);
|
|
4342
|
+
const value = record(await response.json(), "policy delegation");
|
|
4343
|
+
if (value.admitted !== true || typeof value.sessionCid !== "string" || typeof value.authorization !== "string") {
|
|
4344
|
+
throw new Error("policy delegation response is invalid");
|
|
4345
|
+
}
|
|
4346
|
+
const session = parsePolicySessionUcan(value.authorization, [
|
|
4347
|
+
input.policyRootCid,
|
|
4348
|
+
input.enforcementRootCid
|
|
4349
|
+
]);
|
|
4350
|
+
const expectedAttenuation = compactAttenuationForPolicyCapabilities(
|
|
4351
|
+
input.requestedCapabilities
|
|
4352
|
+
);
|
|
4353
|
+
if (session.cid !== value.sessionCid || session.aud !== input.credential.holderDid || session.fact.policyCid !== input.policyCid || session.fact.recipientDid !== input.credential.holderDid || session.fact.policyDelegationCid !== input.policyRootCid || session.fact.enforcementDelegationCid !== input.enforcementRootCid || jcsCanonicalize(session.att) !== jcsCanonicalize(expectedAttenuation)) {
|
|
4354
|
+
throw new Error("policy delegation signed binding is invalid");
|
|
4355
|
+
}
|
|
4356
|
+
return Object.freeze({ challenge, presentation, session });
|
|
4357
|
+
}
|
|
3952
4358
|
//# sourceMappingURL=index.cjs.map
|