@tinycloud/share-sdk 0.2.0-beta.0 → 0.2.0-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +432 -62
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +27 -4
- package/dist/index.d.ts +27 -4
- package/dist/index.js +432 -62
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -152,10 +152,10 @@ var util;
|
|
|
152
152
|
return obj[e];
|
|
153
153
|
});
|
|
154
154
|
};
|
|
155
|
-
util2.objectKeys = typeof Object.keys === "function" ? (obj) => Object.keys(obj) : (
|
|
155
|
+
util2.objectKeys = typeof Object.keys === "function" ? (obj) => Object.keys(obj) : (object3) => {
|
|
156
156
|
const keys = [];
|
|
157
|
-
for (const key in
|
|
158
|
-
if (Object.prototype.hasOwnProperty.call(
|
|
157
|
+
for (const key in object3) {
|
|
158
|
+
if (Object.prototype.hasOwnProperty.call(object3, key)) {
|
|
159
159
|
keys.push(key);
|
|
160
160
|
}
|
|
161
161
|
}
|
|
@@ -4051,6 +4051,9 @@ var NEVER = INVALID;
|
|
|
4051
4051
|
|
|
4052
4052
|
// ../share-envelope/dist/index.js
|
|
4053
4053
|
import { ed25519 as ed255192 } from "@noble/curves/ed25519";
|
|
4054
|
+
import { blake3 } from "@noble/hashes/blake3";
|
|
4055
|
+
import { ed25519 as ed255193 } from "@noble/curves/ed25519";
|
|
4056
|
+
import { sha256 as sha2562 } from "@noble/hashes/sha256";
|
|
4054
4057
|
var empty = new Uint8Array(0);
|
|
4055
4058
|
function equals(aa, bb) {
|
|
4056
4059
|
if (aa === bb) {
|
|
@@ -5223,6 +5226,128 @@ function validateV2Invariants(value, ctx) {
|
|
|
5223
5226
|
}
|
|
5224
5227
|
var unsignedShareEnvelopeV2Schema = unsignedShareEnvelopeV2BaseSchema.superRefine(validateV2Invariants);
|
|
5225
5228
|
var shareEnvelopeV2Schema = unsignedShareEnvelopeV2BaseSchema.extend({ signature: signatureSchema }).strict().superRefine(validateV2Invariants);
|
|
5229
|
+
var unifiedResourceSchema = external_exports.string().refine((value) => {
|
|
5230
|
+
const match = /^tinycloud:\/\/([^/]+)\/kv\/(.+)$/.exec(value);
|
|
5231
|
+
if (match === null || /[:?#%]/.test(match[1])) return false;
|
|
5232
|
+
const path = match[2];
|
|
5233
|
+
return !path.startsWith("/") && !path.endsWith("/") && !path.includes("//") && path.split("/").every((segment) => segment.length > 0 && segment !== "." && segment !== "..");
|
|
5234
|
+
}, { message: "expected canonical TinyCloud KV resource" });
|
|
5235
|
+
var unifiedEncryptionNetworkSchema = external_exports.string().refine((value) => {
|
|
5236
|
+
if (!value.startsWith("urn:tinycloud:encryption:")) return false;
|
|
5237
|
+
const rest = value.slice("urn:tinycloud:encryption:".length);
|
|
5238
|
+
const separator = rest.lastIndexOf(":");
|
|
5239
|
+
const owner = separator < 0 ? "" : rest.slice(0, separator);
|
|
5240
|
+
const network = separator < 0 ? "" : rest.slice(separator + 1);
|
|
5241
|
+
return owner.startsWith("did:") && owner.length > 4 && network.length > 0 && !/[:/%?#\s]/.test(network);
|
|
5242
|
+
}, { message: "expected canonical TinyCloud encryption network" });
|
|
5243
|
+
var unifiedKvCapabilitySchema = external_exports.object({
|
|
5244
|
+
kind: external_exports.literal("kv"),
|
|
5245
|
+
resource: unifiedResourceSchema,
|
|
5246
|
+
selector: external_exports.union([external_exports.literal("exact"), external_exports.literal("prefix")]),
|
|
5247
|
+
actions: external_exports.array(external_exports.union([
|
|
5248
|
+
external_exports.literal("tinycloud.kv/get"),
|
|
5249
|
+
external_exports.literal("tinycloud.kv/list"),
|
|
5250
|
+
external_exports.literal("tinycloud.kv/metadata"),
|
|
5251
|
+
external_exports.literal("tinycloud.kv/put")
|
|
5252
|
+
])).min(1)
|
|
5253
|
+
}).strict();
|
|
5254
|
+
var unifiedEncryptionCapabilitySchema = external_exports.object({
|
|
5255
|
+
kind: external_exports.literal("encryption"),
|
|
5256
|
+
resource: unifiedEncryptionNetworkSchema,
|
|
5257
|
+
action: external_exports.literal("tinycloud.encryption/decrypt")
|
|
5258
|
+
}).strict();
|
|
5259
|
+
var unifiedCapabilitySchema = external_exports.union([unifiedKvCapabilitySchema, unifiedEncryptionCapabilitySchema]);
|
|
5260
|
+
var unifiedContentSourceSchema = external_exports.object({
|
|
5261
|
+
shareId: external_exports.string().min(1),
|
|
5262
|
+
kvResource: unifiedResourceSchema,
|
|
5263
|
+
selector: external_exports.union([external_exports.literal("exact"), external_exports.literal("prefix")]),
|
|
5264
|
+
encryptionNetwork: unifiedEncryptionNetworkSchema,
|
|
5265
|
+
encryptedSymmetricKeyDigestHex: external_exports.string().regex(/^[0-9a-f]{64}$/),
|
|
5266
|
+
keyVersion: external_exports.number().int().positive(),
|
|
5267
|
+
mode: external_exports.union([external_exports.literal("mutable"), external_exports.literal("immutable")]),
|
|
5268
|
+
initialCiphertextDigestHex: external_exports.string().regex(/^[0-9a-f]{64}$/).optional()
|
|
5269
|
+
}).strict();
|
|
5270
|
+
var unifiedPolicySchema = external_exports.object({
|
|
5271
|
+
schema: external_exports.literal("xyz.tinycloud.policy/policy/v1"),
|
|
5272
|
+
policyId: external_exports.string().regex(/^pol_[a-z2-7]+$/),
|
|
5273
|
+
ownerDid: external_exports.string().min(1),
|
|
5274
|
+
createdAt: external_exports.string().datetime({ offset: true }),
|
|
5275
|
+
expiresAt: external_exports.string().datetime({ offset: true }).optional(),
|
|
5276
|
+
contentSource: unifiedContentSourceSchema,
|
|
5277
|
+
capabilityCeiling: external_exports.array(unifiedCapabilitySchema).min(2),
|
|
5278
|
+
signature: external_exports.object({ suite: external_exports.string().min(1), signerDid: external_exports.string().min(1), value: external_exports.string().min(1) }).strict()
|
|
5279
|
+
}).strict();
|
|
5280
|
+
var unifiedRootSchema = external_exports.object({
|
|
5281
|
+
cid: external_exports.string().min(1),
|
|
5282
|
+
authorization: external_exports.string().regex(/^[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$/),
|
|
5283
|
+
role: external_exports.union([external_exports.literal("policy-authority"), external_exports.literal("policy-enforcement")])
|
|
5284
|
+
}).strict();
|
|
5285
|
+
var attestedEnforcerBindingV2Schema = external_exports.object({
|
|
5286
|
+
schema: external_exports.literal("xyz.tinycloud.policy/attested-enforcer/v2"),
|
|
5287
|
+
enforcerDid: external_exports.string().regex(/^did:key:z[1-9A-HJ-NP-Za-km-z]+$/),
|
|
5288
|
+
nodeAudience: external_exports.string().min(1),
|
|
5289
|
+
attestationBindingDigestHex: external_exports.string().regex(/^[0-9a-f]{64}$/),
|
|
5290
|
+
issuedAt: external_exports.string().datetime({ offset: true }),
|
|
5291
|
+
expiresAt: external_exports.string().datetime({ offset: true }),
|
|
5292
|
+
signature: external_exports.object({ suite: external_exports.literal("Ed25519"), signerDid: external_exports.string().min(1), value: base64UrlString() }).strict()
|
|
5293
|
+
}).strict();
|
|
5294
|
+
var v3TargetSchema = external_exports.object({
|
|
5295
|
+
origin: external_exports.string().refine(isCanonicalHttpsOrigin, { message: "expected a canonical https origin" }),
|
|
5296
|
+
nodeAudience: external_exports.string().min(1),
|
|
5297
|
+
spaceId: external_exports.string().refine(isCanonicalPathSegment, { message: "expected a canonical space id" })
|
|
5298
|
+
}).strict();
|
|
5299
|
+
var unsignedShareEnvelopeV3BaseSchema = external_exports.object({
|
|
5300
|
+
version: external_exports.literal(3),
|
|
5301
|
+
shareId: external_exports.string().min(1),
|
|
5302
|
+
recipientMatcher: recipientMatcherSchema,
|
|
5303
|
+
deliveryEmail: external_exports.string().email().optional(),
|
|
5304
|
+
actions: external_exports.array(shareActionSchema).min(1).max(3),
|
|
5305
|
+
resource: resourceSelectorSchema,
|
|
5306
|
+
target: v3TargetSchema,
|
|
5307
|
+
policy: unifiedPolicySchema,
|
|
5308
|
+
policyCid: external_exports.string().min(1),
|
|
5309
|
+
policyRoot: unifiedRootSchema,
|
|
5310
|
+
enforcementRoot: unifiedRootSchema,
|
|
5311
|
+
attestedEnforcerBinding: attestedEnforcerBindingV2Schema,
|
|
5312
|
+
contentSource: unifiedContentSourceSchema,
|
|
5313
|
+
contentSourceDigestHex: external_exports.string().regex(/^[0-9a-f]{64}$/),
|
|
5314
|
+
encryptionNetwork: unifiedEncryptionNetworkSchema,
|
|
5315
|
+
expiry: external_exports.string().datetime({ offset: true }),
|
|
5316
|
+
display: displaySchema,
|
|
5317
|
+
encrypted: external_exports.literal(true),
|
|
5318
|
+
metadata: contentMetadataSchema
|
|
5319
|
+
}).strict();
|
|
5320
|
+
function validateV3Invariants(value, ctx) {
|
|
5321
|
+
const actions = [...value.actions];
|
|
5322
|
+
if (new Set(actions).size !== actions.length || actions.some((action, index) => action !== ["read", "list", "edit"].filter((candidate) => actions.includes(candidate))[index])) {
|
|
5323
|
+
ctx.addIssue({ code: external_exports.ZodIssueCode.custom, path: ["actions"], message: "actions must be unique and canonically ordered" });
|
|
5324
|
+
}
|
|
5325
|
+
if (value.policyRoot.role !== "policy-authority" || value.enforcementRoot.role !== "policy-enforcement") {
|
|
5326
|
+
ctx.addIssue({ code: external_exports.ZodIssueCode.custom, path: ["policyRoot", "role"], message: "root roles are fixed" });
|
|
5327
|
+
}
|
|
5328
|
+
if (value.policyCid.length === 0 || value.policyCid === value.policyRoot.cid || value.policyCid === value.enforcementRoot.cid) {
|
|
5329
|
+
ctx.addIssue({ code: external_exports.ZodIssueCode.custom, path: ["policyCid"], message: "policy CID must be distinct from both roots" });
|
|
5330
|
+
}
|
|
5331
|
+
if (value.contentSource.encryptionNetwork !== value.encryptionNetwork) {
|
|
5332
|
+
ctx.addIssue({ code: external_exports.ZodIssueCode.custom, path: ["encryptionNetwork"], message: "encryption network is not bound to the source" });
|
|
5333
|
+
}
|
|
5334
|
+
if (value.attestedEnforcerBinding.enforcerDid !== value.target.nodeAudience || value.attestedEnforcerBinding.nodeAudience !== value.target.nodeAudience || value.attestedEnforcerBinding.signature.signerDid !== value.target.nodeAudience || Date.parse(value.attestedEnforcerBinding.expiresAt) < Date.parse(value.expiry)) {
|
|
5335
|
+
ctx.addIssue({ code: external_exports.ZodIssueCode.custom, path: ["attestedEnforcerBinding"], message: "enforcer binding does not cover the target and share lifetime" });
|
|
5336
|
+
}
|
|
5337
|
+
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) {
|
|
5338
|
+
ctx.addIssue({ code: external_exports.ZodIssueCode.custom, path: ["contentSource"], message: "content source is not bound to policy" });
|
|
5339
|
+
}
|
|
5340
|
+
if (value.policy.signature.suite !== "Ed25519" || value.policy.signature.signerDid !== value.policy.ownerDid) {
|
|
5341
|
+
ctx.addIssue({ code: external_exports.ZodIssueCode.custom, path: ["policy", "signature"], message: "policy must be signed by its owner" });
|
|
5342
|
+
}
|
|
5343
|
+
const kvCapabilities = value.policy.capabilityCeiling.filter((capability) => capability.kind === "kv");
|
|
5344
|
+
const decryptCapabilities = value.policy.capabilityCeiling.filter((capability) => capability.kind === "encryption" && capability.resource === value.encryptionNetwork && capability.action === "tinycloud.encryption/decrypt");
|
|
5345
|
+
if (value.policy.capabilityCeiling.length !== 2 || kvCapabilities.length !== 1 || decryptCapabilities.length !== 1 || kvCapabilities[0]?.resource !== value.contentSource.kvResource || kvCapabilities[0]?.selector !== value.contentSource.selector) {
|
|
5346
|
+
ctx.addIssue({ code: external_exports.ZodIssueCode.custom, path: ["policy", "capabilityCeiling"], message: "policy ceiling must contain exact decrypt network" });
|
|
5347
|
+
}
|
|
5348
|
+
}
|
|
5349
|
+
var unsignedShareEnvelopeV3Schema = unsignedShareEnvelopeV3BaseSchema.superRefine(validateV3Invariants);
|
|
5350
|
+
var shareEnvelopeV3Schema = unsignedShareEnvelopeV3BaseSchema.extend({ signature: signatureSchema }).strict().superRefine(validateV3Invariants);
|
|
5226
5351
|
var BEARER_READ_ABILITY = "kv/get";
|
|
5227
5352
|
var READ_ABILITIES = /* @__PURE__ */ new Set([BEARER_READ_ABILITY]);
|
|
5228
5353
|
var ED25519_VERIFY_OPTS = { zip215: false };
|
|
@@ -5562,6 +5687,83 @@ function serialize(value) {
|
|
|
5562
5687
|
function canonicalize(value) {
|
|
5563
5688
|
return serialize(value);
|
|
5564
5689
|
}
|
|
5690
|
+
async function signCompactUcanAuthorization(input) {
|
|
5691
|
+
if (input.notBefore >= input.expiresAt || input.expiresAt - input.notBefore > 60) {
|
|
5692
|
+
throw new TypeError("compact invocation lifetime must be between one and 60 seconds");
|
|
5693
|
+
}
|
|
5694
|
+
const principal = input.issuerDid.split("#", 1)[0];
|
|
5695
|
+
const publicKey = ed25519PublicKeyFromDidKey(principal);
|
|
5696
|
+
const header = {
|
|
5697
|
+
alg: "EdDSA",
|
|
5698
|
+
jwk: { alg: "EdDSA", crv: "Ed25519", kty: "OKP", x: encodeBase64Url(publicKey) },
|
|
5699
|
+
typ: "JWT",
|
|
5700
|
+
ucv: "0.10.0"
|
|
5701
|
+
};
|
|
5702
|
+
const payload = {
|
|
5703
|
+
att: input.attenuation,
|
|
5704
|
+
aud: input.audienceDid,
|
|
5705
|
+
exp: input.expiresAt,
|
|
5706
|
+
fct: input.facts,
|
|
5707
|
+
iss: input.issuerDid.includes("#") ? input.issuerDid : `${principal}#${principal.slice("did:key:".length)}`,
|
|
5708
|
+
nbf: input.notBefore,
|
|
5709
|
+
nnc: input.nonce,
|
|
5710
|
+
prf: input.proofs
|
|
5711
|
+
};
|
|
5712
|
+
const protectedSegment = encodeBase64Url(new TextEncoder().encode(canonicalize(header)));
|
|
5713
|
+
const payloadSegment = encodeBase64Url(new TextEncoder().encode(canonicalize(payload)));
|
|
5714
|
+
const signingInput = new TextEncoder().encode(`${protectedSegment}.${payloadSegment}`);
|
|
5715
|
+
const signature = await input.sign(signingInput);
|
|
5716
|
+
if (signature.length !== 64) throw new TypeError("compact Authorization signature must be Ed25519");
|
|
5717
|
+
return verifyCompactUcanAuthorization(
|
|
5718
|
+
`${protectedSegment}.${payloadSegment}.${encodeBase64Url(signature)}`
|
|
5719
|
+
);
|
|
5720
|
+
}
|
|
5721
|
+
function verifyCompactUcanAuthorization(authorization, expectedCid) {
|
|
5722
|
+
if (/\s/.test(authorization)) throw new TypeError("compact Authorization contains whitespace");
|
|
5723
|
+
const segments = authorization.split(".");
|
|
5724
|
+
if (segments.length !== 3 || segments.some((segment) => segment.length === 0)) throw new TypeError("compact Authorization must contain three segments");
|
|
5725
|
+
const [headerSegment, payloadSegment, signatureSegment] = segments;
|
|
5726
|
+
const headerBytes = decodeBase64Url(headerSegment);
|
|
5727
|
+
const payloadBytes = decodeBase64Url(payloadSegment);
|
|
5728
|
+
const signature = decodeBase64Url(signatureSegment);
|
|
5729
|
+
const decoder = new TextDecoder("utf-8", { fatal: true });
|
|
5730
|
+
const header = JSON.parse(decoder.decode(headerBytes));
|
|
5731
|
+
const payload = JSON.parse(decoder.decode(payloadBytes));
|
|
5732
|
+
if (canonicalize(header) !== decoder.decode(headerBytes) || canonicalize(payload) !== decoder.decode(payloadBytes)) throw new TypeError("compact Authorization JSON is not canonical");
|
|
5733
|
+
assertExactKeys(header, ["alg", "jwk", "typ", "ucv"], "protected header");
|
|
5734
|
+
const jwk = object(header.jwk, "protected JWK");
|
|
5735
|
+
assertExactKeys(jwk, ["alg", "crv", "kty", "x"], "protected JWK");
|
|
5736
|
+
if (header.alg !== "EdDSA" || header.typ !== "JWT" || header.ucv !== "0.10.0" || jwk.alg !== "EdDSA" || jwk.crv !== "Ed25519" || jwk.kty !== "OKP" || typeof jwk.x !== "string") throw new TypeError("compact Authorization header is invalid");
|
|
5737
|
+
assertExactKeys(payload, ["att", "aud", "exp", "fct", "iss", "nbf", "nnc", "prf"], "UCAN payload");
|
|
5738
|
+
if (typeof payload.iss !== "string" || typeof payload.aud !== "string" || typeof payload.nnc !== "string" || !Number.isInteger(payload.nbf) || !Number.isInteger(payload.exp) || payload.nbf >= payload.exp || !Array.isArray(payload.prf) || payload.prf.some((proof) => typeof proof !== "string") || !Array.isArray(payload.fct) || payload.fct.length !== 1) throw new TypeError("compact Authorization payload is invalid");
|
|
5739
|
+
const principal = payload.iss.split("#", 1)[0];
|
|
5740
|
+
const publicKey = ed25519PublicKeyFromDidKey(principal);
|
|
5741
|
+
if (!equal(publicKey, decodeBase64Url(jwk.x))) throw new TypeError("compact Authorization JWK does not bind issuer");
|
|
5742
|
+
if (!ed255192.verify(signature, new TextEncoder().encode(`${headerSegment}.${payloadSegment}`), publicKey, { zip215: false })) throw new TypeError("compact Authorization signature is invalid");
|
|
5743
|
+
const cid2 = CID.createV1(85, create(30, blake3(new TextEncoder().encode(authorization)))).toString();
|
|
5744
|
+
if (expectedCid !== void 0 && cid2 !== expectedCid) throw new TypeError("compact Authorization CID mismatch");
|
|
5745
|
+
return { authorization, cid: cid2, header, payload };
|
|
5746
|
+
}
|
|
5747
|
+
function object(value, label) {
|
|
5748
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) throw new TypeError(`${label} must be an object`);
|
|
5749
|
+
return value;
|
|
5750
|
+
}
|
|
5751
|
+
function assertExactKeys(value, keys, label) {
|
|
5752
|
+
if (Object.keys(value).length !== keys.length || Object.keys(value).some((key) => !keys.includes(key))) throw new TypeError(`${label} has unknown or missing fields`);
|
|
5753
|
+
}
|
|
5754
|
+
function decodeBase64Url(value) {
|
|
5755
|
+
if (!/^[A-Za-z0-9_-]+$/.test(value) || value.length % 4 === 1) throw new TypeError("compact Authorization segment is not base64url");
|
|
5756
|
+
const bytes3 = typeof Buffer !== "undefined" ? new Uint8Array(Buffer.from(value, "base64url")) : Uint8Array.from(atob(value.replace(/-/g, "+").replace(/_/g, "/").padEnd(Math.ceil(value.length / 4) * 4, "=")), (character) => character.charCodeAt(0));
|
|
5757
|
+
const encoded = typeof Buffer !== "undefined" ? Buffer.from(bytes3).toString("base64url") : btoa(String.fromCharCode(...bytes3)).replace(/=/g, "").replace(/\+/g, "-").replace(/\//g, "_");
|
|
5758
|
+
if (encoded !== value) throw new TypeError("compact Authorization segment is not canonical");
|
|
5759
|
+
return bytes3;
|
|
5760
|
+
}
|
|
5761
|
+
function encodeBase64Url(value) {
|
|
5762
|
+
return typeof Buffer !== "undefined" ? Buffer.from(value).toString("base64url") : btoa(String.fromCharCode(...value)).replace(/=/g, "").replace(/\+/g, "-").replace(/\//g, "_");
|
|
5763
|
+
}
|
|
5764
|
+
function equal(left, right) {
|
|
5765
|
+
return left.length === right.length && left.every((byte, index) => byte === right[index]);
|
|
5766
|
+
}
|
|
5565
5767
|
var ED25519_VERIFY_OPTS2 = { zip215: false };
|
|
5566
5768
|
var ENVELOPE_SIGNATURE_DOMAIN = "xyz.tinycloud.share/envelope/v1\0";
|
|
5567
5769
|
var ENVELOPE_V2_SIGNATURE_DOMAIN = "xyz.tinycloud.share/envelope/v2\0";
|
|
@@ -5583,8 +5785,8 @@ function signingBytesV2(unsigned) {
|
|
|
5583
5785
|
}
|
|
5584
5786
|
function signEnvelope(envelopeWithoutSig, ed25519PrivKey) {
|
|
5585
5787
|
const unsigned = unsignedShareEnvelopeSchema.parse(envelopeWithoutSig);
|
|
5586
|
-
const publicKey =
|
|
5587
|
-
const signature =
|
|
5788
|
+
const publicKey = ed255193.getPublicKey(ed25519PrivKey);
|
|
5789
|
+
const signature = ed255193.sign(signingBytes(unsigned), ed25519PrivKey);
|
|
5588
5790
|
return {
|
|
5589
5791
|
...unsigned,
|
|
5590
5792
|
signature: {
|
|
@@ -5598,7 +5800,7 @@ function verifyEnvelopeSignatureOnly(envelope) {
|
|
|
5598
5800
|
const parsed = shareEnvelopeSchema.parse(envelope);
|
|
5599
5801
|
const { signature, ...unsigned } = parsed;
|
|
5600
5802
|
const publicKey = ed25519PublicKeyFromDidKey(signature.signerDid);
|
|
5601
|
-
return
|
|
5803
|
+
return ed255193.verify(
|
|
5602
5804
|
fromBase64Url(signature.value),
|
|
5603
5805
|
signingBytes(unsigned),
|
|
5604
5806
|
publicKey,
|
|
@@ -5620,7 +5822,7 @@ async function verifyEnvelope(envelope, options) {
|
|
|
5620
5822
|
function verifyEnvelopeV2SignatureOnly(envelope) {
|
|
5621
5823
|
const parsed = shareEnvelopeV2Schema.parse(envelope);
|
|
5622
5824
|
const { signature, ...unsigned } = parsed;
|
|
5623
|
-
return
|
|
5825
|
+
return ed255193.verify(fromBase64Url(signature.value), signingBytesV2(unsigned), ed25519PublicKeyFromDidKey(signature.signerDid), ED25519_VERIFY_OPTS2);
|
|
5624
5826
|
}
|
|
5625
5827
|
async function verifyEnvelopeV2(envelope, options) {
|
|
5626
5828
|
const parsed = shareEnvelopeV2Schema.parse(envelope);
|
|
@@ -6058,7 +6260,7 @@ async function receiveShare(link, options = {}) {
|
|
|
6058
6260
|
}
|
|
6059
6261
|
|
|
6060
6262
|
// src/publish.ts
|
|
6061
|
-
import { ed25519 as
|
|
6263
|
+
import { ed25519 as ed255194 } from "@noble/curves/ed25519";
|
|
6062
6264
|
var SHARE_CONTENT_LIMIT = 100 * 1024 * 1024;
|
|
6063
6265
|
var SHARE_SEALED_OVERHEAD = 1 + 12 + 16;
|
|
6064
6266
|
var SHARE_PUBLISH_RESULT_VERSION = 1;
|
|
@@ -6243,12 +6445,12 @@ async function publishShare(options) {
|
|
|
6243
6445
|
const spaceId = options.spaceId ?? "bearer";
|
|
6244
6446
|
const shareId = toBase64Url(globalThis.crypto.getRandomValues(new Uint8Array(16)));
|
|
6245
6447
|
const path = `shares/${shareId}/${options.filename}`;
|
|
6246
|
-
const sessionPrivateKey =
|
|
6247
|
-
const senderPrivateKey =
|
|
6448
|
+
const sessionPrivateKey = ed255194.utils.randomPrivateKey();
|
|
6449
|
+
const senderPrivateKey = ed255194.utils.randomPrivateKey();
|
|
6248
6450
|
let contentKey;
|
|
6249
6451
|
let envelopeKey;
|
|
6250
6452
|
try {
|
|
6251
|
-
const sessionPublicKey =
|
|
6453
|
+
const sessionPublicKey = ed255194.getPublicKey(sessionPrivateKey);
|
|
6252
6454
|
const sessionDid = didKeyFromEd25519PublicKey(sessionPublicKey);
|
|
6253
6455
|
const delegation = mintBearerDelegation({
|
|
6254
6456
|
issuerPrivateKey: senderPrivateKey,
|
|
@@ -6340,8 +6542,8 @@ async function parseAddressedEnvelope(value) {
|
|
|
6340
6542
|
}
|
|
6341
6543
|
|
|
6342
6544
|
// src/owner-policy.ts
|
|
6343
|
-
import { ed25519 as
|
|
6344
|
-
import { sha256 as
|
|
6545
|
+
import { ed25519 as ed255195 } from "@noble/curves/ed25519";
|
|
6546
|
+
import { sha256 as sha2563 } from "@noble/hashes/sha256";
|
|
6345
6547
|
|
|
6346
6548
|
// ../../node_modules/multiformats/dist/src/bytes.js
|
|
6347
6549
|
var empty2 = new Uint8Array(0);
|
|
@@ -7248,10 +7450,10 @@ function fromB64(value) {
|
|
|
7248
7450
|
throw new Error("base64url decoding is unavailable");
|
|
7249
7451
|
}
|
|
7250
7452
|
function cid(bytesValue) {
|
|
7251
|
-
return CID2.createV1(code2, create2(SHA256_CODE2,
|
|
7453
|
+
return CID2.createV1(code2, create2(SHA256_CODE2, sha2563(bytesValue))).toString(base322.encoder);
|
|
7252
7454
|
}
|
|
7253
7455
|
function digest(bytesValue) {
|
|
7254
|
-
return b64(
|
|
7456
|
+
return b64(sha2563(bytesValue));
|
|
7255
7457
|
}
|
|
7256
7458
|
function didKeyFromEd25519PublicKey2(publicKey) {
|
|
7257
7459
|
if (publicKey.length !== 32) throw new Error("share key public key must be 32 bytes");
|
|
@@ -7429,7 +7631,7 @@ function validateOwnerSharePolicyRegistration(value, expected) {
|
|
|
7429
7631
|
const publicKey = encodedKid.length === 34 ? encodedKid.slice(2) : encodedKid;
|
|
7430
7632
|
const signatureBytes = fromB64(proof.signature);
|
|
7431
7633
|
const signedBytes = new TextEncoder().encode(`${OWNER_SHARE_REGISTRATION_DOMAIN}${canonicalize(registrationCore)}`);
|
|
7432
|
-
if (signatureBytes.length !== 64 || !
|
|
7634
|
+
if (signatureBytes.length !== 64 || !ed255195.verify(signatureBytes, signedBytes, publicKey)) throw new Error("owner-share registration proof signature is invalid");
|
|
7433
7635
|
return { registration, proof };
|
|
7434
7636
|
}
|
|
7435
7637
|
function validateOwnerSharePolicyRegistrationBytes(bytesValue, expected) {
|
|
@@ -7589,7 +7791,7 @@ function assertSafeInput(input) {
|
|
|
7589
7791
|
if (input.artifact === "html" && (input.resource.kind !== "prefix" || !input.actions.includes("read") || !input.actions.includes("list"))) throw new TypeError("html artifacts require a readable prefix");
|
|
7590
7792
|
if (input.contentSource.kind !== "kv") throw new TypeError("addressed policy publication requires a KV content source");
|
|
7591
7793
|
}
|
|
7592
|
-
async function
|
|
7794
|
+
async function sha2564(value) {
|
|
7593
7795
|
return toBase64Url(new Uint8Array(await crypto.subtle.digest("SHA-256", value)));
|
|
7594
7796
|
}
|
|
7595
7797
|
function publicationResult(input) {
|
|
@@ -7638,7 +7840,7 @@ async function publishAddressedShare(options) {
|
|
|
7638
7840
|
const contentSource = options.contentSource;
|
|
7639
7841
|
const matcher = targetMatcher(target);
|
|
7640
7842
|
const expiresAt = options.expiresAt.toISOString();
|
|
7641
|
-
const contentSourceDigest = options.contentSourceDigest ?? await
|
|
7843
|
+
const contentSourceDigest = options.contentSourceDigest ?? await sha2564(new TextEncoder().encode(canonicalize(contentSource)));
|
|
7642
7844
|
const shareKey = await createDelegatedShareKey({ extractable: false });
|
|
7643
7845
|
let envelopeKey;
|
|
7644
7846
|
try {
|
|
@@ -7702,7 +7904,7 @@ async function publishAddressedShare(options) {
|
|
|
7702
7904
|
enforcementDelegation,
|
|
7703
7905
|
contentSourceDigest
|
|
7704
7906
|
});
|
|
7705
|
-
const authorityMaterialDigest = await
|
|
7907
|
+
const authorityMaterialDigest = await sha2564(fromBase64Url(enforcementDelegation.dagCbor));
|
|
7706
7908
|
const authorityTarget = {
|
|
7707
7909
|
origin: options.nodeOrigin,
|
|
7708
7910
|
nodeAudience: options.nodeAudience,
|
|
@@ -7802,7 +8004,7 @@ async function publishAddressedShare(options) {
|
|
|
7802
8004
|
}
|
|
7803
8005
|
|
|
7804
8006
|
// src/policy-authority.ts
|
|
7805
|
-
import { ed25519 as
|
|
8007
|
+
import { ed25519 as ed255196 } from "@noble/curves/ed25519";
|
|
7806
8008
|
var ENVELOPE_DOMAIN2 = "xyz.tinycloud.share/envelope/v2\0";
|
|
7807
8009
|
function record(value, keys, label) {
|
|
7808
8010
|
if (typeof value !== "object" || value === null || Array.isArray(value)) throw new Error(`${label} is invalid`);
|
|
@@ -7858,7 +8060,7 @@ async function verifyEnforcement(delegation) {
|
|
|
7858
8060
|
});
|
|
7859
8061
|
if (expectedDagCbor.length !== dagCbor.length || expectedDagCbor.some((byte, index) => byte !== dagCbor[index])) return false;
|
|
7860
8062
|
const signature = fromBase64Url(delegation.signature);
|
|
7861
|
-
return signature.length === 64 &&
|
|
8063
|
+
return signature.length === 64 && ed255196.verify(
|
|
7862
8064
|
signature,
|
|
7863
8065
|
dagCbor,
|
|
7864
8066
|
ed25519PublicKeyFromDidKey(delegation.issuerDid),
|
|
@@ -7882,7 +8084,7 @@ async function verifyOuterEnvelope(value, envelope, policyCid, expectedEnforcerD
|
|
|
7882
8084
|
if (shareCid !== await computeCid(new TextEncoder().encode(canonicalize({ version: 2, shareId: envelope.shareId, policyCid, envelopeCid })))) return void 0;
|
|
7883
8085
|
const unsigned = { ...identity, envelopeCid, shareCid };
|
|
7884
8086
|
const signatureBytes = fromBase64Url(requiredString(signature.value, "outer signature"));
|
|
7885
|
-
if (signatureBytes.length !== 64 || !
|
|
8087
|
+
if (signatureBytes.length !== 64 || !ed255196.verify(
|
|
7886
8088
|
signatureBytes,
|
|
7887
8089
|
new TextEncoder().encode(`${ENVELOPE_DOMAIN2}${canonicalize(unsigned)}`),
|
|
7888
8090
|
ed25519PublicKeyFromDidKey(envelope.signature.signerDid),
|
|
@@ -7949,13 +8151,13 @@ function createRegisteredPolicyAuthority(options) {
|
|
|
7949
8151
|
}
|
|
7950
8152
|
|
|
7951
8153
|
// src/cache.ts
|
|
7952
|
-
import { sha256 as
|
|
8154
|
+
import { sha256 as sha2565 } from "@noble/hashes/sha256";
|
|
7953
8155
|
function cacheKey(key) {
|
|
7954
8156
|
if (!key.cid || !key.expiresAt) throw new Error("share cache keys require CID and expiry");
|
|
7955
8157
|
return `${key.cid}\0${key.expiresAt}`;
|
|
7956
8158
|
}
|
|
7957
8159
|
function digest2(bytes3) {
|
|
7958
|
-
return
|
|
8160
|
+
return sha2565(bytes3);
|
|
7959
8161
|
}
|
|
7960
8162
|
function equalBytes(left, right) {
|
|
7961
8163
|
if (left.byteLength !== right.byteLength) return false;
|
|
@@ -8010,7 +8212,7 @@ var MemoryShareCache = class {
|
|
|
8010
8212
|
};
|
|
8011
8213
|
|
|
8012
8214
|
// src/delivery.ts
|
|
8013
|
-
import { ed25519 as
|
|
8215
|
+
import { ed25519 as ed255197 } from "@noble/curves/ed25519";
|
|
8014
8216
|
var SHARE_DELIVERY_AUTHORIZATION_DOMAIN = "xyz.tinycloud.share/delivery-authorization/v2\0";
|
|
8015
8217
|
function isCanonicalHttpsOrigin2(value) {
|
|
8016
8218
|
let url;
|
|
@@ -8102,7 +8304,7 @@ function validateShareDeliveryAuthorizationBytes(bytesValue, expected) {
|
|
|
8102
8304
|
if (publicKey.length !== 32) throw new Error("share delivery authorization proof key is invalid");
|
|
8103
8305
|
const signatureBytes = fromB642(proof.signature);
|
|
8104
8306
|
const signedBytes = new TextEncoder().encode(`${SHARE_DELIVERY_AUTHORIZATION_DOMAIN}${canonicalize(authorization)}`);
|
|
8105
|
-
if (signatureBytes.length !== 64 || !
|
|
8307
|
+
if (signatureBytes.length !== 64 || !ed255197.verify(signatureBytes, signedBytes, publicKey)) throw new Error("share delivery authorization proof signature is invalid");
|
|
8106
8308
|
return { authorization, proof };
|
|
8107
8309
|
}
|
|
8108
8310
|
function shareDeliveryTrustedKid(publicKey) {
|
|
@@ -8401,7 +8603,8 @@ async function migrateShare(input) {
|
|
|
8401
8603
|
}
|
|
8402
8604
|
|
|
8403
8605
|
// src/recipient.ts
|
|
8404
|
-
import { ed25519 as
|
|
8606
|
+
import { ed25519 as ed255198, x25519 } from "@noble/curves/ed25519";
|
|
8607
|
+
import { sha256 as sha2566 } from "@noble/hashes/sha256";
|
|
8405
8608
|
|
|
8406
8609
|
// src/protocol.ts
|
|
8407
8610
|
var SHARE_V2_PROTOCOL = Object.freeze({
|
|
@@ -8446,7 +8649,7 @@ var DOMAIN = SHARE_V2_PROTOCOL.challengeDomain;
|
|
|
8446
8649
|
var PRESENTATION_DOMAIN = SHARE_V2_PROTOCOL.sessionDomain;
|
|
8447
8650
|
var SESSION_DOMAIN = SHARE_V2_PROTOCOL.sessionDomain;
|
|
8448
8651
|
var INVOCATION_DOMAIN = SHARE_V2_PROTOCOL.invocationDomain;
|
|
8449
|
-
function
|
|
8652
|
+
function object2(value, label) {
|
|
8450
8653
|
if (typeof value !== "object" || value === null || Array.isArray(value)) throw new Error(`${label} is invalid`);
|
|
8451
8654
|
return value;
|
|
8452
8655
|
}
|
|
@@ -8472,12 +8675,12 @@ function trustedPublicKey(value) {
|
|
|
8472
8675
|
return value.invitationPublicKey;
|
|
8473
8676
|
}
|
|
8474
8677
|
async function verifyWrapped(value, key, domain, trust) {
|
|
8475
|
-
const wrapper =
|
|
8678
|
+
const wrapper = object2(value, `${key} response`);
|
|
8476
8679
|
if (Object.keys(wrapper).length !== 2 || !Object.hasOwn(wrapper, key) || !Object.hasOwn(wrapper, "proof")) throw new Error(`${key} response is invalid`);
|
|
8477
|
-
const artifact =
|
|
8478
|
-
const proof =
|
|
8680
|
+
const artifact = object2(wrapper[key], `${key} artifact`);
|
|
8681
|
+
const proof = object2(wrapper.proof, `${key} proof`);
|
|
8479
8682
|
if (proof.alg !== "EdDSA" || proof.kid !== trust.invitationKid) throw new Error(`${key} proof is invalid`);
|
|
8480
|
-
if (!
|
|
8683
|
+
if (!ed255198.verify(bytes2(proof.signature, `${key} signature`), new TextEncoder().encode(`${domain}${canonicalize(artifact)}`), trustedPublicKey(trust))) throw new Error(`${key} proof is invalid`);
|
|
8481
8684
|
return artifact;
|
|
8482
8685
|
}
|
|
8483
8686
|
function nativeAction(action) {
|
|
@@ -8489,6 +8692,42 @@ function uiAction(action) {
|
|
|
8489
8692
|
function selectedAction(envelope) {
|
|
8490
8693
|
return envelope.actions.includes("list") ? "tinycloud.kv/list" : envelope.actions.includes("edit") ? "tinycloud.kv/put" : "tinycloud.kv/get";
|
|
8491
8694
|
}
|
|
8695
|
+
function hex(value) {
|
|
8696
|
+
return [...value].map((byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
8697
|
+
}
|
|
8698
|
+
function canonicalHashHex(value) {
|
|
8699
|
+
return hex(sha2566(new TextEncoder().encode(canonicalize(value))));
|
|
8700
|
+
}
|
|
8701
|
+
async function aesGcmDecrypt(key, blob) {
|
|
8702
|
+
if (key.length !== 32 || blob.length < 28) throw new Error("encrypted content is malformed");
|
|
8703
|
+
const cryptoKey = await crypto.subtle.importKey("raw", key, "AES-GCM", false, ["decrypt"]);
|
|
8704
|
+
return new Uint8Array(await crypto.subtle.decrypt({ name: "AES-GCM", iv: blob.slice(0, 12) }, cryptoKey, blob.slice(12)));
|
|
8705
|
+
}
|
|
8706
|
+
async function aesGcmEncrypt(key, plaintext) {
|
|
8707
|
+
if (key.length !== 32) throw new Error("encrypted content key is malformed");
|
|
8708
|
+
const nonce = crypto.getRandomValues(new Uint8Array(12));
|
|
8709
|
+
const cryptoKey = await crypto.subtle.importKey("raw", key, "AES-GCM", false, ["encrypt"]);
|
|
8710
|
+
const ciphertext = new Uint8Array(await crypto.subtle.encrypt({ name: "AES-GCM", iv: nonce }, cryptoKey, plaintext));
|
|
8711
|
+
const output = new Uint8Array(nonce.length + ciphertext.length);
|
|
8712
|
+
output.set(nonce);
|
|
8713
|
+
output.set(ciphertext, nonce.length);
|
|
8714
|
+
return output;
|
|
8715
|
+
}
|
|
8716
|
+
function parseV3InlineEncryptedEnvelope(bytes3, expected) {
|
|
8717
|
+
let value;
|
|
8718
|
+
try {
|
|
8719
|
+
value = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(bytes3));
|
|
8720
|
+
} catch {
|
|
8721
|
+
throw new Error("encrypted content envelope is malformed");
|
|
8722
|
+
}
|
|
8723
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) throw new Error("encrypted content envelope is malformed");
|
|
8724
|
+
const record2 = value;
|
|
8725
|
+
const allowed = ["v", "networkId", "alg", "keyVersion", "encryptedSymmetricKey", "encryptedSymmetricKeyHash", "ciphertext", "metadata"];
|
|
8726
|
+
if (Object.keys(record2).some((key) => !allowed.includes(key)) || record2.v !== 1 || record2.networkId !== expected.encryptionNetwork || record2.alg !== "x25519-aes256gcm/v1" || record2.keyVersion !== expected.contentSource.keyVersion || typeof record2.encryptedSymmetricKey !== "string" || typeof record2.encryptedSymmetricKeyHash !== "string" || record2.encryptedSymmetricKeyHash !== expected.contentSource.encryptedSymmetricKeyDigestHex || record2.encryptedSymmetricKeyHash !== canonicalHashHex(record2.encryptedSymmetricKey) || typeof record2.ciphertext !== "string" || record2.metadata !== void 0 && (record2.metadata === null || typeof record2.metadata !== "object" || Array.isArray(record2.metadata) || Object.values(record2.metadata).some((entry) => typeof entry !== "string"))) {
|
|
8727
|
+
throw new Error("encrypted content envelope binding is invalid");
|
|
8728
|
+
}
|
|
8729
|
+
return record2;
|
|
8730
|
+
}
|
|
8492
8731
|
async function verifyDetachedResponse(response, trust) {
|
|
8493
8732
|
let value;
|
|
8494
8733
|
try {
|
|
@@ -8496,12 +8735,12 @@ async function verifyDetachedResponse(response, trust) {
|
|
|
8496
8735
|
} catch {
|
|
8497
8736
|
throw new Error("share read response is invalid");
|
|
8498
8737
|
}
|
|
8499
|
-
const record2 =
|
|
8500
|
-
const proof =
|
|
8738
|
+
const record2 = object2(value, "share read response");
|
|
8739
|
+
const proof = object2(record2.proof, "share read detached proof");
|
|
8501
8740
|
if (proof.alg !== "EdDSA" || proof.kid !== trust.invitationKid) throw new Error("share read detached proof is invalid");
|
|
8502
8741
|
const unsigned = { ...record2 };
|
|
8503
8742
|
delete unsigned.proof;
|
|
8504
|
-
if (!
|
|
8743
|
+
if (!ed255198.verify(bytes2(proof.signature, "share read signature"), new TextEncoder().encode(`${SHARE_V2_PROTOCOL.readResponseDomain}${canonicalize(unsigned)}`), trustedPublicKey(trust))) throw new Error("share read detached proof is invalid");
|
|
8505
8744
|
}
|
|
8506
8745
|
async function post(fetchFn, origin, path, body) {
|
|
8507
8746
|
const response = await fetchFn(new URL(path, origin), { method: "POST", redirect: "error", headers: { accept: "application/json", "content-type": "application/json" }, body: JSON.stringify(body) });
|
|
@@ -8517,15 +8756,19 @@ var ShareRecipientClient = class {
|
|
|
8517
8756
|
fetchFn;
|
|
8518
8757
|
session;
|
|
8519
8758
|
signer;
|
|
8759
|
+
nativeSigner;
|
|
8520
8760
|
holderProof;
|
|
8761
|
+
v3Authorization;
|
|
8762
|
+
v3ContentKey;
|
|
8763
|
+
v3ContentEnvelope;
|
|
8521
8764
|
async beginChallenge(envelope) {
|
|
8522
8765
|
const authority = envelope.ownerAuthority;
|
|
8523
8766
|
if (authority === void 0) throw new Error("addressed owner authority is required");
|
|
8524
|
-
const outer =
|
|
8525
|
-
const enforcement =
|
|
8526
|
-
const target =
|
|
8527
|
-
const outerResource =
|
|
8528
|
-
const source =
|
|
8767
|
+
const outer = object2(authority.outerEnvelope, "owner authority outer envelope");
|
|
8768
|
+
const enforcement = object2(authority.enforcementDelegation, "owner authority enforcement delegation");
|
|
8769
|
+
const target = object2(outer.target, "owner authority target");
|
|
8770
|
+
const outerResource = object2(outer.resource, "owner authority resource");
|
|
8771
|
+
const source = object2(outer.contentSource, "owner authority content source");
|
|
8529
8772
|
if (!Array.isArray(outer.actions) || outer.actions.length === 0 || outer.actions.some((value) => typeof value !== "string")) throw new Error("owner authority outer envelope actions are invalid");
|
|
8530
8773
|
const actions = [...outer.actions];
|
|
8531
8774
|
const action = selectedAction(envelope);
|
|
@@ -8538,11 +8781,11 @@ var ShareRecipientClient = class {
|
|
|
8538
8781
|
async establish(envelope) {
|
|
8539
8782
|
const authority = envelope.ownerAuthority;
|
|
8540
8783
|
if (authority === void 0) throw new Error("addressed owner authority is required");
|
|
8541
|
-
const outer =
|
|
8542
|
-
const enforcement =
|
|
8543
|
-
const target =
|
|
8544
|
-
const outerResource =
|
|
8545
|
-
const source =
|
|
8784
|
+
const outer = object2(authority.outerEnvelope, "owner authority outer envelope");
|
|
8785
|
+
const enforcement = object2(authority.enforcementDelegation, "owner authority enforcement delegation");
|
|
8786
|
+
const target = object2(outer.target, "owner authority target");
|
|
8787
|
+
const outerResource = object2(outer.resource, "owner authority resource");
|
|
8788
|
+
const source = object2(outer.contentSource, "owner authority content source");
|
|
8546
8789
|
if (!Array.isArray(outer.actions) || outer.actions.length === 0 || outer.actions.some((value) => typeof value !== "string")) throw new Error("owner authority outer envelope actions are invalid");
|
|
8547
8790
|
const actions = [...outer.actions];
|
|
8548
8791
|
const action = selectedAction(envelope);
|
|
@@ -8554,6 +8797,7 @@ var ShareRecipientClient = class {
|
|
|
8554
8797
|
const material = await this.options.buildPresentation({ challenge, envelope, policy: {} });
|
|
8555
8798
|
this.holderProof = material.proof;
|
|
8556
8799
|
this.signer = material.sign;
|
|
8800
|
+
this.nativeSigner = material.sign;
|
|
8557
8801
|
if (this.signer === void 0) throw new Error("share holder signer is required");
|
|
8558
8802
|
const presentation = {
|
|
8559
8803
|
type: "TinyCloudSharePolicyPresentation",
|
|
@@ -8589,7 +8833,7 @@ var ShareRecipientClient = class {
|
|
|
8589
8833
|
if (this.session === void 0) await this.establish(envelope);
|
|
8590
8834
|
const response = await this.nativeInvoke({ action: "get", resource: envelope.resource });
|
|
8591
8835
|
if (!response.ok) throw new Error("share recipient read was rejected");
|
|
8592
|
-
const value =
|
|
8836
|
+
const value = object2(await response.json(), "share read response");
|
|
8593
8837
|
const expectedAction = nativeAction("read");
|
|
8594
8838
|
if (value.type !== "TinyCloudShareInvokeResponse" || value.version !== 2 || value.action !== expectedAction || value.resource !== envelope.resource.path || typeof value.content !== "string" || typeof value.bodyDigest !== "string" || !/^[A-Za-z0-9_-]{43}$/.test(value.bodyDigest) || !Object.hasOwn(value, "proof")) throw new Error("share read response is invalid");
|
|
8595
8839
|
const content = fromBase64Url(value.content);
|
|
@@ -8598,18 +8842,110 @@ var ShareRecipientClient = class {
|
|
|
8598
8842
|
}
|
|
8599
8843
|
async establishPolicySession() {
|
|
8600
8844
|
if (this.session !== void 0) return this.session;
|
|
8601
|
-
|
|
8845
|
+
const envelope = this.options.envelope;
|
|
8846
|
+
if (envelope.version === 3) return this.establishV3(envelope);
|
|
8847
|
+
return this.establish(envelope);
|
|
8848
|
+
}
|
|
8849
|
+
async establishV3(envelope) {
|
|
8850
|
+
if (this.options.buildPresentation === void 0) throw new Error("share presentation builder is required");
|
|
8851
|
+
const challengeResponse = await this.fetchFn(new URL("/share/v3/policy/challenges", this.options.nodeOrigin), {
|
|
8852
|
+
method: "POST",
|
|
8853
|
+
redirect: "error",
|
|
8854
|
+
headers: { accept: "application/json", "content-type": "application/json" },
|
|
8855
|
+
body: JSON.stringify({ policyCid: envelope.policyCid, recipientDid: this.options.holderDid, requestedCapabilities: envelope.policy.capabilityCeiling })
|
|
8856
|
+
});
|
|
8857
|
+
if (!challengeResponse.ok) throw new Error(`v3 policy challenge rejected (${challengeResponse.status})`);
|
|
8858
|
+
const challenge = object2(await challengeResponse.json(), "v3 policy challenge");
|
|
8859
|
+
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");
|
|
8860
|
+
const material = await this.options.buildPresentation({
|
|
8861
|
+
challenge,
|
|
8862
|
+
envelope,
|
|
8863
|
+
policy: envelope.policy
|
|
8864
|
+
});
|
|
8865
|
+
if (material.sign === void 0 || material.claim === void 0 || material.presentation === void 0) throw new Error("v3 ceremony requires a recipient signer, claim, and presentation");
|
|
8866
|
+
const delegationResponse = await this.fetchFn(new URL("/share/v3/policy/delegations", this.options.nodeOrigin), {
|
|
8867
|
+
method: "POST",
|
|
8868
|
+
redirect: "error",
|
|
8869
|
+
headers: { accept: "application/json", "content-type": "application/json" },
|
|
8870
|
+
body: JSON.stringify({ policyCid: envelope.policyCid, challengeId: challenge.challengeId, nonce: challenge.nonce, claim: material.claim, presentation: material.presentation })
|
|
8871
|
+
});
|
|
8872
|
+
if (!delegationResponse.ok) throw new Error(`v3 policy delegation rejected (${delegationResponse.status})`);
|
|
8873
|
+
const delegation = object2(await delegationResponse.json(), "v3 policy delegation");
|
|
8874
|
+
if (delegation.admitted !== true || typeof delegation.sessionCid !== "string" || typeof delegation.authorization !== "string") throw new Error("v3 policy delegation response is not admitted");
|
|
8875
|
+
const compact = verifyCompactUcanAuthorization(delegation.authorization, delegation.sessionCid);
|
|
8876
|
+
const fact = compact.payload.fct[0];
|
|
8877
|
+
if (compact.payload.aud !== this.options.holderDid || fact.profile !== "policy-session-ucan/v1" || fact.policyCid !== envelope.policyCid || fact.recipientDid !== this.options.holderDid || fact.policyDelegationCid !== envelope.policyRoot.cid || fact.enforcementDelegationCid !== envelope.enforcementRoot.cid || compact.payload.prf.length !== 2 || compact.payload.prf[0] !== envelope.policyRoot.cid || compact.payload.prf[1] !== envelope.enforcementRoot.cid || compact.payload.exp - compact.payload.nbf > 60) throw new Error("v3 policy delegation signed binding mismatch");
|
|
8878
|
+
const imported = await this.fetchFn(new URL("/delegate", this.options.nodeOrigin), { method: "POST", redirect: "error", headers: { Authorization: delegation.authorization } });
|
|
8879
|
+
if (!imported.ok) throw new Error(`ordinary delegation import rejected (${imported.status})`);
|
|
8880
|
+
this.signer = material.sign;
|
|
8881
|
+
this.v3Authorization = delegation.authorization;
|
|
8882
|
+
this.session = { sessionId: delegation.sessionCid, expiresAt: new Date(compact.payload.exp * 1e3).toISOString(), actions: envelope.actions, resource: envelope.resource };
|
|
8883
|
+
return this.session;
|
|
8884
|
+
}
|
|
8885
|
+
/** Decrypt a v3 KV value through a fresh recipient-signed decrypt invocation. */
|
|
8886
|
+
async decryptV3Content(bytes3) {
|
|
8887
|
+
const envelope = this.options.envelope;
|
|
8888
|
+
const signer = this.nativeSigner ?? this.signer;
|
|
8889
|
+
if (envelope.version !== 3 || this.session === void 0 || this.v3Authorization === void 0 || signer === void 0) throw new Error("v3 policy session signer is required");
|
|
8890
|
+
const encrypted = parseV3InlineEncryptedEnvelope(bytes3, envelope);
|
|
8891
|
+
const receiverPrivateKey = crypto.getRandomValues(new Uint8Array(32));
|
|
8892
|
+
const receiverPublicKey = toBase64Url(x25519.getPublicKey(receiverPrivateKey));
|
|
8893
|
+
const receiverPublicKeyHash = canonicalHashHex(receiverPublicKey);
|
|
8894
|
+
const body = { type: "tinycloud.encryption.decrypt/v1", targetNode: envelope.target.nodeAudience, networkId: encrypted.networkId, alg: encrypted.alg, keyVersion: encrypted.keyVersion, encryptedSymmetricKey: encrypted.encryptedSymmetricKey, encryptedSymmetricKeyHash: encrypted.encryptedSymmetricKeyHash, receiverPublicKey, receiverPublicKeyHash };
|
|
8895
|
+
const bodyHash = hex(sha2566(new TextEncoder().encode(canonicalize(body))));
|
|
8896
|
+
const session = verifyCompactUcanAuthorization(this.v3Authorization, this.session.sessionId);
|
|
8897
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
8898
|
+
const invocation = await signCompactUcanAuthorization({
|
|
8899
|
+
issuerDid: this.options.holderDid,
|
|
8900
|
+
audienceDid: envelope.target.nodeAudience,
|
|
8901
|
+
attenuation: { [encrypted.networkId]: { "tinycloud.encryption/decrypt": [{}] } },
|
|
8902
|
+
facts: [{ type: body.type, targetNode: body.targetNode, networkId: body.networkId, bodyHash, encryptedSymmetricKeyHash: body.encryptedSymmetricKeyHash, receiverPublicKeyHash, alg: body.alg, keyVersion: body.keyVersion }],
|
|
8903
|
+
proofs: [session.cid],
|
|
8904
|
+
notBefore: now,
|
|
8905
|
+
expiresAt: Math.min(now + 60, session.payload.exp),
|
|
8906
|
+
nonce: toBase64Url(crypto.getRandomValues(new Uint8Array(16))),
|
|
8907
|
+
sign: signer
|
|
8908
|
+
});
|
|
8909
|
+
try {
|
|
8910
|
+
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) });
|
|
8911
|
+
if (!response.ok) throw new Error(`v3 decrypt invocation rejected (${response.status})`);
|
|
8912
|
+
const value = object2(await response.json(), "v3 decrypt response");
|
|
8913
|
+
const allowed = ["type", "targetNode", "networkId", "invocationCid", "encryptedSymmetricKeyHash", "receiverPublicKeyHash", "wrappedKey", "alg", "keyVersion", "requestHash", "nodeId", "nodeSignature"];
|
|
8914
|
+
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 !== hex(sha2566(new TextEncoder().encode(`${invocation.cid}${bodyHash}`))) || typeof value.wrappedKey !== "string" || typeof value.nodeSignature !== "string") throw new Error("v3 decrypt response binding is invalid");
|
|
8915
|
+
const unsigned = { ...value };
|
|
8916
|
+
delete unsigned.nodeSignature;
|
|
8917
|
+
const signature = fromBase64Url(value.nodeSignature);
|
|
8918
|
+
if (signature.length !== 64 || !ed255198.verify(signature, new TextEncoder().encode(canonicalize(unsigned)), ed25519PublicKeyFromDidKey(body.targetNode), { zip215: false })) throw new Error("v3 decrypt response signature is invalid");
|
|
8919
|
+
const wrapped = fromBase64Url(value.wrappedKey);
|
|
8920
|
+
if (wrapped.length < 60) throw new Error("v3 wrapped content key is malformed");
|
|
8921
|
+
const shared = x25519.getSharedSecret(receiverPrivateKey, wrapped.slice(0, 32));
|
|
8922
|
+
const symmetricKey = await aesGcmDecrypt(shared, wrapped.slice(32));
|
|
8923
|
+
shared.fill(0);
|
|
8924
|
+
if (symmetricKey.length !== 32) throw new Error("v3 content key is malformed");
|
|
8925
|
+
const plaintext = await aesGcmDecrypt(symmetricKey, fromBase64Url(encrypted.ciphertext));
|
|
8926
|
+
this.v3ContentKey?.fill(0);
|
|
8927
|
+
this.v3ContentKey = symmetricKey;
|
|
8928
|
+
this.v3ContentEnvelope = encrypted;
|
|
8929
|
+
return { bytes: plaintext, mediaType: encrypted.metadata?.contentType ?? envelope.metadata.mediaType ?? "application/octet-stream" };
|
|
8930
|
+
} finally {
|
|
8931
|
+
receiverPrivateKey.fill(0);
|
|
8932
|
+
}
|
|
8933
|
+
}
|
|
8934
|
+
/** Re-encrypt edited v3 content with the admitted content key. */
|
|
8935
|
+
async encryptV3Content(bytes3, mediaType) {
|
|
8936
|
+
if (this.options.envelope.version !== 3 || this.v3ContentKey === void 0 || this.v3ContentEnvelope === void 0) throw new Error("v3 content must be decrypted before it can be saved");
|
|
8937
|
+
return new TextEncoder().encode(canonicalize({ ...this.v3ContentEnvelope, ciphertext: toBase64Url(await aesGcmEncrypt(this.v3ContentKey, bytes3)), metadata: { ...this.v3ContentEnvelope.metadata ?? {}, contentType: mediaType } }));
|
|
8602
8938
|
}
|
|
8603
8939
|
async resumeWithProof(envelope, resumeToken, proof) {
|
|
8604
|
-
const material =
|
|
8605
|
-
const presentation =
|
|
8606
|
-
const presentationProof =
|
|
8940
|
+
const material = object2(proof, "share authorization proof");
|
|
8941
|
+
const presentation = object2(material.presentation, "share presentation");
|
|
8942
|
+
const presentationProof = object2(material.presentationProof, "share presentation proof");
|
|
8607
8943
|
const nonce = material.nonce;
|
|
8608
8944
|
const credential = material.credential;
|
|
8609
8945
|
const holderDid = material.holderDid;
|
|
8610
8946
|
const holderBinding = material.holderBinding;
|
|
8611
8947
|
if (typeof nonce !== "string" || typeof credential !== "string" || typeof holderDid !== "string" || holderDid !== this.options.holderDid || typeof holderBinding !== "object" || holderBinding === null || presentationProof.alg !== "EdDSA" || typeof presentationProof.signature !== "string") throw new Error("share authorization proof is incomplete");
|
|
8612
|
-
const value =
|
|
8948
|
+
const value = object2(await post(this.fetchFn, this.options.nodeOrigin, "/share/v2/policy/session", { challengeId: resumeToken, nonce, presentation, credential, proof: presentationProof, holderBinding, readSignerDid: holderDid }), "share policy session");
|
|
8613
8949
|
const session = await verifyWrapped(value, "session", SESSION_DOMAIN, this.options.trustedNode);
|
|
8614
8950
|
const authority = envelope.ownerAuthority;
|
|
8615
8951
|
if (authority === void 0 || session.type !== "TinyCloudSharePolicySession" || session.version !== 2 || typeof session.sessionId !== "string" || session.shareCid !== authority.shareCid || session.shareId !== envelope.shareId || session.registrationCid !== authority.registrationCid || session.envelopeCid !== authority.envelopeCid || session.delegationCid !== envelope.delegationCid || typeof session.resource !== "string" || typeof session.expiresAt !== "string") throw new Error("share authority returned an unbound session");
|
|
@@ -8623,27 +8959,61 @@ var ShareRecipientClient = class {
|
|
|
8623
8959
|
}
|
|
8624
8960
|
async nativeInvoke(request) {
|
|
8625
8961
|
if (this.session === void 0) throw new Error("share policy session is required");
|
|
8626
|
-
|
|
8627
|
-
|
|
8962
|
+
const envelope = this.options.envelope;
|
|
8963
|
+
if (envelope.version === 3) return this.nativeInvokeV3(request, envelope);
|
|
8964
|
+
if (envelope.ownerAuthority === void 0 || this.signer === void 0 || this.holderProof === void 0) throw new Error("share holder signer is required");
|
|
8965
|
+
const authority = envelope.ownerAuthority;
|
|
8628
8966
|
const action = request.action === "list" ? "tinycloud.kv/list" : request.action === "put" ? "tinycloud.kv/put" : request.action === "metadata" ? "tinycloud.kv/metadata" : "tinycloud.kv/get";
|
|
8629
8967
|
const resource = typeof request.resource?.path === "string" ? request.resource.path : this.session.resource.path;
|
|
8630
8968
|
const actions = [...new Set(this.session.actions.map(nativeAction).concat(action === "tinycloud.kv/metadata" ? [action] : []))].sort();
|
|
8631
8969
|
const bodyBytes = request.body === void 0 ? void 0 : Uint8Array.from(request.body);
|
|
8632
8970
|
const bodyDigest = bodyBytes === void 0 ? void 0 : toBase64Url(new Uint8Array(await crypto.subtle.digest("SHA-256", bodyBytes)));
|
|
8633
|
-
const outer =
|
|
8634
|
-
const enforcement =
|
|
8635
|
-
const invocationBase = { type: "TinyCloudShareReadInvocation", version: 2, sessionId: this.session.sessionId, envelopeCid: authority.envelopeCid, shareCid: authority.shareCid, shareId:
|
|
8971
|
+
const outer = object2(authority.outerEnvelope, "owner authority outer envelope");
|
|
8972
|
+
const enforcement = object2(authority.enforcementDelegation, "owner authority enforcement delegation");
|
|
8973
|
+
const invocationBase = { type: "TinyCloudShareReadInvocation", version: 2, sessionId: this.session.sessionId, envelopeCid: authority.envelopeCid, shareCid: authority.shareCid, shareId: envelope.shareId, registrationCid: authority.registrationCid, delegationCid: envelope.delegationCid, policyCid: envelope.authorizationTarget.kind === "policy" ? envelope.authorizationTarget.policyCid : "", enforcementDelegationCid: String(enforcement.cid), contentSource: outer.contentSource, contentSourceDigest: String(outer.contentSourceDigest), holderDid: this.options.holderDid, targetOrigin: String(object2(outer.target, "owner authority target").origin), nodeAudience: String(object2(outer.target, "owner authority target").nodeAudience), action, actions, resource, ...action === "tinycloud.kv/list" ? { limit: 100 } : {}, ...bodyDigest === void 0 ? {} : { bodyDigest, ifMatch: request.ifMatch, contentType: request.contentType }, issuedAt: (/* @__PURE__ */ new Date()).toISOString(), expiresAt: new Date(Math.min(Date.now() + 6e4, Date.parse(this.session.expiresAt))).toISOString(), jti: toBase64Url(crypto.getRandomValues(new Uint8Array(16))) };
|
|
8636
8974
|
const requestBodyDigest = await digest3(invocationBase);
|
|
8637
8975
|
const invocation = { ...invocationBase, requestBodyDigest };
|
|
8638
8976
|
const proof = { ...this.holderProof, signature: toBase64Url(await this.signer(new TextEncoder().encode(`${INVOCATION_DOMAIN}${canonicalize(invocation)}`))) };
|
|
8639
|
-
const signedRequest = { sessionId: this.session.sessionId, envelopeCid: authority.envelopeCid, shareCid: authority.shareCid, shareId:
|
|
8977
|
+
const signedRequest = { sessionId: this.session.sessionId, envelopeCid: authority.envelopeCid, shareCid: authority.shareCid, shareId: envelope.shareId, registrationCid: authority.registrationCid, delegationCid: envelope.delegationCid, policyCid: envelope.authorizationTarget.kind === "policy" ? envelope.authorizationTarget.policyCid : "", enforcementDelegationCid: String(enforcement.cid), contentSource: outer.contentSource, contentSourceDigest: String(outer.contentSourceDigest), holderDid: this.options.holderDid, nodeAudience: String(object2(outer.target, "owner authority target").nodeAudience), action, actions, resource, requestBodyDigest, invocation, proof };
|
|
8640
8978
|
const response = await this.fetchFn(new URL("/share/v2/invoke", this.options.nodeOrigin), { method: "POST", redirect: "error", headers: { accept: "application/vnd.tinycloud.share+json", "content-type": "application/vnd.tinycloud.share+json" }, body: JSON.stringify({ request: signedRequest, ...action === "tinycloud.kv/list" ? { limit: 100 } : {}, ...bodyBytes === void 0 ? {} : { body: toBase64Url(bodyBytes), bodyDigest, ifMatch: request.ifMatch, contentType: request.contentType } }) });
|
|
8641
8979
|
if (response.ok) await verifyDetachedResponse(response, this.options.trustedNode);
|
|
8642
8980
|
return response;
|
|
8643
8981
|
}
|
|
8982
|
+
async nativeInvokeV3(request, envelope) {
|
|
8983
|
+
const signer = this.nativeSigner ?? this.signer;
|
|
8984
|
+
if (this.session === void 0 || this.v3Authorization === void 0 || signer === void 0) throw new Error("v3 policy session signer is required");
|
|
8985
|
+
const session = verifyCompactUcanAuthorization(this.v3Authorization, this.session.sessionId);
|
|
8986
|
+
if (session.payload.aud !== this.options.holderDid) throw new Error("v3 session recipient mismatch");
|
|
8987
|
+
const action = request.action === "list" ? "tinycloud.kv/list" : request.action === "put" ? "tinycloud.kv/put" : request.action === "metadata" ? "tinycloud.kv/metadata" : "tinycloud.kv/get";
|
|
8988
|
+
const path = typeof request.resource?.path === "string" ? request.resource.path.replace(/^\//, "").replace(/\/$/, "") : envelope.resource.path;
|
|
8989
|
+
const capability = envelope.policy.capabilityCeiling.find((candidate) => candidate.kind === "kv");
|
|
8990
|
+
if (capability === void 0 || !capability.actions.includes(action)) throw new Error("v3 requested capability is not in the signed policy");
|
|
8991
|
+
const marker = "/kv/";
|
|
8992
|
+
const split = capability.resource.indexOf(marker);
|
|
8993
|
+
if (split < 0) throw new Error("v3 KV resource is invalid");
|
|
8994
|
+
const root = capability.resource.slice(split + marker.length).replace(/\/$/, "");
|
|
8995
|
+
if (path !== root && !(capability.selector === "prefix" && path.startsWith(`${root}/`))) throw new Error("v3 KV request is outside the signed selector");
|
|
8996
|
+
const resource = `${capability.resource.slice(0, split + marker.length)}${path}`;
|
|
8997
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
8998
|
+
const invocation = await signCompactUcanAuthorization({ issuerDid: this.options.holderDid, audienceDid: envelope.target.nodeAudience, 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 });
|
|
8999
|
+
const headers = new Headers({ accept: "application/json", Authorization: invocation.authorization });
|
|
9000
|
+
let body;
|
|
9001
|
+
if (action === "tinycloud.kv/put") {
|
|
9002
|
+
if (request.body === void 0) throw new Error("v3 KV put requires bytes");
|
|
9003
|
+
body = Uint8Array.from(request.body);
|
|
9004
|
+
headers.set("content-type", request.contentType ?? "application/octet-stream");
|
|
9005
|
+
if (request.ifMatch !== void 0) headers.set("if-match", request.ifMatch);
|
|
9006
|
+
}
|
|
9007
|
+
return this.fetchFn(new URL("/invoke", this.options.nodeOrigin), { method: "POST", redirect: "error", headers, ...body === void 0 ? {} : { body } });
|
|
9008
|
+
}
|
|
8644
9009
|
};
|
|
8645
9010
|
function createAddressedAuthorization(input) {
|
|
8646
|
-
const
|
|
9011
|
+
const { buildPresentation, ...options } = input;
|
|
9012
|
+
const client = (envelope) => new ShareRecipientClient({
|
|
9013
|
+
...options,
|
|
9014
|
+
envelope,
|
|
9015
|
+
...buildPresentation === void 0 ? {} : { buildPresentation: ({ challenge, envelope: candidate, policy }) => buildPresentation({ challenge, envelope: candidate, policy }) }
|
|
9016
|
+
});
|
|
8647
9017
|
return {
|
|
8648
9018
|
async begin({ envelope, method }) {
|
|
8649
9019
|
const current = client(envelope);
|
|
@@ -8657,15 +9027,15 @@ function createAddressedAuthorization(input) {
|
|
|
8657
9027
|
},
|
|
8658
9028
|
async verifyResult({ envelope, value, proof }) {
|
|
8659
9029
|
try {
|
|
8660
|
-
const wrapper =
|
|
8661
|
-
const response =
|
|
9030
|
+
const wrapper = object2(proof, "share read proof");
|
|
9031
|
+
const response = object2(wrapper.response, "share read response proof");
|
|
8662
9032
|
const envelopeAction = nativeAction("read");
|
|
8663
9033
|
if (response.type !== "TinyCloudShareInvokeResponse" || response.version !== 2 || response.action !== envelopeAction || response.resource !== envelope.resource.path || typeof response.bodyDigest !== "string" || response.bodyDigest !== value.bodyDigest || response.bodyDigest !== await digestBytes2(value.bytes)) return false;
|
|
8664
|
-
const detached =
|
|
9034
|
+
const detached = object2(wrapper.detached, "share read detached proof");
|
|
8665
9035
|
if (detached.alg !== "EdDSA" || detached.kid !== input.trustedNode.invitationKid) return false;
|
|
8666
9036
|
const unsigned = { ...response };
|
|
8667
9037
|
delete unsigned.proof;
|
|
8668
|
-
return
|
|
9038
|
+
return ed255198.verify(bytes2(detached.signature, "share read signature"), new TextEncoder().encode(`${SHARE_V2_PROTOCOL.readResponseDomain}${canonicalize(unsigned)}`), trustedPublicKey(input.trustedNode));
|
|
8669
9039
|
} catch {
|
|
8670
9040
|
return false;
|
|
8671
9041
|
}
|