@tinycloud/share-sdk 0.2.0 → 0.3.0-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/index.cjs +296 -32
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +16 -2
- package/dist/index.d.ts +16 -2
- package/dist/index.js +296 -32
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -5227,9 +5227,12 @@ function validateV2Invariants(value, ctx) {
|
|
|
5227
5227
|
var unsignedShareEnvelopeV2Schema = unsignedShareEnvelopeV2BaseSchema.superRefine(validateV2Invariants);
|
|
5228
5228
|
var shareEnvelopeV2Schema = unsignedShareEnvelopeV2BaseSchema.extend({ signature: signatureSchema }).strict().superRefine(validateV2Invariants);
|
|
5229
5229
|
var unifiedResourceSchema = external_exports.string().refine((value) => {
|
|
5230
|
-
const
|
|
5231
|
-
if (
|
|
5232
|
-
const
|
|
5230
|
+
const marker = value.indexOf("/kv/");
|
|
5231
|
+
if (marker < 1) return false;
|
|
5232
|
+
const space = value.slice(0, marker);
|
|
5233
|
+
const legacy = space.startsWith("tinycloud://");
|
|
5234
|
+
if (legacy ? /[:/?#%]/.test(space.slice("tinycloud://".length)) : !space.startsWith("tinycloud:") || /[/?#%]/.test(space)) return false;
|
|
5235
|
+
const path = value.slice(marker + 4);
|
|
5233
5236
|
return !path.startsWith("/") && !path.endsWith("/") && !path.includes("//") && path.split("/").every((segment) => segment.length > 0 && segment !== "." && segment !== "..");
|
|
5234
5237
|
}, { message: "expected canonical TinyCloud KV resource" });
|
|
5235
5238
|
var unifiedEncryptionNetworkSchema = external_exports.string().refine((value) => {
|
|
@@ -5267,7 +5270,7 @@ var unifiedContentSourceSchema = external_exports.object({
|
|
|
5267
5270
|
mode: external_exports.union([external_exports.literal("mutable"), external_exports.literal("immutable")]),
|
|
5268
5271
|
initialCiphertextDigestHex: external_exports.string().regex(/^[0-9a-f]{64}$/).optional()
|
|
5269
5272
|
}).strict();
|
|
5270
|
-
var
|
|
5273
|
+
var unifiedPolicyV1Schema = external_exports.object({
|
|
5271
5274
|
schema: external_exports.literal("xyz.tinycloud.policy/policy/v1"),
|
|
5272
5275
|
policyId: external_exports.string().regex(/^pol_[a-z2-7]+$/),
|
|
5273
5276
|
ownerDid: external_exports.string().min(1),
|
|
@@ -5277,6 +5280,31 @@ var unifiedPolicySchema = external_exports.object({
|
|
|
5277
5280
|
capabilityCeiling: external_exports.array(unifiedCapabilitySchema).min(2),
|
|
5278
5281
|
signature: external_exports.object({ suite: external_exports.string().min(1), signerDid: external_exports.string().min(1), value: external_exports.string().min(1) }).strict()
|
|
5279
5282
|
}).strict();
|
|
5283
|
+
var policyCredentialRequirementV1Schema = external_exports.object({
|
|
5284
|
+
type: external_exports.literal("TinyCloudPolicyCredentialRequirement"),
|
|
5285
|
+
version: external_exports.literal(1),
|
|
5286
|
+
requirementDigest: external_exports.string().regex(/^[A-Za-z0-9_-]{43}$/),
|
|
5287
|
+
descriptorDigest: external_exports.string().regex(/^[A-Za-z0-9_-]{43}$/),
|
|
5288
|
+
issuerDid: external_exports.string().min(1),
|
|
5289
|
+
issuerKid: external_exports.string().min(1),
|
|
5290
|
+
profile: external_exports.object({ id: external_exports.string().min(1), version: external_exports.literal(1) }).strict(),
|
|
5291
|
+
credentialType: external_exports.object({ id: external_exports.string().min(1), version: external_exports.literal(1) }).strict()
|
|
5292
|
+
}).strict();
|
|
5293
|
+
var unifiedPolicyV2Schema = external_exports.object({
|
|
5294
|
+
schema: external_exports.literal("xyz.tinycloud.policy/policy/v2"),
|
|
5295
|
+
policyId: external_exports.string().regex(/^pol_[a-z2-7]+$/),
|
|
5296
|
+
ownerDid: external_exports.string().min(1),
|
|
5297
|
+
createdAt: external_exports.string().datetime({ offset: true }),
|
|
5298
|
+
expiresAt: external_exports.string().datetime({ offset: true }).optional(),
|
|
5299
|
+
contentSource: unifiedContentSourceSchema,
|
|
5300
|
+
capabilityCeiling: external_exports.array(unifiedCapabilitySchema).min(2),
|
|
5301
|
+
credentialRequirement: policyCredentialRequirementV1Schema,
|
|
5302
|
+
signature: external_exports.object({ suite: external_exports.literal("Ed25519"), signerDid: external_exports.string().min(1), value: external_exports.string().min(1) }).strict()
|
|
5303
|
+
}).strict();
|
|
5304
|
+
var unifiedPolicySchema = external_exports.discriminatedUnion("schema", [
|
|
5305
|
+
unifiedPolicyV1Schema,
|
|
5306
|
+
unifiedPolicyV2Schema
|
|
5307
|
+
]);
|
|
5280
5308
|
var unifiedRootSchema = external_exports.object({
|
|
5281
5309
|
cid: external_exports.string().min(1),
|
|
5282
5310
|
authorization: external_exports.string().regex(/^[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$/),
|
|
@@ -5331,7 +5359,7 @@ function validateV3Invariants(value, ctx) {
|
|
|
5331
5359
|
if (value.contentSource.encryptionNetwork !== value.encryptionNetwork) {
|
|
5332
5360
|
ctx.addIssue({ code: external_exports.ZodIssueCode.custom, path: ["encryptionNetwork"], message: "encryption network is not bound to the source" });
|
|
5333
5361
|
}
|
|
5334
|
-
if (value.attestedEnforcerBinding.enforcerDid !== value.target.nodeAudience || value.attestedEnforcerBinding.
|
|
5362
|
+
if (value.attestedEnforcerBinding.enforcerDid !== value.target.nodeAudience || value.attestedEnforcerBinding.signature.signerDid !== value.attestedEnforcerBinding.nodeAudience || Date.parse(value.attestedEnforcerBinding.expiresAt) < Date.parse(value.expiry)) {
|
|
5335
5363
|
ctx.addIssue({ code: external_exports.ZodIssueCode.custom, path: ["attestedEnforcerBinding"], message: "enforcer binding does not cover the target and share lifetime" });
|
|
5336
5364
|
}
|
|
5337
5365
|
if (value.policy.contentSource.shareId !== value.contentSource.shareId || value.policy.contentSource.kvResource !== value.contentSource.kvResource || value.policy.contentSource.selector !== value.contentSource.selector || value.policy.contentSource.encryptionNetwork !== value.encryptionNetwork || value.policy.contentSource.encryptedSymmetricKeyDigestHex !== value.contentSource.encryptedSymmetricKeyDigestHex || value.policy.contentSource.keyVersion !== value.contentSource.keyVersion || value.policy.contentSource.mode !== value.contentSource.mode || value.policy.contentSource.initialCiphertextDigestHex !== value.contentSource.initialCiphertextDigestHex) {
|
|
@@ -5767,6 +5795,13 @@ function equal(left, right) {
|
|
|
5767
5795
|
var ED25519_VERIFY_OPTS2 = { zip215: false };
|
|
5768
5796
|
var ENVELOPE_SIGNATURE_DOMAIN = "xyz.tinycloud.share/envelope/v1\0";
|
|
5769
5797
|
var ENVELOPE_V2_SIGNATURE_DOMAIN = "xyz.tinycloud.share/envelope/v2\0";
|
|
5798
|
+
var ENVELOPE_V3_SIGNATURE_DOMAIN = "xyz.tinycloud.share/envelope/v3\0";
|
|
5799
|
+
var POLICY_V1_SIGNATURE_DOMAIN = "xyz.tinycloud.policy/policy/v1\0";
|
|
5800
|
+
var POLICY_V2_SIGNATURE_DOMAIN = "xyz.tinycloud.policy/policy/v2\0";
|
|
5801
|
+
var CONTENT_SOURCE_V1_DOMAIN = "xyz.tinycloud.policy/ContentSource/v1\0";
|
|
5802
|
+
var POLICY_CAPABILITY_V1_DOMAIN = "xyz.tinycloud.policy/PolicyCapability/v1\0";
|
|
5803
|
+
var NATIVE_PROJECTION_V1_DOMAIN = "xyz.tinycloud.policy/NativeProjection/v1\0";
|
|
5804
|
+
var ATTESTED_ENFORCER_V2_DOMAIN = "xyz.tinycloud.policy/AttestedEnforcerBinding/v2\0";
|
|
5770
5805
|
function signingBytes(unsigned) {
|
|
5771
5806
|
const domain = utf8Bytes(ENVELOPE_SIGNATURE_DOMAIN);
|
|
5772
5807
|
const body = utf8Bytes(canonicalize(unsigned));
|
|
@@ -5783,6 +5818,14 @@ function signingBytesV2(unsigned) {
|
|
|
5783
5818
|
bytes3.set(body, domain.length);
|
|
5784
5819
|
return bytes3;
|
|
5785
5820
|
}
|
|
5821
|
+
function signingBytesV3(unsigned) {
|
|
5822
|
+
const domain = utf8Bytes(ENVELOPE_V3_SIGNATURE_DOMAIN);
|
|
5823
|
+
const body = utf8Bytes(canonicalize(unsigned));
|
|
5824
|
+
const bytes3 = new Uint8Array(domain.length + body.length);
|
|
5825
|
+
bytes3.set(domain);
|
|
5826
|
+
bytes3.set(body, domain.length);
|
|
5827
|
+
return bytes3;
|
|
5828
|
+
}
|
|
5786
5829
|
function signEnvelope(envelopeWithoutSig, ed25519PrivKey) {
|
|
5787
5830
|
const unsigned = unsignedShareEnvelopeSchema.parse(envelopeWithoutSig);
|
|
5788
5831
|
const publicKey = ed255193.getPublicKey(ed25519PrivKey);
|
|
@@ -5830,6 +5873,92 @@ async function verifyEnvelopeV2(envelope, options) {
|
|
|
5830
5873
|
if (parsed.authorizationTarget.kind === "policy") return await computeCid(fromBase64Url(parsed.authorizationTarget.policyBytes)) === parsed.authorizationTarget.policyCid;
|
|
5831
5874
|
return true;
|
|
5832
5875
|
}
|
|
5876
|
+
function verifyEnvelopeV3SignatureOnly(envelope) {
|
|
5877
|
+
const parsed = shareEnvelopeV3Schema.parse(envelope);
|
|
5878
|
+
const { signature, ...unsigned } = parsed;
|
|
5879
|
+
return ed255193.verify(fromBase64Url(signature.value), sha2562(signingBytesV3(unsigned)), ed25519PublicKeyFromDidKey(signature.signerDid), ED25519_VERIFY_OPTS2);
|
|
5880
|
+
}
|
|
5881
|
+
async function verifyEnvelopeV3(envelope, options) {
|
|
5882
|
+
const parsed = shareEnvelopeV3Schema.parse(envelope);
|
|
5883
|
+
if (parsed.signature.signerDid !== options.expectedSignerDid || parsed.signature.signerDid !== parsed.policy.ownerDid || !verifyEnvelopeV3SignatureOnly(parsed)) return false;
|
|
5884
|
+
const { policy } = parsed;
|
|
5885
|
+
const unsignedPolicy = { ...policy };
|
|
5886
|
+
delete unsignedPolicy.policyId;
|
|
5887
|
+
delete unsignedPolicy.signature;
|
|
5888
|
+
const policySignatureDomain = policy.schema === "xyz.tinycloud.policy/policy/v2" ? POLICY_V2_SIGNATURE_DOMAIN : POLICY_V1_SIGNATURE_DOMAIN;
|
|
5889
|
+
const policyDigest = sha2562(new TextEncoder().encode(`${policySignatureDomain}${canonicalize(unsignedPolicy)}`));
|
|
5890
|
+
const policySignature = policy.signature;
|
|
5891
|
+
if (policySignature.suite !== "Ed25519" || policySignature.signerDid !== policy.ownerDid) return false;
|
|
5892
|
+
let policyPublicKey;
|
|
5893
|
+
let policySignatureBytes;
|
|
5894
|
+
try {
|
|
5895
|
+
policyPublicKey = ed25519PublicKeyFromDidKey(policy.ownerDid);
|
|
5896
|
+
policySignatureBytes = fromBase64Url(policySignature.value);
|
|
5897
|
+
} catch {
|
|
5898
|
+
return false;
|
|
5899
|
+
}
|
|
5900
|
+
if (policySignatureBytes.length !== 64 || !ed255193.verify(policySignatureBytes, policyDigest, policyPublicKey, ED25519_VERIFY_OPTS2)) return false;
|
|
5901
|
+
const policyIdDigest = policyDigest;
|
|
5902
|
+
if (policy.policyId !== `pol_${base32Lower(policyIdDigest)}`) return false;
|
|
5903
|
+
const policyBytes = new TextEncoder().encode(canonicalize(policy));
|
|
5904
|
+
if (await computeCid(policyBytes) !== parsed.policyCid) return false;
|
|
5905
|
+
const sourceDigest = sha2562(new TextEncoder().encode(`${CONTENT_SOURCE_V1_DOMAIN}${canonicalize(policy.contentSource)}`));
|
|
5906
|
+
if (hex(sourceDigest) !== parsed.contentSourceDigestHex) return false;
|
|
5907
|
+
const sortedCapabilities = [...policy.capabilityCeiling].sort((left, right) => canonicalize(left).localeCompare(canonicalize(right)));
|
|
5908
|
+
const capabilityCeilingHashHex = hex(sha2562(new TextEncoder().encode(`${POLICY_CAPABILITY_V1_DOMAIN}${canonicalize(sortedCapabilities)}`)));
|
|
5909
|
+
const nativeProjection = sortedCapabilities.map((capability) => capability.kind === "encryption" ? { service: "tinycloud.encryption", space: capability.resource, path: capability.resource, actions: [capability.action] } : { service: "tinycloud.kv", space: capability.resource.slice(0, capability.resource.indexOf("/kv/")), path: capability.resource.split("/kv/")[1], actions: [...capability.actions], caveat: { type: "xyz.tinycloud.resource/selector", kind: capability.selector, value: capability.resource } }).sort((left, right) => canonicalize(left).localeCompare(canonicalize(right)));
|
|
5910
|
+
const nativeProjectionHashHex = hex(sha2562(new TextEncoder().encode(`${NATIVE_PROJECTION_V1_DOMAIN}${canonicalize(nativeProjection)}`)));
|
|
5911
|
+
const expectedAttenuation = Object.fromEntries(sortedCapabilities.map((capability) => capability.kind === "encryption" ? [capability.resource, { [capability.action]: [{}] }] : [capability.resource, Object.fromEntries(capability.actions.map((action) => [action, [{ kind: capability.selector, type: "xyz.tinycloud.resource/selector", value: capability.resource }]]))]));
|
|
5912
|
+
const kv = policy.capabilityCeiling.find((capability) => capability.kind === "kv");
|
|
5913
|
+
const expectedKvActions = parsed.actions.flatMap((action) => action === "read" ? ["tinycloud.kv/get", "tinycloud.kv/metadata"] : action === "list" ? ["tinycloud.kv/list"] : ["tinycloud.kv/put"]);
|
|
5914
|
+
const marker = parsed.contentSource.kvResource.indexOf("/kv/");
|
|
5915
|
+
const resourceSpace = marker < 1 ? "" : parsed.contentSource.kvResource.slice(0, marker);
|
|
5916
|
+
const resourcePath = marker < 1 ? "" : parsed.contentSource.kvResource.slice(marker + 4);
|
|
5917
|
+
if (parsed.shareId !== parsed.contentSource.shareId || resourceSpace !== parsed.target.spaceId || resourcePath !== parsed.resource.path.replace(/\/$/, "") || parsed.resource.kind !== parsed.contentSource.selector || kv?.kind !== "kv" || expectedKvActions.some((action) => !kv.actions.includes(action)) || parsed.encryptionNetwork !== parsed.contentSource.encryptionNetwork || parsed.contentSource.keyVersion <= 0 || policy.expiresAt !== void 0 && Date.parse(parsed.expiry) > Date.parse(policy.expiresAt)) return false;
|
|
5918
|
+
const binding = parsed.attestedEnforcerBinding;
|
|
5919
|
+
const { signature: bindingSignature, ...unsignedBinding } = binding;
|
|
5920
|
+
const expectedBindingDigestHex = hex(sha2562(new TextEncoder().encode(canonicalize({ enforcerDid: binding.enforcerDid, nodeAudience: binding.nodeAudience }))));
|
|
5921
|
+
if (binding.enforcerDid !== parsed.target.nodeAudience || binding.attestationBindingDigestHex !== expectedBindingDigestHex || bindingSignature.signerDid !== binding.nodeAudience || bindingSignature.suite !== "Ed25519" || Date.parse(binding.issuedAt) > Date.now() || Date.parse(binding.expiresAt) <= Date.now() || Date.parse(binding.expiresAt) < Date.parse(parsed.expiry)) return false;
|
|
5922
|
+
try {
|
|
5923
|
+
const digest4 = sha2562(new TextEncoder().encode(`${ATTESTED_ENFORCER_V2_DOMAIN}${canonicalize(unsignedBinding)}`));
|
|
5924
|
+
if (!ed255193.verify(fromBase64Url(bindingSignature.value), digest4, ed25519PublicKeyFromDidKey(binding.nodeAudience), ED25519_VERIFY_OPTS2)) return false;
|
|
5925
|
+
} catch {
|
|
5926
|
+
return false;
|
|
5927
|
+
}
|
|
5928
|
+
if (parsed.policyRoot.role !== "policy-authority" || parsed.enforcementRoot.role !== "policy-enforcement" || parsed.policyRoot.cid === parsed.enforcementRoot.cid) return false;
|
|
5929
|
+
try {
|
|
5930
|
+
const policyRoot = verifyCompactUcanAuthorization(parsed.policyRoot.authorization, parsed.policyRoot.cid);
|
|
5931
|
+
const enforcementRoot = verifyCompactUcanAuthorization(parsed.enforcementRoot.authorization, parsed.enforcementRoot.cid);
|
|
5932
|
+
const policyFact = policyRoot.payload.fct[0];
|
|
5933
|
+
const enforcementFact = enforcementRoot.payload.fct[0];
|
|
5934
|
+
const common = ["ownerDid", "policyId", "policyDigestHex", "policyCid", "contentSourceDigestHex", "capabilityCeilingHashHex", "nativeProjectionHashHex", "nodeAudience"];
|
|
5935
|
+
const policyKeys = [...common, "role", "mode"];
|
|
5936
|
+
const enforcementKeys = [...policyKeys, "enforcerDid"];
|
|
5937
|
+
if (Object.keys(policyFact).length !== policyKeys.length || policyKeys.some((key) => !(key in policyFact)) || Object.keys(enforcementFact).length !== enforcementKeys.length || enforcementKeys.some((key) => !(key in enforcementFact)) || policyRoot.payload.prf.length !== 0 || enforcementRoot.payload.prf.length !== 0 || policyRoot.payload.iss.split("#", 1)[0] !== policy.ownerDid || enforcementRoot.payload.iss.split("#", 1)[0] !== policy.ownerDid || policyRoot.payload.nbf !== enforcementRoot.payload.nbf || policyRoot.payload.exp !== enforcementRoot.payload.exp || canonicalize(policyRoot.payload.att) !== canonicalize(expectedAttenuation) || canonicalize(enforcementRoot.payload.att) !== canonicalize(expectedAttenuation) || policyFact.role !== "policy-authority" || policyFact.mode !== "policy-source" || "enforcerDid" in policyFact || enforcementFact.role !== "policy-enforcement" || enforcementFact.mode !== "conditional-mint" || policyRoot.payload.aud !== `did:tinycloud:policy:${hex(policyIdDigest)}` || enforcementRoot.payload.aud !== binding.enforcerDid || enforcementFact.enforcerDid !== binding.enforcerDid || common.some((key) => policyFact[key] !== enforcementFact[key]) || policyFact.ownerDid !== policy.ownerDid || policyFact.policyId !== policy.policyId || policyFact.policyDigestHex !== hex(policyIdDigest) || policyFact.policyCid !== parsed.policyCid || policyFact.contentSourceDigestHex !== parsed.contentSourceDigestHex || policyFact.capabilityCeilingHashHex !== capabilityCeilingHashHex || policyFact.nativeProjectionHashHex !== nativeProjectionHashHex || policyFact.nodeAudience !== binding.nodeAudience || policyRoot.payload.exp * 1e3 < Date.parse(parsed.expiry)) return false;
|
|
5938
|
+
} catch {
|
|
5939
|
+
return false;
|
|
5940
|
+
}
|
|
5941
|
+
return true;
|
|
5942
|
+
}
|
|
5943
|
+
function hex(bytes3) {
|
|
5944
|
+
return [...bytes3].map((byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
5945
|
+
}
|
|
5946
|
+
function base32Lower(bytes3) {
|
|
5947
|
+
const alphabet = "abcdefghijklmnopqrstuvwxyz234567";
|
|
5948
|
+
let output = "";
|
|
5949
|
+
let buffer = 0;
|
|
5950
|
+
let bits = 0;
|
|
5951
|
+
for (const byte of bytes3) {
|
|
5952
|
+
buffer = buffer << 8 | byte;
|
|
5953
|
+
bits += 8;
|
|
5954
|
+
while (bits >= 5) {
|
|
5955
|
+
output += alphabet[buffer >>> bits - 5 & 31];
|
|
5956
|
+
bits -= 5;
|
|
5957
|
+
}
|
|
5958
|
+
}
|
|
5959
|
+
if (bits > 0) output += alphabet[buffer << 5 - bits & 31];
|
|
5960
|
+
return output;
|
|
5961
|
+
}
|
|
5833
5962
|
var KEY_LENGTH2 = 32;
|
|
5834
5963
|
var INLINE_PREFIX = "#v=2&p=";
|
|
5835
5964
|
var MAX_INLINE_BYTES = 256 * 1024;
|
|
@@ -6014,8 +6143,12 @@ async function readResponseBytes(response, limit, tooLargeCode) {
|
|
|
6014
6143
|
function registryFetcher(options, limit, tooLargeCode = "max-bytes-exceeded", stage = "envelope") {
|
|
6015
6144
|
if (options.fetchBlob !== void 0) return async (input) => {
|
|
6016
6145
|
try {
|
|
6017
|
-
|
|
6146
|
+
options.signal?.throwIfAborted();
|
|
6147
|
+
const bytes3 = await options.fetchBlob({ ...input, ...options.signal === void 0 ? {} : { signal: options.signal } });
|
|
6148
|
+
options.signal?.throwIfAborted();
|
|
6149
|
+
return boundedBytes(bytes3, limit, tooLargeCode);
|
|
6018
6150
|
} catch (error) {
|
|
6151
|
+
if (options.signal?.aborted) throw options.signal.reason ?? error;
|
|
6019
6152
|
if (error instanceof ShareReceiveError) throw error;
|
|
6020
6153
|
throw new ShareReceiveError("fetch-failed", "registry unavailable", { stage });
|
|
6021
6154
|
}
|
|
@@ -6025,17 +6158,26 @@ function registryFetcher(options, limit, tooLargeCode = "max-bytes-exceeded", st
|
|
|
6025
6158
|
const base3 = options.registryBaseUrl.replace(/\/+$/, "");
|
|
6026
6159
|
return async ({ cid: cid2 }) => {
|
|
6027
6160
|
try {
|
|
6028
|
-
|
|
6161
|
+
options.signal?.throwIfAborted();
|
|
6162
|
+
const response = await fetchFn(`${base3}/ipfs/${cid2}?format=raw`, {
|
|
6163
|
+
headers: { accept: "application/vnd.ipld.raw" },
|
|
6164
|
+
redirect: "error",
|
|
6165
|
+
...options.signal === void 0 ? {} : { signal: options.signal }
|
|
6166
|
+
});
|
|
6167
|
+
options.signal?.throwIfAborted();
|
|
6029
6168
|
if (!response.ok) throw new ShareReceiveError("fetch-failed", `registry returned ${response.status}`, { stage });
|
|
6030
|
-
|
|
6169
|
+
const bytes3 = await readResponseBytes(response, limit, tooLargeCode);
|
|
6170
|
+
options.signal?.throwIfAborted();
|
|
6171
|
+
return bytes3;
|
|
6031
6172
|
} catch (error) {
|
|
6173
|
+
if (options.signal?.aborted) throw options.signal.reason ?? error;
|
|
6032
6174
|
if (error instanceof ShareReceiveError) throw error;
|
|
6033
6175
|
throw new ShareReceiveError("fetch-failed", "registry unavailable", { stage });
|
|
6034
6176
|
}
|
|
6035
6177
|
};
|
|
6036
6178
|
}
|
|
6037
6179
|
function metadataFor(envelope, origin) {
|
|
6038
|
-
if (envelope.version === 2) {
|
|
6180
|
+
if (envelope.version === 2 || envelope.version === 3) {
|
|
6039
6181
|
const targetKind3 = envelope.recipientMatcher.kind === "recipientDid" ? "recipientDid" : envelope.recipientMatcher.kind === "exactEmail" ? "email" : envelope.recipientMatcher.kind === "emailDomain" ? "emailDomain" : "bearer";
|
|
6040
6182
|
return {
|
|
6041
6183
|
protocol: "tinycloud-share",
|
|
@@ -6051,7 +6193,7 @@ function metadataFor(envelope, origin) {
|
|
|
6051
6193
|
...envelope.display.filename === void 0 ? {} : { filename: envelope.display.filename },
|
|
6052
6194
|
...envelope.display.mode === void 0 ? {} : { mode: envelope.display.mode }
|
|
6053
6195
|
},
|
|
6054
|
-
...envelope.
|
|
6196
|
+
...envelope.version === 2 && envelope.content !== void 0 ? { content: { cid: envelope.content.cid } } : {}
|
|
6055
6197
|
};
|
|
6056
6198
|
}
|
|
6057
6199
|
return {
|
|
@@ -6076,8 +6218,10 @@ function parseEnvelope(bytes3) {
|
|
|
6076
6218
|
const text = new TextDecoder("utf-8", { fatal: true }).decode(bytes3);
|
|
6077
6219
|
const value = JSON.parse(text);
|
|
6078
6220
|
if (canonicalize(value) !== text) throw new Error("share envelope is not canonical JSON");
|
|
6079
|
-
if (typeof value === "object" && value !== null && !Array.isArray(value)
|
|
6080
|
-
|
|
6221
|
+
if (typeof value === "object" && value !== null && !Array.isArray(value)) {
|
|
6222
|
+
const version = value.version;
|
|
6223
|
+
if (version === 3) return shareEnvelopeV3Schema.parse(value);
|
|
6224
|
+
if (version === 2) return shareEnvelopeV2Schema.parse(value);
|
|
6081
6225
|
}
|
|
6082
6226
|
return shareEnvelopeSchema.parse(value);
|
|
6083
6227
|
} catch {
|
|
@@ -6149,6 +6293,19 @@ async function verifyV2Envelope(envelope, linkOrigin, options) {
|
|
|
6149
6293
|
if (options.trustedSignerDid === void 0 || envelope.signature.signerDid !== options.trustedSignerDid) throw new ShareReceiveError("signature-invalid", "an addressed signer trust root is required");
|
|
6150
6294
|
}
|
|
6151
6295
|
}
|
|
6296
|
+
async function verifyV3Envelope(envelope, linkOrigin, options) {
|
|
6297
|
+
if (options.expectedOrigin !== void 0 && linkOrigin !== options.expectedOrigin) {
|
|
6298
|
+
throw new ShareReceiveError("origin-mismatch", "share link origin does not match the trusted origin");
|
|
6299
|
+
}
|
|
6300
|
+
try {
|
|
6301
|
+
if (!await verifyEnvelopeV3(envelope, { expectedSignerDid: envelope.policy.ownerDid })) throw new Error("signature");
|
|
6302
|
+
} catch {
|
|
6303
|
+
throw new ShareReceiveError("signature-invalid", "share signature is invalid");
|
|
6304
|
+
}
|
|
6305
|
+
const expiry = Date.parse(envelope.expiry);
|
|
6306
|
+
if (!Number.isFinite(expiry)) throw new ShareReceiveError("envelope-invalid", "share expiry is invalid");
|
|
6307
|
+
if (expiry <= (options.now?.() ?? Date.now())) throw new ShareReceiveError("expired", "share has expired", { expiresAt: envelope.expiry });
|
|
6308
|
+
}
|
|
6152
6309
|
async function verifyV1PolicyEnvelope(envelope, linkOrigin, options) {
|
|
6153
6310
|
if (envelope.authorizationTarget.kind !== "policy") throw new ShareReceiveError("unsupported-target", "share target is not an addressed policy", { reason: "policy-target" });
|
|
6154
6311
|
if (envelope.target.origin !== linkOrigin || options.expectedOrigin !== void 0 && (linkOrigin !== options.expectedOrigin || envelope.target.origin !== options.expectedOrigin)) {
|
|
@@ -6180,12 +6337,18 @@ async function inspectShare(link, options = {}) {
|
|
|
6180
6337
|
const resolved = await resolveShareEnvelope(link, options);
|
|
6181
6338
|
if (resolved.envelope.version === 1 && resolved.envelope.authorizationTarget.kind === "policy") await verifyV1PolicyEnvelope(resolved.envelope, resolved.origin, options);
|
|
6182
6339
|
else if (resolved.envelope.version === 1) await verifyResolved(resolved.envelope, resolved.origin, options);
|
|
6183
|
-
else await verifyV2Envelope(resolved.envelope, resolved.origin, options);
|
|
6184
|
-
|
|
6340
|
+
else if (resolved.envelope.version === 2) await verifyV2Envelope(resolved.envelope, resolved.origin, options);
|
|
6341
|
+
else await verifyV3Envelope(resolved.envelope, resolved.origin, options);
|
|
6342
|
+
if (resolved.envelope.version === 2 || resolved.envelope.version === 3 || resolved.envelope.authorizationTarget.kind === "policy") options.onResolvedAddressedEnvelope?.(resolved.envelope, resolved.cid);
|
|
6185
6343
|
return { metadata: metadataFor(resolved.envelope, resolved.origin), link: { origin: resolved.origin, cid: resolved.cid, kind: resolved.kind } };
|
|
6186
6344
|
}
|
|
6187
6345
|
async function receiveShare(link, options = {}) {
|
|
6188
6346
|
const resolved = await resolveShareEnvelope(link, options);
|
|
6347
|
+
if (resolved.envelope.version === 3) {
|
|
6348
|
+
await verifyV3Envelope(resolved.envelope, resolved.origin, options);
|
|
6349
|
+
options.onResolvedAddressedEnvelope?.(resolved.envelope, resolved.cid);
|
|
6350
|
+
return { state: "authorization-required", method: resolved.envelope.recipientMatcher.kind === "recipientDid" ? "openkey-device" : "email-claim" };
|
|
6351
|
+
}
|
|
6189
6352
|
if (resolved.envelope.version === 2) {
|
|
6190
6353
|
await verifyV2Envelope(resolved.envelope, resolved.origin, options);
|
|
6191
6354
|
options.onResolvedAddressedEnvelope?.(resolved.envelope, resolved.cid);
|
|
@@ -8728,11 +8891,80 @@ function uiAction(action) {
|
|
|
8728
8891
|
function selectedAction(envelope) {
|
|
8729
8892
|
return envelope.actions.includes("list") ? "tinycloud.kv/list" : envelope.actions.includes("edit") ? "tinycloud.kv/put" : "tinycloud.kv/get";
|
|
8730
8893
|
}
|
|
8731
|
-
function
|
|
8894
|
+
function policyAttenuationForV3(envelope) {
|
|
8895
|
+
const policy = object2(envelope.policy, "v3 policy");
|
|
8896
|
+
if (!Array.isArray(policy.capabilityCeiling)) throw new Error("v3 policy capability ceiling is invalid");
|
|
8897
|
+
const attenuation = {};
|
|
8898
|
+
for (const raw of policy.capabilityCeiling) {
|
|
8899
|
+
const capability = object2(raw, "v3 policy capability");
|
|
8900
|
+
if (typeof capability.resource !== "string" || attenuation[capability.resource] !== void 0) throw new Error("v3 policy capability is invalid");
|
|
8901
|
+
if (capability.kind === "encryption") {
|
|
8902
|
+
if (capability.action !== "tinycloud.encryption/decrypt") throw new Error("v3 policy capability is invalid");
|
|
8903
|
+
attenuation[capability.resource] = { [capability.action]: [{}] };
|
|
8904
|
+
continue;
|
|
8905
|
+
}
|
|
8906
|
+
if (capability.kind !== "kv" || capability.selector !== "exact" && capability.selector !== "prefix" || !Array.isArray(capability.actions) || capability.actions.length === 0 || capability.actions.some((action) => typeof action !== "string")) throw new Error("v3 policy capability is invalid");
|
|
8907
|
+
attenuation[capability.resource] = Object.fromEntries(capability.actions.map((action) => [action, [{
|
|
8908
|
+
type: "xyz.tinycloud.resource/selector",
|
|
8909
|
+
kind: capability.selector,
|
|
8910
|
+
value: capability.resource
|
|
8911
|
+
}]]));
|
|
8912
|
+
}
|
|
8913
|
+
return attenuation;
|
|
8914
|
+
}
|
|
8915
|
+
var POLICY_SESSION_FACT_KEYS = [
|
|
8916
|
+
"profile",
|
|
8917
|
+
"ownerDid",
|
|
8918
|
+
"policyId",
|
|
8919
|
+
"policyDigestHex",
|
|
8920
|
+
"policyCid",
|
|
8921
|
+
"policyDelegationCid",
|
|
8922
|
+
"enforcementDelegationCid",
|
|
8923
|
+
"contentSourceDigestHex",
|
|
8924
|
+
"capabilityCeilingHashHex",
|
|
8925
|
+
"nativeProjectionHashHex",
|
|
8926
|
+
"enforcerDid",
|
|
8927
|
+
"nodeAudience",
|
|
8928
|
+
"recipientDid",
|
|
8929
|
+
"challengeId",
|
|
8930
|
+
"claimDigestHex",
|
|
8931
|
+
"claimJti",
|
|
8932
|
+
"vpDigestHex",
|
|
8933
|
+
"credentialEvidenceDigestHex",
|
|
8934
|
+
"decisionContextDigestHex",
|
|
8935
|
+
"issuanceAuditDigestHex",
|
|
8936
|
+
"remainingRedelegationDepth"
|
|
8937
|
+
];
|
|
8938
|
+
var POLICY_SESSION_V4_AUDIT_FACT_KEYS = ["credentialIdAuditDigestHex", "presentationJtiAuditDigestHex"];
|
|
8939
|
+
var POLICY_SESSION_DIGEST_FACT_KEYS = [
|
|
8940
|
+
"policyDigestHex",
|
|
8941
|
+
"contentSourceDigestHex",
|
|
8942
|
+
"capabilityCeilingHashHex",
|
|
8943
|
+
"nativeProjectionHashHex",
|
|
8944
|
+
"claimDigestHex",
|
|
8945
|
+
"vpDigestHex",
|
|
8946
|
+
"credentialEvidenceDigestHex",
|
|
8947
|
+
"decisionContextDigestHex",
|
|
8948
|
+
"issuanceAuditDigestHex"
|
|
8949
|
+
];
|
|
8950
|
+
var LOWER_SHA256_HEX = /^[0-9a-f]{64}$/;
|
|
8951
|
+
function verifyV3PolicyAuthorization(input) {
|
|
8952
|
+
const compact = verifyCompactUcanAuthorization(input.authorization, input.cid);
|
|
8953
|
+
const fact = compact.payload.fct[0];
|
|
8954
|
+
const factKeys = Object.keys(fact);
|
|
8955
|
+
const legacyShape = factKeys.length === POLICY_SESSION_FACT_KEYS.length && factKeys.every((key) => POLICY_SESSION_FACT_KEYS.includes(key)) && POLICY_SESSION_FACT_KEYS.every((key) => key in fact);
|
|
8956
|
+
const v4Shape = factKeys.length === POLICY_SESSION_FACT_KEYS.length + POLICY_SESSION_V4_AUDIT_FACT_KEYS.length && factKeys.every((key) => POLICY_SESSION_FACT_KEYS.includes(key) || POLICY_SESSION_V4_AUDIT_FACT_KEYS.includes(key)) && POLICY_SESSION_FACT_KEYS.every((key) => key in fact) && POLICY_SESSION_V4_AUDIT_FACT_KEYS.every((key) => key in fact);
|
|
8957
|
+
const binding = object2(input.envelope.attestedEnforcerBinding, "v3 attested enforcer binding");
|
|
8958
|
+
const policy = object2(input.envelope.policy, "v3 policy");
|
|
8959
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
8960
|
+
if (!legacyShape && !v4Shape || POLICY_SESSION_DIGEST_FACT_KEYS.some((key) => !LOWER_SHA256_HEX.test(fact[key])) || v4Shape && (!LOWER_SHA256_HEX.test(fact.credentialIdAuditDigestHex) || !LOWER_SHA256_HEX.test(fact.presentationJtiAuditDigestHex)) || compact.payload.aud !== input.holderDid || compact.payload.iss.split("#", 1)[0] !== fact.nodeAudience || fact.profile !== "policy-session-ucan/v1" || fact.ownerDid !== policy.ownerDid || fact.policyId !== policy.policyId || fact.policyCid !== input.envelope.policyCid || fact.contentSourceDigestHex !== input.envelope.contentSourceDigestHex || fact.enforcerDid !== binding.enforcerDid || fact.nodeAudience !== binding.nodeAudience || fact.recipientDid !== input.holderDid || fact.policyDelegationCid !== input.envelope.policyRoot.cid || fact.enforcementDelegationCid !== input.envelope.enforcementRoot.cid || typeof fact.remainingRedelegationDepth !== "number" || !Number.isInteger(fact.remainingRedelegationDepth) || fact.remainingRedelegationDepth < 0 || fact.remainingRedelegationDepth > 8 || compact.payload.prf.length !== 2 || compact.payload.prf[0] !== input.envelope.policyRoot.cid || compact.payload.prf[1] !== input.envelope.enforcementRoot.cid || compact.payload.nbf > now || compact.payload.exp <= now || compact.payload.exp - compact.payload.nbf > 60 || canonicalize(compact.payload.att) !== canonicalize(policyAttenuationForV3(input.envelope))) throw new Error("v3 policy delegation signed binding mismatch");
|
|
8961
|
+
return compact;
|
|
8962
|
+
}
|
|
8963
|
+
function hex2(value) {
|
|
8732
8964
|
return [...value].map((byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
8733
8965
|
}
|
|
8734
8966
|
function canonicalHashHex(value) {
|
|
8735
|
-
return
|
|
8967
|
+
return hex2(sha2566(new TextEncoder().encode(canonicalize(value))));
|
|
8736
8968
|
}
|
|
8737
8969
|
async function aesGcmDecrypt(key, blob) {
|
|
8738
8970
|
if (key.length !== 32 || blob.length < 28) throw new Error("encrypted content is malformed");
|
|
@@ -8788,6 +9020,20 @@ var ShareRecipientClient = class {
|
|
|
8788
9020
|
this.options = options;
|
|
8789
9021
|
this.fetchFn = options.fetchFn ?? globalThis.fetch.bind(globalThis);
|
|
8790
9022
|
this.signer = options.sign;
|
|
9023
|
+
if (options.policyAuthorization !== void 0) {
|
|
9024
|
+
if (options.envelope.version !== 3) throw new Error("policy delegation fast path requires a v3 envelope");
|
|
9025
|
+
const compact = verifyV3PolicyAuthorization({ ...options.policyAuthorization, envelope: options.envelope, holderDid: options.holderDid });
|
|
9026
|
+
const nodeAudience = compact.payload.fct[0].nodeAudience;
|
|
9027
|
+
this.v3Authorization = options.policyAuthorization.authorization;
|
|
9028
|
+
this.v3NodeAudience = nodeAudience;
|
|
9029
|
+
this.nativeSigner = options.sign;
|
|
9030
|
+
this.session = {
|
|
9031
|
+
sessionId: options.policyAuthorization.cid,
|
|
9032
|
+
expiresAt: new Date(compact.payload.exp * 1e3).toISOString(),
|
|
9033
|
+
actions: options.envelope.actions,
|
|
9034
|
+
resource: options.envelope.resource
|
|
9035
|
+
};
|
|
9036
|
+
}
|
|
8791
9037
|
}
|
|
8792
9038
|
fetchFn;
|
|
8793
9039
|
session;
|
|
@@ -8795,6 +9041,7 @@ var ShareRecipientClient = class {
|
|
|
8795
9041
|
nativeSigner;
|
|
8796
9042
|
holderProof;
|
|
8797
9043
|
v3Authorization;
|
|
9044
|
+
v3NodeAudience;
|
|
8798
9045
|
v3ContentKey;
|
|
8799
9046
|
v3ContentEnvelope;
|
|
8800
9047
|
async beginChallenge(envelope) {
|
|
@@ -8884,37 +9131,52 @@ var ShareRecipientClient = class {
|
|
|
8884
9131
|
}
|
|
8885
9132
|
async establishV3(envelope) {
|
|
8886
9133
|
if (this.options.buildPresentation === void 0) throw new Error("share presentation builder is required");
|
|
9134
|
+
const attestedEnforcer = object2(envelope.attestedEnforcerBinding, "v3 attested enforcer binding");
|
|
9135
|
+
if (attestedEnforcer.enforcerDid !== envelope.target.nodeAudience || typeof attestedEnforcer.nodeAudience !== "string") throw new Error("v3 attested enforcer binding mismatch");
|
|
9136
|
+
try {
|
|
9137
|
+
ed25519PublicKeyFromDidKey(attestedEnforcer.nodeAudience);
|
|
9138
|
+
} catch {
|
|
9139
|
+
throw new Error("v3 attested Node audience is invalid");
|
|
9140
|
+
}
|
|
8887
9141
|
const challengeResponse = await this.fetchFn(new URL("/share/v3/policy/challenges", this.options.nodeOrigin), {
|
|
8888
9142
|
method: "POST",
|
|
8889
9143
|
redirect: "error",
|
|
8890
9144
|
headers: { accept: "application/json", "content-type": "application/json" },
|
|
8891
|
-
body: JSON.stringify({ policyCid: envelope.policyCid, recipientDid: this.options.holderDid, requestedCapabilities: envelope.policy.capabilityCeiling })
|
|
9145
|
+
body: JSON.stringify({ policyCid: envelope.policyCid, recipientDid: this.options.holderDid, requestedCapabilities: envelope.policy.capabilityCeiling }),
|
|
9146
|
+
...this.options.signal === void 0 ? {} : { signal: this.options.signal }
|
|
8892
9147
|
});
|
|
8893
9148
|
if (!challengeResponse.ok) throw new Error(`v3 policy challenge rejected (${challengeResponse.status})`);
|
|
8894
9149
|
const challenge = object2(await challengeResponse.json(), "v3 policy challenge");
|
|
8895
|
-
if (typeof challenge.challengeId !== "string" || typeof challenge.nonce !== "string" || challenge.policyCid !== envelope.policyCid || challenge.recipientDid !== this.options.holderDid) throw new Error("v3 policy challenge binding mismatch");
|
|
9150
|
+
if (typeof challenge.challengeId !== "string" || typeof challenge.nonce !== "string" || challenge.policyCid !== envelope.policyCid || challenge.recipientDid !== this.options.holderDid || challenge.nodeAudience !== attestedEnforcer.nodeAudience || challenge.enforcerDid !== void 0 && challenge.enforcerDid !== attestedEnforcer.enforcerDid || typeof challenge.expiresAt !== "string" || !Number.isFinite(Date.parse(challenge.expiresAt)) || Date.parse(challenge.expiresAt) <= Date.now()) throw new Error("v3 policy challenge binding mismatch");
|
|
8896
9151
|
const material = await this.options.buildPresentation({
|
|
8897
9152
|
challenge,
|
|
8898
9153
|
envelope,
|
|
8899
9154
|
policy: envelope.policy
|
|
8900
9155
|
});
|
|
8901
|
-
if (material.sign === void 0 || material.
|
|
9156
|
+
if (material.sign === void 0 || material.presentation === void 0) throw new Error("v3 ceremony requires a recipient signer and presentation");
|
|
9157
|
+
const accountless = material.presentation.schema === "xyz.tinycloud.policy/presentation/v4";
|
|
9158
|
+
if (accountless ? material.credentialEnvelope === void 0 || material.requirement === void 0 : material.claim === void 0) {
|
|
9159
|
+
throw new Error(accountless ? "v4 ceremony requires a verified credential and requirement" : "v3 ceremony requires a claim");
|
|
9160
|
+
}
|
|
9161
|
+
this.options.onStage?.("policy-admission");
|
|
8902
9162
|
const delegationResponse = await this.fetchFn(new URL("/share/v3/policy/delegations", this.options.nodeOrigin), {
|
|
8903
9163
|
method: "POST",
|
|
8904
9164
|
redirect: "error",
|
|
8905
9165
|
headers: { accept: "application/json", "content-type": "application/json" },
|
|
8906
|
-
body: JSON.stringify({ policyCid: envelope.policyCid, challengeId: challenge.challengeId, nonce: challenge.nonce, claim: material.claim, presentation: material.presentation })
|
|
9166
|
+
body: JSON.stringify(accountless ? { policyCid: envelope.policyCid, challengeId: challenge.challengeId, nonce: challenge.nonce, requirement: material.requirement, credential: material.credentialEnvelope, presentation: material.presentation } : { policyCid: envelope.policyCid, challengeId: challenge.challengeId, nonce: challenge.nonce, claim: material.claim, presentation: material.presentation }),
|
|
9167
|
+
...this.options.signal === void 0 ? {} : { signal: this.options.signal }
|
|
8907
9168
|
});
|
|
8908
9169
|
if (!delegationResponse.ok) throw new Error(`v3 policy delegation rejected (${delegationResponse.status})`);
|
|
8909
9170
|
const delegation = object2(await delegationResponse.json(), "v3 policy delegation");
|
|
8910
9171
|
if (delegation.admitted !== true || typeof delegation.sessionCid !== "string" || typeof delegation.authorization !== "string") throw new Error("v3 policy delegation response is not admitted");
|
|
8911
|
-
const compact =
|
|
9172
|
+
const compact = verifyV3PolicyAuthorization({ authorization: delegation.authorization, cid: delegation.sessionCid, envelope, holderDid: this.options.holderDid });
|
|
8912
9173
|
const fact = compact.payload.fct[0];
|
|
8913
|
-
|
|
8914
|
-
const imported = await this.fetchFn(new URL("/delegate", this.options.nodeOrigin), { method: "POST", redirect: "error", headers: { Authorization: delegation.authorization } });
|
|
9174
|
+
this.options.onStage?.("delegation-import");
|
|
9175
|
+
const imported = await this.fetchFn(new URL("/delegate", this.options.nodeOrigin), { method: "POST", redirect: "error", headers: { Authorization: delegation.authorization }, ...this.options.signal === void 0 ? {} : { signal: this.options.signal } });
|
|
8915
9176
|
if (!imported.ok) throw new Error(`ordinary delegation import rejected (${imported.status})`);
|
|
8916
9177
|
this.signer = material.sign;
|
|
8917
9178
|
this.v3Authorization = delegation.authorization;
|
|
9179
|
+
this.v3NodeAudience = fact.nodeAudience;
|
|
8918
9180
|
this.session = { sessionId: delegation.sessionCid, expiresAt: new Date(compact.payload.exp * 1e3).toISOString(), actions: envelope.actions, resource: envelope.resource };
|
|
8919
9181
|
return this.session;
|
|
8920
9182
|
}
|
|
@@ -8922,18 +9184,18 @@ var ShareRecipientClient = class {
|
|
|
8922
9184
|
async decryptV3Content(bytes3) {
|
|
8923
9185
|
const envelope = this.options.envelope;
|
|
8924
9186
|
const signer = this.nativeSigner ?? this.signer;
|
|
8925
|
-
if (envelope.version !== 3 || this.session === void 0 || this.v3Authorization === void 0 || signer === void 0) throw new Error("v3 policy session signer is required");
|
|
9187
|
+
if (envelope.version !== 3 || this.session === void 0 || this.v3Authorization === void 0 || this.v3NodeAudience === void 0 || signer === void 0) throw new Error("v3 policy session signer is required");
|
|
8926
9188
|
const encrypted = parseV3InlineEncryptedEnvelope(bytes3, envelope);
|
|
8927
9189
|
const receiverPrivateKey = crypto.getRandomValues(new Uint8Array(32));
|
|
8928
9190
|
const receiverPublicKey = toBase64Url(x25519.getPublicKey(receiverPrivateKey));
|
|
8929
9191
|
const receiverPublicKeyHash = canonicalHashHex(receiverPublicKey);
|
|
8930
|
-
const body = { type: "tinycloud.encryption.decrypt/v1", targetNode:
|
|
8931
|
-
const bodyHash =
|
|
9192
|
+
const body = { type: "tinycloud.encryption.decrypt/v1", targetNode: this.v3NodeAudience, networkId: encrypted.networkId, alg: encrypted.alg, keyVersion: encrypted.keyVersion, encryptedSymmetricKey: encrypted.encryptedSymmetricKey, encryptedSymmetricKeyHash: encrypted.encryptedSymmetricKeyHash, receiverPublicKey, receiverPublicKeyHash };
|
|
9193
|
+
const bodyHash = hex2(sha2566(new TextEncoder().encode(canonicalize(body))));
|
|
8932
9194
|
const session = verifyCompactUcanAuthorization(this.v3Authorization, this.session.sessionId);
|
|
8933
9195
|
const now = Math.floor(Date.now() / 1e3);
|
|
8934
9196
|
const invocation = await signCompactUcanAuthorization({
|
|
8935
9197
|
issuerDid: this.options.holderDid,
|
|
8936
|
-
audienceDid:
|
|
9198
|
+
audienceDid: this.v3NodeAudience,
|
|
8937
9199
|
attenuation: { [encrypted.networkId]: { "tinycloud.encryption/decrypt": [{}] } },
|
|
8938
9200
|
facts: [{ type: body.type, targetNode: body.targetNode, networkId: body.networkId, bodyHash, encryptedSymmetricKeyHash: body.encryptedSymmetricKeyHash, receiverPublicKeyHash, alg: body.alg, keyVersion: body.keyVersion }],
|
|
8939
9201
|
proofs: [session.cid],
|
|
@@ -8943,11 +9205,12 @@ var ShareRecipientClient = class {
|
|
|
8943
9205
|
sign: signer
|
|
8944
9206
|
});
|
|
8945
9207
|
try {
|
|
8946
|
-
|
|
9208
|
+
this.options.onStage?.("decryption");
|
|
9209
|
+
const response = await this.fetchFn(new URL("/invoke", this.options.nodeOrigin), { method: "POST", redirect: "error", headers: { accept: "application/json", "content-type": "application/json", Authorization: invocation.authorization }, body: JSON.stringify(body), ...this.options.signal === void 0 ? {} : { signal: this.options.signal } });
|
|
8947
9210
|
if (!response.ok) throw new Error(`v3 decrypt invocation rejected (${response.status})`);
|
|
8948
9211
|
const value = object2(await response.json(), "v3 decrypt response");
|
|
8949
9212
|
const allowed = ["type", "targetNode", "networkId", "invocationCid", "encryptedSymmetricKeyHash", "receiverPublicKeyHash", "wrappedKey", "alg", "keyVersion", "requestHash", "nodeId", "nodeSignature"];
|
|
8950
|
-
if (Object.keys(value).length !== allowed.length || Object.keys(value).some((key) => !allowed.includes(key)) || value.type !== "tinycloud.encryption.decrypt-result/v1" || value.targetNode !== body.targetNode || value.nodeId !== body.targetNode || value.networkId !== body.networkId || value.invocationCid !== invocation.cid || value.encryptedSymmetricKeyHash !== body.encryptedSymmetricKeyHash || value.receiverPublicKeyHash !== receiverPublicKeyHash || value.alg !== body.alg || value.keyVersion !== body.keyVersion || value.requestHash !==
|
|
9213
|
+
if (Object.keys(value).length !== allowed.length || Object.keys(value).some((key) => !allowed.includes(key)) || value.type !== "tinycloud.encryption.decrypt-result/v1" || value.targetNode !== body.targetNode || value.nodeId !== body.targetNode || value.networkId !== body.networkId || value.invocationCid !== invocation.cid || value.encryptedSymmetricKeyHash !== body.encryptedSymmetricKeyHash || value.receiverPublicKeyHash !== receiverPublicKeyHash || value.alg !== body.alg || value.keyVersion !== body.keyVersion || value.requestHash !== hex2(sha2566(new TextEncoder().encode(`${invocation.cid}${bodyHash}`))) || typeof value.wrappedKey !== "string" || typeof value.nodeSignature !== "string") throw new Error("v3 decrypt response binding is invalid");
|
|
8951
9214
|
const unsigned = { ...value };
|
|
8952
9215
|
delete unsigned.nodeSignature;
|
|
8953
9216
|
const signature = fromBase64(value.nodeSignature, "v3 decrypt response signature");
|
|
@@ -9017,7 +9280,7 @@ var ShareRecipientClient = class {
|
|
|
9017
9280
|
}
|
|
9018
9281
|
async nativeInvokeV3(request, envelope) {
|
|
9019
9282
|
const signer = this.nativeSigner ?? this.signer;
|
|
9020
|
-
if (this.session === void 0 || this.v3Authorization === void 0 || signer === void 0) throw new Error("v3 policy session signer is required");
|
|
9283
|
+
if (this.session === void 0 || this.v3Authorization === void 0 || this.v3NodeAudience === void 0 || signer === void 0) throw new Error("v3 policy session signer is required");
|
|
9021
9284
|
const session = verifyCompactUcanAuthorization(this.v3Authorization, this.session.sessionId);
|
|
9022
9285
|
if (session.payload.aud !== this.options.holderDid) throw new Error("v3 session recipient mismatch");
|
|
9023
9286
|
const action = request.action === "list" ? "tinycloud.kv/list" : request.action === "put" ? "tinycloud.kv/put" : request.action === "metadata" ? "tinycloud.kv/metadata" : "tinycloud.kv/get";
|
|
@@ -9031,7 +9294,7 @@ var ShareRecipientClient = class {
|
|
|
9031
9294
|
if (path !== root && !(capability.selector === "prefix" && path.startsWith(`${root}/`))) throw new Error("v3 KV request is outside the signed selector");
|
|
9032
9295
|
const resource = `${capability.resource.slice(0, split + marker.length)}${path}`;
|
|
9033
9296
|
const now = Math.floor(Date.now() / 1e3);
|
|
9034
|
-
const invocation = await signCompactUcanAuthorization({ issuerDid: this.options.holderDid, audienceDid:
|
|
9297
|
+
const invocation = await signCompactUcanAuthorization({ issuerDid: this.options.holderDid, audienceDid: this.v3NodeAudience, attenuation: { [resource]: { [action]: [{ type: "xyz.tinycloud.resource/selector", kind: capability.selector, value: resource }] } }, facts: [{ type: "tinycloud.policy.invocation/v1", policyCid: envelope.policyCid, sessionCid: session.cid }], proofs: [session.cid], notBefore: now, expiresAt: Math.min(now + 60, session.payload.exp), nonce: toBase64Url(crypto.getRandomValues(new Uint8Array(16))), sign: signer });
|
|
9035
9298
|
const headers = new Headers({ accept: "application/json", Authorization: invocation.authorization });
|
|
9036
9299
|
let body;
|
|
9037
9300
|
if (action === "tinycloud.kv/put") {
|
|
@@ -9040,7 +9303,8 @@ var ShareRecipientClient = class {
|
|
|
9040
9303
|
headers.set("content-type", request.contentType ?? "application/octet-stream");
|
|
9041
9304
|
if (request.ifMatch !== void 0) headers.set("if-match", request.ifMatch);
|
|
9042
9305
|
}
|
|
9043
|
-
|
|
9306
|
+
this.options.onStage?.("invocation");
|
|
9307
|
+
return this.fetchFn(new URL("/invoke", this.options.nodeOrigin), { method: "POST", redirect: "error", headers, ...body === void 0 ? {} : { body }, ...this.options.signal === void 0 ? {} : { signal: this.options.signal } });
|
|
9044
9308
|
}
|
|
9045
9309
|
};
|
|
9046
9310
|
function createAddressedAuthorization(input) {
|