@tinycloud/share-sdk 1.4.1-beta.0 → 1.4.1-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/{addressed-publish-BP6nsgRq.d.cts → addressed-publish-BcvisG4a.d.cts} +7 -0
- package/dist/{addressed-publish-CpkwV-Au.d.ts → addressed-publish-CwTTn9Jn.d.ts} +7 -0
- package/dist/artifact.cjs +27 -4
- package/dist/artifact.cjs.map +1 -1
- package/dist/artifact.js +27 -4
- package/dist/artifact.js.map +1 -1
- package/dist/index.cjs +283 -13
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +3 -3
- package/dist/index.d.ts +3 -3
- package/dist/index.js +283 -13
- package/dist/index.js.map +1 -1
- package/dist/share-index.cjs +27 -4
- package/dist/share-index.cjs.map +1 -1
- package/dist/share-index.js +27 -4
- package/dist/share-index.js.map +1 -1
- package/dist/share-root.cjs +27 -4
- package/dist/share-root.cjs.map +1 -1
- package/dist/share-root.d.cts +1 -1
- package/dist/share-root.d.ts +1 -1
- package/dist/share-root.js +27 -4
- package/dist/share-root.js.map +1 -1
- package/dist/{web-recipient-entry-DVyTQBl0.d.ts → web-recipient-entry-CPntAP0H.d.ts} +46 -5
- package/dist/{web-recipient-entry-CiUzdSQs.d.cts → web-recipient-entry-Cy1fXl_5.d.cts} +46 -5
- package/dist/web-recipient.cjs +258 -11
- package/dist/web-recipient.cjs.map +1 -1
- package/dist/web-recipient.d.cts +1 -1
- package/dist/web-recipient.d.ts +1 -1
- package/dist/web-recipient.js +258 -11
- package/dist/web-recipient.js.map +1 -1
- package/package.json +2 -2
package/dist/web-recipient.d.cts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
export { f as ShareMetadata,
|
|
1
|
+
export { f as ShareMetadata, r as ShareRecipientClient, u as inspectShare } from './web-recipient-entry-Cy1fXl_5.cjs';
|
|
2
2
|
import '@tinycloud/share-envelope';
|
|
3
3
|
import './authorization-CmP1skO8.cjs';
|
package/dist/web-recipient.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
export { f as ShareMetadata,
|
|
1
|
+
export { f as ShareMetadata, r as ShareRecipientClient, u as inspectShare } from './web-recipient-entry-CPntAP0H.js';
|
|
2
2
|
import '@tinycloud/share-envelope';
|
|
3
3
|
import './authorization-CmP1skO8.js';
|
package/dist/web-recipient.js
CHANGED
|
@@ -5225,6 +5225,23 @@ var unifiedEncryptionNetworkSchema = external_exports.string().refine((value) =>
|
|
|
5225
5225
|
const network = separator < 0 ? "" : rest.slice(separator + 1);
|
|
5226
5226
|
return owner.startsWith("did:") && owner.length > 4 && network.length > 0 && !/[:/%?#\s]/.test(network);
|
|
5227
5227
|
}, { message: "expected canonical TinyCloud encryption network" });
|
|
5228
|
+
var unifiedEncryptionResourceSchema = external_exports.string().refine((value) => {
|
|
5229
|
+
const prefix = "urn:tinycloud:encryption:";
|
|
5230
|
+
if (!value.startsWith(prefix)) return false;
|
|
5231
|
+
const match = /^(did:.+):([^/:?#\s]+)(?:\/(.+))?$/.exec(value.slice(prefix.length));
|
|
5232
|
+
if (match === null) return false;
|
|
5233
|
+
const path = match[3];
|
|
5234
|
+
return path === void 0 || path.split("/").every(
|
|
5235
|
+
(segment) => segment.length > 0 && segment !== "." && segment !== ".." && !/[\\:%?#\s]/.test(segment)
|
|
5236
|
+
);
|
|
5237
|
+
}, { message: "expected a canonical TinyCloud encryption resource" });
|
|
5238
|
+
var unifiedEncryptionKeyPathSchema = external_exports.string().regex(/^share\/[^/]+$/).refine(
|
|
5239
|
+
(value) => {
|
|
5240
|
+
const segment = value.slice("share/".length);
|
|
5241
|
+
return segment !== "." && segment !== ".." && !/[\\:%?#\s]/.test(segment);
|
|
5242
|
+
},
|
|
5243
|
+
{ message: "expected an exact share key path" }
|
|
5244
|
+
);
|
|
5228
5245
|
var unifiedKvCapabilitySchema = external_exports.object({
|
|
5229
5246
|
kind: external_exports.literal("kv"),
|
|
5230
5247
|
resource: unifiedResourceSchema,
|
|
@@ -5238,7 +5255,7 @@ var unifiedKvCapabilitySchema = external_exports.object({
|
|
|
5238
5255
|
}).strict();
|
|
5239
5256
|
var unifiedEncryptionCapabilitySchema = external_exports.object({
|
|
5240
5257
|
kind: external_exports.literal("encryption"),
|
|
5241
|
-
resource:
|
|
5258
|
+
resource: unifiedEncryptionResourceSchema,
|
|
5242
5259
|
action: external_exports.literal("tinycloud.encryption/decrypt")
|
|
5243
5260
|
}).strict();
|
|
5244
5261
|
var unifiedCapabilitySchema = external_exports.union([unifiedKvCapabilitySchema, unifiedEncryptionCapabilitySchema]);
|
|
@@ -5247,6 +5264,7 @@ var unifiedContentSourceSchema = external_exports.object({
|
|
|
5247
5264
|
kvResource: unifiedResourceSchema,
|
|
5248
5265
|
selector: external_exports.union([external_exports.literal("exact"), external_exports.literal("prefix")]),
|
|
5249
5266
|
encryptionNetwork: unifiedEncryptionNetworkSchema,
|
|
5267
|
+
encryptionKeyPath: unifiedEncryptionKeyPathSchema.optional(),
|
|
5250
5268
|
encryptedSymmetricKeyDigestHex: external_exports.string().regex(/^[0-9a-f]{64}$/),
|
|
5251
5269
|
keyVersion: external_exports.number().int().positive(),
|
|
5252
5270
|
mode: external_exports.union([external_exports.literal("mutable"), external_exports.literal("immutable")]),
|
|
@@ -5364,22 +5382,71 @@ function validateV3Invariants(value, ctx) {
|
|
|
5364
5382
|
if (value.attestedEnforcerBinding.nodeAudience !== value.target.nodeAudience || value.attestedEnforcerBinding.signature.signerDid !== value.attestedEnforcerBinding.nodeAudience || Date.parse(value.attestedEnforcerBinding.expiresAt) < Date.parse(value.expiry)) {
|
|
5365
5383
|
ctx.addIssue({ code: external_exports.ZodIssueCode.custom, path: ["attestedEnforcerBinding"], message: "enforcer binding does not cover the target and share lifetime" });
|
|
5366
5384
|
}
|
|
5367
|
-
|
|
5385
|
+
const encryptionKeyPath = value.contentSource.encryptionKeyPath;
|
|
5386
|
+
if (encryptionKeyPath !== void 0 && encryptionKeyPath !== `share/${value.contentSource.shareId}`) {
|
|
5387
|
+
ctx.addIssue({ code: external_exports.ZodIssueCode.custom, path: ["contentSource", "encryptionKeyPath"], message: "share key path must name this share" });
|
|
5388
|
+
}
|
|
5389
|
+
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.encryptionKeyPath !== encryptionKeyPath || 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) {
|
|
5368
5390
|
ctx.addIssue({ code: external_exports.ZodIssueCode.custom, path: ["contentSource"], message: "content source is not bound to policy" });
|
|
5369
5391
|
}
|
|
5370
5392
|
if (value.policy.signature.suite !== "Ed25519" || value.policy.signature.signerDid !== value.policy.ownerDid) {
|
|
5371
5393
|
ctx.addIssue({ code: external_exports.ZodIssueCode.custom, path: ["policy", "signature"], message: "policy must be signed by its owner" });
|
|
5372
5394
|
}
|
|
5373
5395
|
const kvCapabilities = value.policy.capabilityCeiling.filter((capability) => capability.kind === "kv");
|
|
5374
|
-
const
|
|
5396
|
+
const expectedEncryptionResource = encryptionKeyPath === void 0 ? value.encryptionNetwork : `${value.encryptionNetwork}/${encryptionKeyPath}`;
|
|
5397
|
+
const decryptCapabilities = value.policy.capabilityCeiling.filter((capability) => capability.kind === "encryption" && capability.resource === expectedEncryptionResource && capability.action === "tinycloud.encryption/decrypt");
|
|
5375
5398
|
if (value.policy.capabilityCeiling.length !== 2 || kvCapabilities.length !== 1 || decryptCapabilities.length !== 1 || kvCapabilities[0]?.resource !== value.contentSource.kvResource || kvCapabilities[0]?.selector !== value.contentSource.selector) {
|
|
5376
|
-
ctx.addIssue({ code: external_exports.ZodIssueCode.custom, path: ["policy", "capabilityCeiling"], message: "policy ceiling must contain exact decrypt
|
|
5399
|
+
ctx.addIssue({ code: external_exports.ZodIssueCode.custom, path: ["policy", "capabilityCeiling"], message: "policy ceiling must contain the exact decrypt resource" });
|
|
5377
5400
|
}
|
|
5378
5401
|
}
|
|
5379
5402
|
var unsignedShareEnvelopeV3Schema = unsignedShareEnvelopeV3BaseSchema.superRefine(validateV3Invariants);
|
|
5380
5403
|
var shareEnvelopeV3Schema = unsignedShareEnvelopeV3BaseSchema.extend({ signature: signatureSchema }).strict().superRefine(validateV3Invariants);
|
|
5381
5404
|
var ENVELOPE_AAD_LABEL = "tinycloud-share-envelope-v1";
|
|
5405
|
+
var SEALED_BLOB_VERSION = 1;
|
|
5382
5406
|
var AAD = utf8Bytes(ENVELOPE_AAD_LABEL);
|
|
5407
|
+
var KEY_LENGTH = 32;
|
|
5408
|
+
var NONCE_LENGTH = 12;
|
|
5409
|
+
var TAG_LENGTH = 16;
|
|
5410
|
+
var HEADER_LENGTH = 1;
|
|
5411
|
+
function assertKey(key32) {
|
|
5412
|
+
if (key32.length !== KEY_LENGTH) {
|
|
5413
|
+
throw new TypeError(`key must be ${KEY_LENGTH} bytes, got ${key32.length}`);
|
|
5414
|
+
}
|
|
5415
|
+
}
|
|
5416
|
+
async function importAesKey(key32, usage) {
|
|
5417
|
+
assertKey(key32);
|
|
5418
|
+
return globalThis.crypto.subtle.importKey(
|
|
5419
|
+
"raw",
|
|
5420
|
+
key32,
|
|
5421
|
+
"AES-GCM",
|
|
5422
|
+
false,
|
|
5423
|
+
[usage]
|
|
5424
|
+
);
|
|
5425
|
+
}
|
|
5426
|
+
async function open(blob, key32) {
|
|
5427
|
+
if (blob.length < HEADER_LENGTH + NONCE_LENGTH + TAG_LENGTH) {
|
|
5428
|
+
throw new TypeError(`sealed blob too short: ${blob.length} bytes`);
|
|
5429
|
+
}
|
|
5430
|
+
if (blob[0] !== SEALED_BLOB_VERSION) {
|
|
5431
|
+
throw new TypeError(`unknown sealed blob version: ${blob[0]}`);
|
|
5432
|
+
}
|
|
5433
|
+
const nonce = blob.subarray(HEADER_LENGTH, HEADER_LENGTH + NONCE_LENGTH);
|
|
5434
|
+
const ciphertext = blob.subarray(HEADER_LENGTH + NONCE_LENGTH);
|
|
5435
|
+
return decryptEnvelope(nonce, ciphertext, key32);
|
|
5436
|
+
}
|
|
5437
|
+
async function decryptEnvelope(nonce, ciphertext, key32) {
|
|
5438
|
+
if (nonce.length !== NONCE_LENGTH) {
|
|
5439
|
+
throw new TypeError(`nonce must be ${NONCE_LENGTH} bytes, got ${nonce.length}`);
|
|
5440
|
+
}
|
|
5441
|
+
const key = await importAesKey(key32, "decrypt");
|
|
5442
|
+
return new Uint8Array(
|
|
5443
|
+
await globalThis.crypto.subtle.decrypt(
|
|
5444
|
+
{ name: "AES-GCM", iv: nonce, additionalData: AAD },
|
|
5445
|
+
key,
|
|
5446
|
+
ciphertext
|
|
5447
|
+
)
|
|
5448
|
+
);
|
|
5449
|
+
}
|
|
5383
5450
|
function isPlainRecord(value) {
|
|
5384
5451
|
const proto = Object.getPrototypeOf(value);
|
|
5385
5452
|
return proto === Object.prototype || proto === null;
|
|
@@ -5510,6 +5577,7 @@ function equal(left, right) {
|
|
|
5510
5577
|
return left.length === right.length && left.every((byte, index) => byte === right[index]);
|
|
5511
5578
|
}
|
|
5512
5579
|
var ED25519_VERIFY_OPTS2 = { zip215: false };
|
|
5580
|
+
var ENVELOPE_V2_SIGNATURE_DOMAIN = "xyz.tinycloud.share/envelope/v2\0";
|
|
5513
5581
|
var ENVELOPE_V3_SIGNATURE_DOMAIN = "xyz.tinycloud.share/envelope/v3\0";
|
|
5514
5582
|
var POLICY_V1_SIGNATURE_DOMAIN = "xyz.tinycloud.policy/policy/v1\0";
|
|
5515
5583
|
var POLICY_V2_SIGNATURE_DOMAIN = "xyz.tinycloud.policy/policy/v2\0";
|
|
@@ -5517,6 +5585,14 @@ var CONTENT_SOURCE_V1_DOMAIN = "xyz.tinycloud.policy/ContentSource/v1\0";
|
|
|
5517
5585
|
var POLICY_CAPABILITY_V1_DOMAIN = "xyz.tinycloud.policy/PolicyCapability/v1\0";
|
|
5518
5586
|
var NATIVE_PROJECTION_V1_DOMAIN = "xyz.tinycloud.policy/NativeProjection/v1\0";
|
|
5519
5587
|
var ATTESTED_ENFORCER_V2_DOMAIN = "xyz.tinycloud.policy/AttestedEnforcerBinding/v2\0";
|
|
5588
|
+
function signingBytesV2(unsigned) {
|
|
5589
|
+
const domain = utf8Bytes(ENVELOPE_V2_SIGNATURE_DOMAIN);
|
|
5590
|
+
const body = utf8Bytes(canonicalize(unsigned));
|
|
5591
|
+
const bytes = new Uint8Array(domain.length + body.length);
|
|
5592
|
+
bytes.set(domain);
|
|
5593
|
+
bytes.set(body, domain.length);
|
|
5594
|
+
return bytes;
|
|
5595
|
+
}
|
|
5520
5596
|
function signingBytesV3(unsigned) {
|
|
5521
5597
|
const domain = utf8Bytes(ENVELOPE_V3_SIGNATURE_DOMAIN);
|
|
5522
5598
|
const body = utf8Bytes(canonicalize(unsigned));
|
|
@@ -5525,6 +5601,17 @@ function signingBytesV3(unsigned) {
|
|
|
5525
5601
|
bytes.set(body, domain.length);
|
|
5526
5602
|
return bytes;
|
|
5527
5603
|
}
|
|
5604
|
+
function verifyEnvelopeV2SignatureOnly(envelope) {
|
|
5605
|
+
const parsed = shareEnvelopeV2Schema.parse(envelope);
|
|
5606
|
+
const { signature, ...unsigned } = parsed;
|
|
5607
|
+
return ed255193.verify(fromBase64Url(signature.value), signingBytesV2(unsigned), ed25519PublicKeyFromDidKey(signature.signerDid), ED25519_VERIFY_OPTS2);
|
|
5608
|
+
}
|
|
5609
|
+
async function verifyEnvelopeV2(envelope, options) {
|
|
5610
|
+
const parsed = shareEnvelopeV2Schema.parse(envelope);
|
|
5611
|
+
if (parsed.signature.signerDid !== options.expectedSignerDid || !verifyEnvelopeV2SignatureOnly(parsed)) return false;
|
|
5612
|
+
if (parsed.authorizationTarget.kind === "policy") return await computeCid(fromBase64Url(parsed.authorizationTarget.policyBytes)) === parsed.authorizationTarget.policyCid;
|
|
5613
|
+
return true;
|
|
5614
|
+
}
|
|
5528
5615
|
function verifyEnvelopeV3SignatureOnly(envelope) {
|
|
5529
5616
|
const parsed = shareEnvelopeV3Schema.parse(envelope);
|
|
5530
5617
|
const { signature, ...unsigned } = parsed;
|
|
@@ -5611,6 +5698,52 @@ function base32Lower(bytes) {
|
|
|
5611
5698
|
if (bits > 0) output += alphabet[buffer << 5 - bits & 31];
|
|
5612
5699
|
return output;
|
|
5613
5700
|
}
|
|
5701
|
+
var KEY_LENGTH2 = 32;
|
|
5702
|
+
function assertCanonicalCid(cidString) {
|
|
5703
|
+
const cid = CID.parse(cidString);
|
|
5704
|
+
if (cid.version !== 1 || cid.code !== code || cid.multihash.code !== 18 || // sha2-256 (0x12) only, at the link layer too
|
|
5705
|
+
cid.toString() !== cidString) {
|
|
5706
|
+
throw new TypeError(`not a canonical CIDv1 raw sha2-256 base32 CID: ${cidString}`);
|
|
5707
|
+
}
|
|
5708
|
+
}
|
|
5709
|
+
function parseShareUrl(url, options = {}) {
|
|
5710
|
+
const parsed = new URL(url);
|
|
5711
|
+
if (parsed.protocol !== "https:") {
|
|
5712
|
+
throw new TypeError(`share URL must be https, got ${parsed.protocol}`);
|
|
5713
|
+
}
|
|
5714
|
+
if (parsed.username !== "" || parsed.password !== "") {
|
|
5715
|
+
throw new TypeError("share URL must not carry userinfo");
|
|
5716
|
+
}
|
|
5717
|
+
if (parsed.search !== "") {
|
|
5718
|
+
throw new TypeError("share URL must not have a query string");
|
|
5719
|
+
}
|
|
5720
|
+
if (options.expectedOrigin !== void 0) {
|
|
5721
|
+
if (!isCanonicalHttpsOrigin(options.expectedOrigin)) {
|
|
5722
|
+
throw new TypeError(
|
|
5723
|
+
`expectedOrigin must be a canonical https origin, got ${options.expectedOrigin}`
|
|
5724
|
+
);
|
|
5725
|
+
}
|
|
5726
|
+
if (parsed.origin !== options.expectedOrigin) {
|
|
5727
|
+
throw new TypeError(
|
|
5728
|
+
`share URL origin ${parsed.origin} does not match expected ${options.expectedOrigin}`
|
|
5729
|
+
);
|
|
5730
|
+
}
|
|
5731
|
+
}
|
|
5732
|
+
const match = /^\/s\/([a-z2-7]+)$/.exec(parsed.pathname);
|
|
5733
|
+
if (!match || match[1] === void 0) {
|
|
5734
|
+
throw new TypeError(`not a share URL path: ${parsed.pathname}`);
|
|
5735
|
+
}
|
|
5736
|
+
const ciphertextCid = match[1];
|
|
5737
|
+
assertCanonicalCid(ciphertextCid);
|
|
5738
|
+
if (!parsed.hash.startsWith("#k=")) {
|
|
5739
|
+
throw new TypeError("share URL is missing the #k= key fragment");
|
|
5740
|
+
}
|
|
5741
|
+
const key32 = fromBase64Url(parsed.hash.slice("#k=".length));
|
|
5742
|
+
if (key32.length !== KEY_LENGTH2) {
|
|
5743
|
+
throw new TypeError(`fragment key must be ${KEY_LENGTH2} bytes, got ${key32.length}`);
|
|
5744
|
+
}
|
|
5745
|
+
return { ciphertextCid, key32 };
|
|
5746
|
+
}
|
|
5614
5747
|
|
|
5615
5748
|
// src/artifact-core.ts
|
|
5616
5749
|
var ARTIFACT_ROOT = "artifacts";
|
|
@@ -5707,7 +5840,7 @@ var ShareReceiveError = class extends Error {
|
|
|
5707
5840
|
}
|
|
5708
5841
|
};
|
|
5709
5842
|
function metadataFor(envelope, origin) {
|
|
5710
|
-
const kind = envelope.recipientMatcher.kind === "recipientDid" ? "recipientDid" : envelope.recipientMatcher.kind === "exactEmail" ? "email" : "emailDomain";
|
|
5843
|
+
const kind = envelope.recipientMatcher.kind === "recipientDid" ? "recipientDid" : envelope.recipientMatcher.kind === "exactEmail" ? "email" : envelope.recipientMatcher.kind === "emailDomain" ? "emailDomain" : "bearer";
|
|
5711
5844
|
return {
|
|
5712
5845
|
protocol: "tinycloud-share",
|
|
5713
5846
|
version: 1,
|
|
@@ -5722,7 +5855,7 @@ function metadataFor(envelope, origin) {
|
|
|
5722
5855
|
...envelope.display.filename === void 0 ? {} : { filename: envelope.display.filename },
|
|
5723
5856
|
...envelope.display.mode === void 0 ? {} : { mode: envelope.display.mode }
|
|
5724
5857
|
},
|
|
5725
|
-
...envelope.
|
|
5858
|
+
...envelope.version === 3 && envelope.artifactPin !== void 0 ? { artifact: { id: envelope.artifactPin.artifactId, version: envelope.artifactPin.version } } : {}
|
|
5726
5859
|
};
|
|
5727
5860
|
}
|
|
5728
5861
|
function receiveErrorFor(error) {
|
|
@@ -5820,10 +5953,123 @@ async function resolvePolicyShare(link, options) {
|
|
|
5820
5953
|
options.onResolvedAddressedEnvelope?.(envelope, cid);
|
|
5821
5954
|
return { envelope, origin: url.origin, cid };
|
|
5822
5955
|
}
|
|
5956
|
+
async function resolveLegacyV2(link, options) {
|
|
5957
|
+
let parsed;
|
|
5958
|
+
try {
|
|
5959
|
+
parsed = parseShareUrl(link, { ...options.expectedOrigin === void 0 ? {} : { expectedOrigin: options.expectedOrigin } });
|
|
5960
|
+
} catch {
|
|
5961
|
+
throw new ShareReceiveError("invalid-link", "legacy share link format is invalid");
|
|
5962
|
+
}
|
|
5963
|
+
try {
|
|
5964
|
+
options.signal?.throwIfAborted();
|
|
5965
|
+
const origin = new URL(link).origin;
|
|
5966
|
+
const limit = options.maxSealedBlobBytes ?? DEFAULT_MAX_SEALED_BLOB_BYTES;
|
|
5967
|
+
if (!Number.isSafeInteger(limit) || limit <= 0) throw new ShareReceiveError("max-bytes-exceeded", "sealed envelope limit is invalid");
|
|
5968
|
+
let sealed;
|
|
5969
|
+
if (options.fetchBlob !== void 0) {
|
|
5970
|
+
try {
|
|
5971
|
+
sealed = await options.fetchBlob({ origin, cid: parsed.ciphertextCid, ...options.signal === void 0 ? {} : { signal: options.signal } });
|
|
5972
|
+
} catch {
|
|
5973
|
+
throw new ShareReceiveError("fetch-failed", "legacy registry unavailable");
|
|
5974
|
+
}
|
|
5975
|
+
} else {
|
|
5976
|
+
if (options.registryBaseUrl === void 0) throw new ShareReceiveError("fetch-failed", "legacy registry is required");
|
|
5977
|
+
const response = await (options.fetch ?? globalThis.fetch)(`${options.registryBaseUrl.replace(/\/+$/, "")}/ipfs/${parsed.ciphertextCid}?format=raw`, {
|
|
5978
|
+
headers: { accept: "application/vnd.ipld.raw" },
|
|
5979
|
+
redirect: "error",
|
|
5980
|
+
...options.signal === void 0 ? {} : { signal: options.signal }
|
|
5981
|
+
});
|
|
5982
|
+
if (!response.ok) throw new ShareReceiveError("fetch-failed", "legacy registry unavailable");
|
|
5983
|
+
if (Number(response.headers.get("content-length")) > limit) throw new ShareReceiveError("max-bytes-exceeded", "sealed envelope exceeds the size limit");
|
|
5984
|
+
const reader = response.body?.getReader();
|
|
5985
|
+
if (reader === void 0) throw new ShareReceiveError("fetch-failed", "legacy registry returned no body");
|
|
5986
|
+
const chunks = [];
|
|
5987
|
+
let length2 = 0;
|
|
5988
|
+
for (; ; ) {
|
|
5989
|
+
const part = await reader.read();
|
|
5990
|
+
if (part.done) break;
|
|
5991
|
+
length2 += part.value.length;
|
|
5992
|
+
if (length2 > limit) {
|
|
5993
|
+
await reader.cancel();
|
|
5994
|
+
throw new ShareReceiveError("max-bytes-exceeded", "sealed envelope exceeds the size limit");
|
|
5995
|
+
}
|
|
5996
|
+
chunks.push(part.value);
|
|
5997
|
+
}
|
|
5998
|
+
sealed = new Uint8Array(length2);
|
|
5999
|
+
let offset = 0;
|
|
6000
|
+
for (const chunk of chunks) {
|
|
6001
|
+
sealed.set(chunk, offset);
|
|
6002
|
+
offset += chunk.length;
|
|
6003
|
+
}
|
|
6004
|
+
}
|
|
6005
|
+
options.signal?.throwIfAborted();
|
|
6006
|
+
if (!(sealed instanceof Uint8Array) || sealed.length > limit) throw new ShareReceiveError("max-bytes-exceeded", "sealed envelope exceeds the size limit");
|
|
6007
|
+
if (await computeCid(sealed) !== parsed.ciphertextCid) throw new ShareReceiveError("cid-mismatch", "legacy envelope CID does not match");
|
|
6008
|
+
let plaintext;
|
|
6009
|
+
try {
|
|
6010
|
+
plaintext = await open(sealed, parsed.key32);
|
|
6011
|
+
} catch {
|
|
6012
|
+
throw new ShareReceiveError("decrypt-failed", "legacy envelope could not be opened");
|
|
6013
|
+
}
|
|
6014
|
+
let envelope;
|
|
6015
|
+
try {
|
|
6016
|
+
const encoded = new TextDecoder("utf-8", { fatal: true }).decode(plaintext);
|
|
6017
|
+
const value = JSON.parse(encoded);
|
|
6018
|
+
if (canonicalize(value) !== encoded) throw new Error("non-canonical envelope");
|
|
6019
|
+
envelope = shareEnvelopeV2Schema.parse(value);
|
|
6020
|
+
} catch {
|
|
6021
|
+
throw new ShareReceiveError("envelope-invalid", "legacy envelope is invalid");
|
|
6022
|
+
}
|
|
6023
|
+
return { envelope, origin, cid: parsed.ciphertextCid };
|
|
6024
|
+
} finally {
|
|
6025
|
+
parsed.key32.fill(0);
|
|
6026
|
+
}
|
|
6027
|
+
}
|
|
6028
|
+
function evidenceMatchesV2(envelope, evidence) {
|
|
6029
|
+
const actions = new Set(evidence.actions.map((action) => action === "read" ? "tinycloud.kv/get" : action === "list" ? "tinycloud.kv/list" : action === "edit" ? "tinycloud.kv/put" : action));
|
|
6030
|
+
const resourceCovers = evidence.resource.kind === "exact" ? envelope.resource.kind === "exact" && evidence.resource.path === envelope.resource.path : envelope.resource.path === evidence.resource.path || envelope.resource.path.startsWith(evidence.resource.path.endsWith("/") ? evidence.resource.path : `${evidence.resource.path}/`);
|
|
6031
|
+
return envelope.authorizationTarget.kind === "policy" && evidence.policyCid === envelope.authorizationTarget.policyCid && evidence.signerDid === envelope.signature.signerDid && evidence.registrationCid.length > 0 && evidence.shareId === envelope.shareId && canonicalize(evidence.recipientMatcher) === canonicalize(envelope.recipientMatcher) && canonicalize(evidence.target) === canonicalize(envelope.target) && resourceCovers && envelope.actions.every((action) => actions.has(action === "read" ? "tinycloud.kv/get" : action === "list" ? "tinycloud.kv/list" : "tinycloud.kv/put")) && canonicalize(evidence.contentSource) === canonicalize(envelope.contentSource) && evidence.contentSourceDigest === envelope.contentSourceDigest && evidence.delegationCid === envelope.delegationCid && evidence.authorityMaterialHandle === envelope.authorityMaterialHandle && evidence.authorityMaterialDigest === envelope.authorityMaterialDigest && evidence.expiresAt === envelope.expiry && Number.isFinite(Date.parse(evidence.expiresAt)) && Date.parse(evidence.expiresAt) === Date.parse(envelope.expiry);
|
|
6032
|
+
}
|
|
6033
|
+
async function verifyLegacyV2(envelope, options) {
|
|
6034
|
+
try {
|
|
6035
|
+
if (!await verifyEnvelopeV2(envelope, { expectedSignerDid: envelope.signature.signerDid })) throw new Error("signature");
|
|
6036
|
+
} catch {
|
|
6037
|
+
throw new ShareReceiveError("signature-invalid", "legacy envelope signature is invalid");
|
|
6038
|
+
}
|
|
6039
|
+
const expiry = Date.parse(envelope.expiry);
|
|
6040
|
+
if (!Number.isFinite(expiry)) throw new ShareReceiveError("envelope-invalid", "legacy share expiry is invalid");
|
|
6041
|
+
if (expiry <= (options.now?.() ?? Date.now())) throw new ShareReceiveError("expired", "legacy share has expired", { expiresAt: envelope.expiry });
|
|
6042
|
+
if (envelope.authorizationTarget.kind === "policy") {
|
|
6043
|
+
if (options.trustedPolicyAuthority === void 0) throw new ShareReceiveError("envelope-invalid", "a legacy policy authority is required");
|
|
6044
|
+
let evidence;
|
|
6045
|
+
try {
|
|
6046
|
+
evidence = await options.trustedPolicyAuthority.resolve({ policyCid: envelope.authorizationTarget.policyCid, envelope });
|
|
6047
|
+
} catch {
|
|
6048
|
+
}
|
|
6049
|
+
if (evidence === void 0 || !evidenceMatchesV2(envelope, evidence)) throw new ShareReceiveError("capability-invalid", "legacy policy evidence does not match");
|
|
6050
|
+
if (options.trustedSignerDid !== void 0 && options.trustedSignerDid !== evidence.signerDid) throw new ShareReceiveError("signature-invalid", "legacy signer is not trusted");
|
|
6051
|
+
} else if (options.trustedSignerDid === void 0 || options.trustedSignerDid !== envelope.signature.signerDid || envelope.authorizationTarget.kind === "recipientDid" && envelope.recipientMatcher.kind === "recipientDid" && envelope.authorizationTarget.did !== envelope.recipientMatcher.value) {
|
|
6052
|
+
throw new ShareReceiveError("capability-invalid", "legacy recipient authority does not match");
|
|
6053
|
+
}
|
|
6054
|
+
}
|
|
5823
6055
|
async function inspectShare(link, options = {}) {
|
|
6056
|
+
if (isLegacyV2Link(link)) {
|
|
6057
|
+
const legacy = await resolveLegacyV2(link, options);
|
|
6058
|
+
await verifyLegacyV2(legacy.envelope, options);
|
|
6059
|
+
options.onResolvedAddressedEnvelope?.(legacy.envelope, legacy.cid);
|
|
6060
|
+
return { metadata: metadataFor(legacy.envelope, legacy.origin), link: { origin: legacy.origin, cid: legacy.cid, kind: "compact" } };
|
|
6061
|
+
}
|
|
5824
6062
|
const resolved = await resolvePolicyShare(link, options);
|
|
5825
6063
|
return { metadata: metadataFor(resolved.envelope, resolved.origin), link: { origin: resolved.origin, cid: resolved.cid, kind: "policy" } };
|
|
5826
6064
|
}
|
|
6065
|
+
function isLegacyV2Link(link) {
|
|
6066
|
+
try {
|
|
6067
|
+
const url = new URL(link);
|
|
6068
|
+
return /^\/s\/bafkrei[a-z2-7]{52}$/.test(url.pathname) && url.hash.startsWith("#k=");
|
|
6069
|
+
} catch {
|
|
6070
|
+
return false;
|
|
6071
|
+
}
|
|
6072
|
+
}
|
|
5827
6073
|
|
|
5828
6074
|
// src/recipient.ts
|
|
5829
6075
|
import { ed25519 as ed255194, x25519 } from "@noble/curves/ed25519";
|
|
@@ -6019,8 +6265,8 @@ function parseV3InlineEncryptedEnvelope(bytes, expected) {
|
|
|
6019
6265
|
}
|
|
6020
6266
|
if (value === null || typeof value !== "object" || Array.isArray(value)) throw new Error("encrypted content envelope is malformed");
|
|
6021
6267
|
const record = value;
|
|
6022
|
-
const allowed = ["v", "networkId", "alg", "keyVersion", "encryptedSymmetricKey", "encryptedSymmetricKeyHash", "ciphertext", "metadata"];
|
|
6023
|
-
if (Object.keys(record).some((key) => !allowed.includes(key)) || record.v !== 1 || record.networkId !== expected.encryptionNetwork || record.alg !== "x25519-aes256gcm/v1" || record.keyVersion !== expected.contentSource.keyVersion || typeof record.encryptedSymmetricKey !== "string" || typeof record.encryptedSymmetricKeyHash !== "string" || record.encryptedSymmetricKeyHash !== expected.contentSource.encryptedSymmetricKeyDigestHex || record.encryptedSymmetricKeyHash !== canonicalHashHex(record.encryptedSymmetricKey) || typeof record.ciphertext !== "string" || record.metadata !== void 0 && (record.metadata === null || typeof record.metadata !== "object" || Array.isArray(record.metadata) || Object.values(record.metadata).some((entry) => typeof entry !== "string"))) {
|
|
6268
|
+
const allowed = ["v", "networkId", "keyPath", "alg", "keyVersion", "encryptedSymmetricKey", "encryptedSymmetricKeyHash", "ciphertext", "metadata"];
|
|
6269
|
+
if (Object.keys(record).some((key) => !allowed.includes(key)) || record.v !== 1 || record.networkId !== expected.encryptionNetwork || record.keyPath !== expected.contentSource.encryptionKeyPath || record.keyPath !== void 0 && record.keyPath !== `share/${expected.shareId}` || record.alg !== "x25519-aes256gcm/v1" || record.keyVersion !== expected.contentSource.keyVersion || typeof record.encryptedSymmetricKey !== "string" || typeof record.encryptedSymmetricKeyHash !== "string" || record.encryptedSymmetricKeyHash !== expected.contentSource.encryptedSymmetricKeyDigestHex || record.encryptedSymmetricKeyHash !== canonicalHashHex(record.encryptedSymmetricKey) || typeof record.ciphertext !== "string" || record.metadata !== void 0 && (record.metadata === null || typeof record.metadata !== "object" || Array.isArray(record.metadata) || Object.values(record.metadata).some((entry) => typeof entry !== "string"))) {
|
|
6024
6270
|
throw new Error("encrypted content envelope binding is invalid");
|
|
6025
6271
|
}
|
|
6026
6272
|
return record;
|
|
@@ -6152,15 +6398,16 @@ var ShareRecipientClient = class {
|
|
|
6152
6398
|
const receiverPrivateKey = crypto.getRandomValues(new Uint8Array(32));
|
|
6153
6399
|
const receiverPublicKey = toBase64(x25519.getPublicKey(receiverPrivateKey));
|
|
6154
6400
|
const receiverPublicKeyHash = canonicalHashHex(receiverPublicKey);
|
|
6155
|
-
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 };
|
|
6401
|
+
const body = { type: "tinycloud.encryption.decrypt/v1", targetNode: this.v3NodeAudience, networkId: encrypted.networkId, ...encrypted.keyPath === void 0 ? {} : { keyPath: encrypted.keyPath }, alg: encrypted.alg, keyVersion: encrypted.keyVersion, encryptedSymmetricKey: encrypted.encryptedSymmetricKey, encryptedSymmetricKeyHash: encrypted.encryptedSymmetricKeyHash, receiverPublicKey, receiverPublicKeyHash };
|
|
6156
6402
|
const bodyHash = hex2(sha2563(new TextEncoder().encode(canonicalize(body))));
|
|
6157
6403
|
const session = verifyCompactUcanAuthorization(this.v3Authorization, this.session.sessionId);
|
|
6158
6404
|
const now = Math.floor(Date.now() / 1e3);
|
|
6405
|
+
const decryptResource = encrypted.keyPath === void 0 ? encrypted.networkId : `${encrypted.networkId}/${encrypted.keyPath}`;
|
|
6159
6406
|
const invocation = await signCompactUcanAuthorization({
|
|
6160
6407
|
issuerDid: this.options.holderDid,
|
|
6161
6408
|
audienceDid: this.v3NodeAudience,
|
|
6162
|
-
attenuation: { [
|
|
6163
|
-
facts: [{ type: body.type, targetNode: body.targetNode, networkId: body.networkId, bodyHash, encryptedSymmetricKeyHash: body.encryptedSymmetricKeyHash, receiverPublicKeyHash, alg: body.alg, keyVersion: body.keyVersion }],
|
|
6409
|
+
attenuation: { [decryptResource]: { "tinycloud.encryption/decrypt": [{}] } },
|
|
6410
|
+
facts: [{ type: body.type, targetNode: body.targetNode, networkId: body.networkId, ...encrypted.keyPath === void 0 ? {} : { keyPath: encrypted.keyPath }, bodyHash, encryptedSymmetricKeyHash: body.encryptedSymmetricKeyHash, receiverPublicKeyHash, alg: body.alg, keyVersion: body.keyVersion }],
|
|
6164
6411
|
proofs: [session.cid],
|
|
6165
6412
|
notBefore: now,
|
|
6166
6413
|
expiresAt: Math.min(now + 60, session.payload.exp),
|