@tinycloud/share-sdk 1.4.1-beta.0 → 1.4.1-beta.2
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 +302 -49
- 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 +302 -49
- 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-CiUzdSQs.d.cts → web-recipient-entry-Bn3yRB7g.d.cts} +47 -6
- package/dist/{web-recipient-entry-DVyTQBl0.d.ts → web-recipient-entry-CTFoIH8l.d.ts} +47 -6
- package/dist/web-recipient.cjs +277 -47
- 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 +277 -47
- package/dist/web-recipient.js.map +1 -1
- package/package.json +2 -2
package/dist/index.d.cts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
export { D as DEFAULT_MAX_CONTENT_BLOB_BYTES, a as DEFAULT_MAX_SEALED_BLOB_BYTES, S as SHARE_RESULT_VERSION, b as ShareErrorCode, c as ShareErrorInfo, d as ShareFetchOptions, e as ShareInspection, f as ShareMetadata, g as ShareNodeTrust, h as
|
|
1
|
+
export { D as DEFAULT_MAX_CONTENT_BLOB_BYTES, a as DEFAULT_MAX_SEALED_BLOB_BYTES, S as SHARE_RESULT_VERSION, b as ShareErrorCode, c as ShareErrorInfo, d as ShareFetchOptions, e as ShareInspection, f as ShareMetadata, g as ShareNodeTrust, h as SharePolicyAuthority, i as SharePolicyChallenge, j as SharePolicyDelegationLink, k as SharePolicyEvidence, l as SharePolicySession, m as SharePresentationMaterial, n as ShareReceiveAuthorizationRequired, o as ShareReceiveError, p as ShareReceiveOutcome, q as ShareReceiveResult, r as ShareRecipientClient, s as ShareRecipientClientOptions, t as createAddressedAuthorization, u as inspectShare, v as receiveShare, w as toShareErrorInfo } from './web-recipient-entry-Bn3yRB7g.cjs';
|
|
2
2
|
export { C as CreateOwnerDelegationParams, h as DEFAULT_SHARE_LIFETIME_MS, D as DelegatedShareKey, M as MAX_CONTENT_BYTES, i as OWNER_SHARE_REGISTRATION_DOMAIN, j as OwnerDelegationPermission, g as OwnerDelegationReceipt, O as OwnerShareAction, k as OwnerShareDecryption, a as OwnerShareMatcher, l as OwnerSharePolicyRegistration, b as OwnerSharePolicyRegistrationReceipt, m as OwnerSharePolicyV2, c as PublishedShare, P as PublishedShareDeliveryMaterial, n as PublishedShareMetadata, R as RegisterOwnerSharePolicyParams, o as SHARE_CONTENT_LIMIT, p as SHARE_PUBLISH_RESULT_VERSION, f as SharePublishError, q as SharePublishErrorCode, e as SharePublishOptions, d as SharePublishTarget, S as SignedDelegation, r as canonicalOwnerSharePolicy, s as computeOwnerShareRegistrationCid, t as createDelegatedShareKey, u as createPolicyEnforcementDelegation, v as redactPublishedShare, w as restoreDelegatedShareKey, x as validateOwnerSharePolicyRegistration, y as validateOwnerSharePolicyRegistrationBytes } from './owner-policy-aPmXIR3e.cjs';
|
|
3
3
|
import { ShareEnvelopeV2, ArtifactPin, UnsignedShareEnvelopeV3, ShareEnvelopeV3 } from '@tinycloud/share-envelope';
|
|
4
4
|
export { CheckBearerDelegationOptions, DelegationCheckResult, canonicalize, checkBearerDelegation, fromBase64Url, requiredResourceUri, resourceUriCovers, toBase64Url } from '@tinycloud/share-envelope';
|
|
5
|
-
import { T as TargetPublishOutcome, c as TargetPublishInput, S as ShareTarget } from './addressed-publish-
|
|
6
|
-
export { d as ADDRESSED_SHARE_TOO_LARGE_MESSAGE, A as AddressedOwnerRootInput, a as AddressedOwnerRootReceipt, e as AddressedPolicyRegistrationInput, f as AddressedPolicyRegistrationReceipt, b as AddressedPublishAuthority, g as AddressedShareBoundInput, h as AddressedShareOverLimitError, i as AddressedSharePublishOptions, j as AddressedShareRequest, k as AddressedShareWrites, P as PreparedAddressedShare, l as TargetPublishAdapter, m as addressedCredentialRequirement, n as addressedShareBodyUpperBound, o as assertAddressedShareFits, p as normalizeShareTarget, q as prepareAddressedShare, r as publishAddressedShare, s as publishTargetShare, t as targetAuthorizationMethod } from './addressed-publish-
|
|
5
|
+
import { T as TargetPublishOutcome, c as TargetPublishInput, S as ShareTarget } from './addressed-publish-BcvisG4a.cjs';
|
|
6
|
+
export { d as ADDRESSED_SHARE_TOO_LARGE_MESSAGE, A as AddressedOwnerRootInput, a as AddressedOwnerRootReceipt, e as AddressedPolicyRegistrationInput, f as AddressedPolicyRegistrationReceipt, b as AddressedPublishAuthority, g as AddressedShareBoundInput, h as AddressedShareOverLimitError, i as AddressedSharePublishOptions, j as AddressedShareRequest, k as AddressedShareWrites, P as PreparedAddressedShare, l as TargetPublishAdapter, m as addressedCredentialRequirement, n as addressedShareBodyUpperBound, o as assertAddressedShareFits, p as normalizeShareTarget, q as prepareAddressedShare, r as publishAddressedShare, s as publishTargetShare, t as targetAuthorizationMethod } from './addressed-publish-BcvisG4a.cjs';
|
|
7
7
|
import { S as SenderShareRecord, a as SenderShareRecordStorage } from './history-Bg25JIsi.cjs';
|
|
8
8
|
export { E as EncryptedSenderShareHistory, b as EncryptedShareHistoryStorage, M as MemoryEncryptedShareHistoryStorage, c as MemorySenderShareKeyStorage, d as MemorySenderShareRecordStorage, e as SenderShareKeyStorage, f as SenderShareRecordProofs, g as SenderShareRevocationScope, h as SenderShareStore, i as historyRecordForPublishedShare } from './history-Bg25JIsi.cjs';
|
|
9
9
|
import { b as ShareAuthorizationResult, c as ShareAuthorizationAdapter } from './authorization-CmP1skO8.cjs';
|
package/dist/index.d.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
export { D as DEFAULT_MAX_CONTENT_BLOB_BYTES, a as DEFAULT_MAX_SEALED_BLOB_BYTES, S as SHARE_RESULT_VERSION, b as ShareErrorCode, c as ShareErrorInfo, d as ShareFetchOptions, e as ShareInspection, f as ShareMetadata, g as ShareNodeTrust, h as
|
|
1
|
+
export { D as DEFAULT_MAX_CONTENT_BLOB_BYTES, a as DEFAULT_MAX_SEALED_BLOB_BYTES, S as SHARE_RESULT_VERSION, b as ShareErrorCode, c as ShareErrorInfo, d as ShareFetchOptions, e as ShareInspection, f as ShareMetadata, g as ShareNodeTrust, h as SharePolicyAuthority, i as SharePolicyChallenge, j as SharePolicyDelegationLink, k as SharePolicyEvidence, l as SharePolicySession, m as SharePresentationMaterial, n as ShareReceiveAuthorizationRequired, o as ShareReceiveError, p as ShareReceiveOutcome, q as ShareReceiveResult, r as ShareRecipientClient, s as ShareRecipientClientOptions, t as createAddressedAuthorization, u as inspectShare, v as receiveShare, w as toShareErrorInfo } from './web-recipient-entry-CTFoIH8l.js';
|
|
2
2
|
export { C as CreateOwnerDelegationParams, h as DEFAULT_SHARE_LIFETIME_MS, D as DelegatedShareKey, M as MAX_CONTENT_BYTES, i as OWNER_SHARE_REGISTRATION_DOMAIN, j as OwnerDelegationPermission, g as OwnerDelegationReceipt, O as OwnerShareAction, k as OwnerShareDecryption, a as OwnerShareMatcher, l as OwnerSharePolicyRegistration, b as OwnerSharePolicyRegistrationReceipt, m as OwnerSharePolicyV2, c as PublishedShare, P as PublishedShareDeliveryMaterial, n as PublishedShareMetadata, R as RegisterOwnerSharePolicyParams, o as SHARE_CONTENT_LIMIT, p as SHARE_PUBLISH_RESULT_VERSION, f as SharePublishError, q as SharePublishErrorCode, e as SharePublishOptions, d as SharePublishTarget, S as SignedDelegation, r as canonicalOwnerSharePolicy, s as computeOwnerShareRegistrationCid, t as createDelegatedShareKey, u as createPolicyEnforcementDelegation, v as redactPublishedShare, w as restoreDelegatedShareKey, x as validateOwnerSharePolicyRegistration, y as validateOwnerSharePolicyRegistrationBytes } from './owner-policy-aPmXIR3e.js';
|
|
3
3
|
import { ShareEnvelopeV2, ArtifactPin, UnsignedShareEnvelopeV3, ShareEnvelopeV3 } from '@tinycloud/share-envelope';
|
|
4
4
|
export { CheckBearerDelegationOptions, DelegationCheckResult, canonicalize, checkBearerDelegation, fromBase64Url, requiredResourceUri, resourceUriCovers, toBase64Url } from '@tinycloud/share-envelope';
|
|
5
|
-
import { T as TargetPublishOutcome, c as TargetPublishInput, S as ShareTarget } from './addressed-publish-
|
|
6
|
-
export { d as ADDRESSED_SHARE_TOO_LARGE_MESSAGE, A as AddressedOwnerRootInput, a as AddressedOwnerRootReceipt, e as AddressedPolicyRegistrationInput, f as AddressedPolicyRegistrationReceipt, b as AddressedPublishAuthority, g as AddressedShareBoundInput, h as AddressedShareOverLimitError, i as AddressedSharePublishOptions, j as AddressedShareRequest, k as AddressedShareWrites, P as PreparedAddressedShare, l as TargetPublishAdapter, m as addressedCredentialRequirement, n as addressedShareBodyUpperBound, o as assertAddressedShareFits, p as normalizeShareTarget, q as prepareAddressedShare, r as publishAddressedShare, s as publishTargetShare, t as targetAuthorizationMethod } from './addressed-publish-
|
|
5
|
+
import { T as TargetPublishOutcome, c as TargetPublishInput, S as ShareTarget } from './addressed-publish-CwTTn9Jn.js';
|
|
6
|
+
export { d as ADDRESSED_SHARE_TOO_LARGE_MESSAGE, A as AddressedOwnerRootInput, a as AddressedOwnerRootReceipt, e as AddressedPolicyRegistrationInput, f as AddressedPolicyRegistrationReceipt, b as AddressedPublishAuthority, g as AddressedShareBoundInput, h as AddressedShareOverLimitError, i as AddressedSharePublishOptions, j as AddressedShareRequest, k as AddressedShareWrites, P as PreparedAddressedShare, l as TargetPublishAdapter, m as addressedCredentialRequirement, n as addressedShareBodyUpperBound, o as assertAddressedShareFits, p as normalizeShareTarget, q as prepareAddressedShare, r as publishAddressedShare, s as publishTargetShare, t as targetAuthorizationMethod } from './addressed-publish-CwTTn9Jn.js';
|
|
7
7
|
import { S as SenderShareRecord, a as SenderShareRecordStorage } from './history-kwJxrZgq.js';
|
|
8
8
|
export { E as EncryptedSenderShareHistory, b as EncryptedShareHistoryStorage, M as MemoryEncryptedShareHistoryStorage, c as MemorySenderShareKeyStorage, d as MemorySenderShareRecordStorage, e as SenderShareKeyStorage, f as SenderShareRecordProofs, g as SenderShareRevocationScope, h as SenderShareStore, i as historyRecordForPublishedShare } from './history-kwJxrZgq.js';
|
|
9
9
|
import { b as ShareAuthorizationResult, c as ShareAuthorizationAdapter } from './authorization-CmP1skO8.js';
|
package/dist/index.js
CHANGED
|
@@ -5261,6 +5261,23 @@ var unifiedEncryptionNetworkSchema = external_exports.string().refine((value) =>
|
|
|
5261
5261
|
const network = separator < 0 ? "" : rest.slice(separator + 1);
|
|
5262
5262
|
return owner.startsWith("did:") && owner.length > 4 && network.length > 0 && !/[:/%?#\s]/.test(network);
|
|
5263
5263
|
}, { message: "expected canonical TinyCloud encryption network" });
|
|
5264
|
+
var unifiedEncryptionResourceSchema = external_exports.string().refine((value) => {
|
|
5265
|
+
const prefix = "urn:tinycloud:encryption:";
|
|
5266
|
+
if (!value.startsWith(prefix)) return false;
|
|
5267
|
+
const match = /^(did:.+):([^/:?#\s]+)(?:\/(.+))?$/.exec(value.slice(prefix.length));
|
|
5268
|
+
if (match === null) return false;
|
|
5269
|
+
const path = match[3];
|
|
5270
|
+
return path === void 0 || path.split("/").every(
|
|
5271
|
+
(segment) => segment.length > 0 && segment !== "." && segment !== ".." && !/[\\:%?#\s]/.test(segment)
|
|
5272
|
+
);
|
|
5273
|
+
}, { message: "expected a canonical TinyCloud encryption resource" });
|
|
5274
|
+
var unifiedEncryptionKeyPathSchema = external_exports.string().regex(/^share\/[^/]+$/).refine(
|
|
5275
|
+
(value) => {
|
|
5276
|
+
const segment = value.slice("share/".length);
|
|
5277
|
+
return segment !== "." && segment !== ".." && !/[\\:%?#\s]/.test(segment);
|
|
5278
|
+
},
|
|
5279
|
+
{ message: "expected an exact share key path" }
|
|
5280
|
+
);
|
|
5264
5281
|
var unifiedKvCapabilitySchema = external_exports.object({
|
|
5265
5282
|
kind: external_exports.literal("kv"),
|
|
5266
5283
|
resource: unifiedResourceSchema,
|
|
@@ -5274,7 +5291,7 @@ var unifiedKvCapabilitySchema = external_exports.object({
|
|
|
5274
5291
|
}).strict();
|
|
5275
5292
|
var unifiedEncryptionCapabilitySchema = external_exports.object({
|
|
5276
5293
|
kind: external_exports.literal("encryption"),
|
|
5277
|
-
resource:
|
|
5294
|
+
resource: unifiedEncryptionResourceSchema,
|
|
5278
5295
|
action: external_exports.literal("tinycloud.encryption/decrypt")
|
|
5279
5296
|
}).strict();
|
|
5280
5297
|
var unifiedCapabilitySchema = external_exports.union([unifiedKvCapabilitySchema, unifiedEncryptionCapabilitySchema]);
|
|
@@ -5283,6 +5300,7 @@ var unifiedContentSourceSchema = external_exports.object({
|
|
|
5283
5300
|
kvResource: unifiedResourceSchema,
|
|
5284
5301
|
selector: external_exports.union([external_exports.literal("exact"), external_exports.literal("prefix")]),
|
|
5285
5302
|
encryptionNetwork: unifiedEncryptionNetworkSchema,
|
|
5303
|
+
encryptionKeyPath: unifiedEncryptionKeyPathSchema.optional(),
|
|
5286
5304
|
encryptedSymmetricKeyDigestHex: external_exports.string().regex(/^[0-9a-f]{64}$/),
|
|
5287
5305
|
keyVersion: external_exports.number().int().positive(),
|
|
5288
5306
|
mode: external_exports.union([external_exports.literal("mutable"), external_exports.literal("immutable")]),
|
|
@@ -5400,16 +5418,21 @@ function validateV3Invariants(value, ctx) {
|
|
|
5400
5418
|
if (value.attestedEnforcerBinding.nodeAudience !== value.target.nodeAudience || value.attestedEnforcerBinding.signature.signerDid !== value.attestedEnforcerBinding.nodeAudience || Date.parse(value.attestedEnforcerBinding.expiresAt) < Date.parse(value.expiry)) {
|
|
5401
5419
|
ctx.addIssue({ code: external_exports.ZodIssueCode.custom, path: ["attestedEnforcerBinding"], message: "enforcer binding does not cover the target and share lifetime" });
|
|
5402
5420
|
}
|
|
5403
|
-
|
|
5421
|
+
const encryptionKeyPath = value.contentSource.encryptionKeyPath;
|
|
5422
|
+
if (encryptionKeyPath !== void 0 && encryptionKeyPath !== `share/${value.contentSource.shareId}`) {
|
|
5423
|
+
ctx.addIssue({ code: external_exports.ZodIssueCode.custom, path: ["contentSource", "encryptionKeyPath"], message: "share key path must name this share" });
|
|
5424
|
+
}
|
|
5425
|
+
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) {
|
|
5404
5426
|
ctx.addIssue({ code: external_exports.ZodIssueCode.custom, path: ["contentSource"], message: "content source is not bound to policy" });
|
|
5405
5427
|
}
|
|
5406
5428
|
if (value.policy.signature.suite !== "Ed25519" || value.policy.signature.signerDid !== value.policy.ownerDid) {
|
|
5407
5429
|
ctx.addIssue({ code: external_exports.ZodIssueCode.custom, path: ["policy", "signature"], message: "policy must be signed by its owner" });
|
|
5408
5430
|
}
|
|
5409
5431
|
const kvCapabilities = value.policy.capabilityCeiling.filter((capability) => capability.kind === "kv");
|
|
5410
|
-
const
|
|
5432
|
+
const expectedEncryptionResource = encryptionKeyPath === void 0 ? value.encryptionNetwork : `${value.encryptionNetwork}/${encryptionKeyPath}`;
|
|
5433
|
+
const decryptCapabilities = value.policy.capabilityCeiling.filter((capability) => capability.kind === "encryption" && capability.resource === expectedEncryptionResource && capability.action === "tinycloud.encryption/decrypt");
|
|
5411
5434
|
if (value.policy.capabilityCeiling.length !== 2 || kvCapabilities.length !== 1 || decryptCapabilities.length !== 1 || kvCapabilities[0]?.resource !== value.contentSource.kvResource || kvCapabilities[0]?.selector !== value.contentSource.selector) {
|
|
5412
|
-
ctx.addIssue({ code: external_exports.ZodIssueCode.custom, path: ["policy", "capabilityCeiling"], message: "policy ceiling must contain exact decrypt
|
|
5435
|
+
ctx.addIssue({ code: external_exports.ZodIssueCode.custom, path: ["policy", "capabilityCeiling"], message: "policy ceiling must contain the exact decrypt resource" });
|
|
5413
5436
|
}
|
|
5414
5437
|
}
|
|
5415
5438
|
var unsignedShareEnvelopeV3Schema = unsignedShareEnvelopeV3BaseSchema.superRefine(validateV3Invariants);
|
|
@@ -5597,6 +5620,7 @@ var SEALED_BLOB_VERSION = 1;
|
|
|
5597
5620
|
var AAD = utf8Bytes(ENVELOPE_AAD_LABEL);
|
|
5598
5621
|
var KEY_LENGTH = 32;
|
|
5599
5622
|
var NONCE_LENGTH = 12;
|
|
5623
|
+
var TAG_LENGTH = 16;
|
|
5600
5624
|
var HEADER_LENGTH = 1;
|
|
5601
5625
|
function assertKey(key32) {
|
|
5602
5626
|
if (key32.length !== KEY_LENGTH) {
|
|
@@ -5624,6 +5648,17 @@ async function seal(plaintextBytes, key32) {
|
|
|
5624
5648
|
blob.set(ciphertext, HEADER_LENGTH + nonce.length);
|
|
5625
5649
|
return { blob, cid: await computeCid(blob) };
|
|
5626
5650
|
}
|
|
5651
|
+
async function open(blob, key32) {
|
|
5652
|
+
if (blob.length < HEADER_LENGTH + NONCE_LENGTH + TAG_LENGTH) {
|
|
5653
|
+
throw new TypeError(`sealed blob too short: ${blob.length} bytes`);
|
|
5654
|
+
}
|
|
5655
|
+
if (blob[0] !== SEALED_BLOB_VERSION) {
|
|
5656
|
+
throw new TypeError(`unknown sealed blob version: ${blob[0]}`);
|
|
5657
|
+
}
|
|
5658
|
+
const nonce = blob.subarray(HEADER_LENGTH, HEADER_LENGTH + NONCE_LENGTH);
|
|
5659
|
+
const ciphertext = blob.subarray(HEADER_LENGTH + NONCE_LENGTH);
|
|
5660
|
+
return decryptEnvelope(nonce, ciphertext, key32);
|
|
5661
|
+
}
|
|
5627
5662
|
async function encryptEnvelope(plaintextBytes, key32) {
|
|
5628
5663
|
const key = await importAesKey(key32, "encrypt");
|
|
5629
5664
|
const nonce = globalThis.crypto.getRandomValues(new Uint8Array(NONCE_LENGTH));
|
|
@@ -5636,6 +5671,19 @@ async function encryptEnvelope(plaintextBytes, key32) {
|
|
|
5636
5671
|
);
|
|
5637
5672
|
return { nonce, ciphertext };
|
|
5638
5673
|
}
|
|
5674
|
+
async function decryptEnvelope(nonce, ciphertext, key32) {
|
|
5675
|
+
if (nonce.length !== NONCE_LENGTH) {
|
|
5676
|
+
throw new TypeError(`nonce must be ${NONCE_LENGTH} bytes, got ${nonce.length}`);
|
|
5677
|
+
}
|
|
5678
|
+
const key = await importAesKey(key32, "decrypt");
|
|
5679
|
+
return new Uint8Array(
|
|
5680
|
+
await globalThis.crypto.subtle.decrypt(
|
|
5681
|
+
{ name: "AES-GCM", iv: nonce, additionalData: AAD },
|
|
5682
|
+
key,
|
|
5683
|
+
ciphertext
|
|
5684
|
+
)
|
|
5685
|
+
);
|
|
5686
|
+
}
|
|
5639
5687
|
function isPlainRecord(value) {
|
|
5640
5688
|
const proto = Object.getPrototypeOf(value);
|
|
5641
5689
|
return proto === Object.prototype || proto === null;
|
|
@@ -5781,6 +5829,7 @@ function equal(left, right) {
|
|
|
5781
5829
|
return left.length === right.length && left.every((byte, index) => byte === right[index]);
|
|
5782
5830
|
}
|
|
5783
5831
|
var ED25519_VERIFY_OPTS2 = { zip215: false };
|
|
5832
|
+
var ENVELOPE_V2_SIGNATURE_DOMAIN = "xyz.tinycloud.share/envelope/v2\0";
|
|
5784
5833
|
var ENVELOPE_V3_SIGNATURE_DOMAIN = "xyz.tinycloud.share/envelope/v3\0";
|
|
5785
5834
|
var POLICY_V1_SIGNATURE_DOMAIN = "xyz.tinycloud.policy/policy/v1\0";
|
|
5786
5835
|
var POLICY_V2_SIGNATURE_DOMAIN = "xyz.tinycloud.policy/policy/v2\0";
|
|
@@ -5788,6 +5837,14 @@ var CONTENT_SOURCE_V1_DOMAIN = "xyz.tinycloud.policy/ContentSource/v1\0";
|
|
|
5788
5837
|
var POLICY_CAPABILITY_V1_DOMAIN = "xyz.tinycloud.policy/PolicyCapability/v1\0";
|
|
5789
5838
|
var NATIVE_PROJECTION_V1_DOMAIN = "xyz.tinycloud.policy/NativeProjection/v1\0";
|
|
5790
5839
|
var ATTESTED_ENFORCER_V2_DOMAIN = "xyz.tinycloud.policy/AttestedEnforcerBinding/v2\0";
|
|
5840
|
+
function signingBytesV2(unsigned) {
|
|
5841
|
+
const domain = utf8Bytes(ENVELOPE_V2_SIGNATURE_DOMAIN);
|
|
5842
|
+
const body = utf8Bytes(canonicalize(unsigned));
|
|
5843
|
+
const bytes3 = new Uint8Array(domain.length + body.length);
|
|
5844
|
+
bytes3.set(domain);
|
|
5845
|
+
bytes3.set(body, domain.length);
|
|
5846
|
+
return bytes3;
|
|
5847
|
+
}
|
|
5791
5848
|
function signingBytesV3(unsigned) {
|
|
5792
5849
|
const domain = utf8Bytes(ENVELOPE_V3_SIGNATURE_DOMAIN);
|
|
5793
5850
|
const body = utf8Bytes(canonicalize(unsigned));
|
|
@@ -5796,6 +5853,17 @@ function signingBytesV3(unsigned) {
|
|
|
5796
5853
|
bytes3.set(body, domain.length);
|
|
5797
5854
|
return bytes3;
|
|
5798
5855
|
}
|
|
5856
|
+
function verifyEnvelopeV2SignatureOnly(envelope) {
|
|
5857
|
+
const parsed = shareEnvelopeV2Schema.parse(envelope);
|
|
5858
|
+
const { signature, ...unsigned } = parsed;
|
|
5859
|
+
return ed255193.verify(fromBase64Url(signature.value), signingBytesV2(unsigned), ed25519PublicKeyFromDidKey(signature.signerDid), ED25519_VERIFY_OPTS2);
|
|
5860
|
+
}
|
|
5861
|
+
async function verifyEnvelopeV2(envelope, options) {
|
|
5862
|
+
const parsed = shareEnvelopeV2Schema.parse(envelope);
|
|
5863
|
+
if (parsed.signature.signerDid !== options.expectedSignerDid || !verifyEnvelopeV2SignatureOnly(parsed)) return false;
|
|
5864
|
+
if (parsed.authorizationTarget.kind === "policy") return await computeCid(fromBase64Url(parsed.authorizationTarget.policyBytes)) === parsed.authorizationTarget.policyCid;
|
|
5865
|
+
return true;
|
|
5866
|
+
}
|
|
5799
5867
|
function verifyEnvelopeV3SignatureOnly(envelope) {
|
|
5800
5868
|
const parsed = shareEnvelopeV3Schema.parse(envelope);
|
|
5801
5869
|
const { signature, ...unsigned } = parsed;
|
|
@@ -5882,6 +5950,52 @@ function base32Lower(bytes3) {
|
|
|
5882
5950
|
if (bits > 0) output += alphabet[buffer << 5 - bits & 31];
|
|
5883
5951
|
return output;
|
|
5884
5952
|
}
|
|
5953
|
+
var KEY_LENGTH2 = 32;
|
|
5954
|
+
function assertCanonicalCid(cidString) {
|
|
5955
|
+
const cid2 = CID.parse(cidString);
|
|
5956
|
+
if (cid2.version !== 1 || cid2.code !== code || cid2.multihash.code !== 18 || // sha2-256 (0x12) only, at the link layer too
|
|
5957
|
+
cid2.toString() !== cidString) {
|
|
5958
|
+
throw new TypeError(`not a canonical CIDv1 raw sha2-256 base32 CID: ${cidString}`);
|
|
5959
|
+
}
|
|
5960
|
+
}
|
|
5961
|
+
function parseShareUrl(url, options = {}) {
|
|
5962
|
+
const parsed = new URL(url);
|
|
5963
|
+
if (parsed.protocol !== "https:") {
|
|
5964
|
+
throw new TypeError(`share URL must be https, got ${parsed.protocol}`);
|
|
5965
|
+
}
|
|
5966
|
+
if (parsed.username !== "" || parsed.password !== "") {
|
|
5967
|
+
throw new TypeError("share URL must not carry userinfo");
|
|
5968
|
+
}
|
|
5969
|
+
if (parsed.search !== "") {
|
|
5970
|
+
throw new TypeError("share URL must not have a query string");
|
|
5971
|
+
}
|
|
5972
|
+
if (options.expectedOrigin !== void 0) {
|
|
5973
|
+
if (!isCanonicalHttpsOrigin(options.expectedOrigin)) {
|
|
5974
|
+
throw new TypeError(
|
|
5975
|
+
`expectedOrigin must be a canonical https origin, got ${options.expectedOrigin}`
|
|
5976
|
+
);
|
|
5977
|
+
}
|
|
5978
|
+
if (parsed.origin !== options.expectedOrigin) {
|
|
5979
|
+
throw new TypeError(
|
|
5980
|
+
`share URL origin ${parsed.origin} does not match expected ${options.expectedOrigin}`
|
|
5981
|
+
);
|
|
5982
|
+
}
|
|
5983
|
+
}
|
|
5984
|
+
const match = /^\/s\/([a-z2-7]+)$/.exec(parsed.pathname);
|
|
5985
|
+
if (!match || match[1] === void 0) {
|
|
5986
|
+
throw new TypeError(`not a share URL path: ${parsed.pathname}`);
|
|
5987
|
+
}
|
|
5988
|
+
const ciphertextCid = match[1];
|
|
5989
|
+
assertCanonicalCid(ciphertextCid);
|
|
5990
|
+
if (!parsed.hash.startsWith("#k=")) {
|
|
5991
|
+
throw new TypeError("share URL is missing the #k= key fragment");
|
|
5992
|
+
}
|
|
5993
|
+
const key32 = fromBase64Url(parsed.hash.slice("#k=".length));
|
|
5994
|
+
if (key32.length !== KEY_LENGTH2) {
|
|
5995
|
+
throw new TypeError(`fragment key must be ${KEY_LENGTH2} bytes, got ${key32.length}`);
|
|
5996
|
+
}
|
|
5997
|
+
return { ciphertextCid, key32 };
|
|
5998
|
+
}
|
|
5885
5999
|
|
|
5886
6000
|
// src/artifact-core.ts
|
|
5887
6001
|
var ARTIFACT_ROOT = "artifacts";
|
|
@@ -5983,7 +6097,7 @@ function toShareErrorInfo(error) {
|
|
|
5983
6097
|
return { protocol: "tinycloud-share", version: SHARE_RESULT_VERSION, error: { code: "fetch-failed" } };
|
|
5984
6098
|
}
|
|
5985
6099
|
function metadataFor(envelope, origin) {
|
|
5986
|
-
const kind = envelope.recipientMatcher.kind === "recipientDid" ? "recipientDid" : envelope.recipientMatcher.kind === "exactEmail" ? "email" : "emailDomain";
|
|
6100
|
+
const kind = envelope.recipientMatcher.kind === "recipientDid" ? "recipientDid" : envelope.recipientMatcher.kind === "exactEmail" ? "email" : envelope.recipientMatcher.kind === "emailDomain" ? "emailDomain" : "bearer";
|
|
5987
6101
|
return {
|
|
5988
6102
|
protocol: "tinycloud-share",
|
|
5989
6103
|
version: 1,
|
|
@@ -5998,7 +6112,7 @@ function metadataFor(envelope, origin) {
|
|
|
5998
6112
|
...envelope.display.filename === void 0 ? {} : { filename: envelope.display.filename },
|
|
5999
6113
|
...envelope.display.mode === void 0 ? {} : { mode: envelope.display.mode }
|
|
6000
6114
|
},
|
|
6001
|
-
...envelope.
|
|
6115
|
+
...envelope.version === 3 && envelope.artifactPin !== void 0 ? { artifact: { id: envelope.artifactPin.artifactId, version: envelope.artifactPin.version } } : {}
|
|
6002
6116
|
};
|
|
6003
6117
|
}
|
|
6004
6118
|
function receiveErrorFor(error) {
|
|
@@ -6096,14 +6210,165 @@ async function resolvePolicyShare(link, options) {
|
|
|
6096
6210
|
options.onResolvedAddressedEnvelope?.(envelope, cid2);
|
|
6097
6211
|
return { envelope, origin: url.origin, cid: cid2 };
|
|
6098
6212
|
}
|
|
6213
|
+
async function resolveLegacyV2(link, options) {
|
|
6214
|
+
let parsed;
|
|
6215
|
+
try {
|
|
6216
|
+
parsed = parseShareUrl(link, { ...options.expectedOrigin === void 0 ? {} : { expectedOrigin: options.expectedOrigin } });
|
|
6217
|
+
} catch {
|
|
6218
|
+
throw new ShareReceiveError("invalid-link", "legacy share link format is invalid");
|
|
6219
|
+
}
|
|
6220
|
+
try {
|
|
6221
|
+
options.signal?.throwIfAborted();
|
|
6222
|
+
const origin = new URL(link).origin;
|
|
6223
|
+
const limit = options.maxSealedBlobBytes ?? DEFAULT_MAX_SEALED_BLOB_BYTES;
|
|
6224
|
+
if (!Number.isSafeInteger(limit) || limit <= 0) throw new ShareReceiveError("max-bytes-exceeded", "sealed envelope limit is invalid");
|
|
6225
|
+
let sealed;
|
|
6226
|
+
if (options.fetchBlob !== void 0) {
|
|
6227
|
+
try {
|
|
6228
|
+
sealed = await options.fetchBlob({ origin, cid: parsed.ciphertextCid, ...options.signal === void 0 ? {} : { signal: options.signal } });
|
|
6229
|
+
} catch {
|
|
6230
|
+
throw new ShareReceiveError("fetch-failed", "legacy registry unavailable");
|
|
6231
|
+
}
|
|
6232
|
+
} else {
|
|
6233
|
+
if (options.registryBaseUrl === void 0) throw new ShareReceiveError("fetch-failed", "legacy registry is required");
|
|
6234
|
+
const response = await (options.fetch ?? globalThis.fetch)(`${options.registryBaseUrl.replace(/\/+$/, "")}/ipfs/${parsed.ciphertextCid}?format=raw`, {
|
|
6235
|
+
headers: { accept: "application/vnd.ipld.raw" },
|
|
6236
|
+
redirect: "error",
|
|
6237
|
+
...options.signal === void 0 ? {} : { signal: options.signal }
|
|
6238
|
+
});
|
|
6239
|
+
if (!response.ok) throw new ShareReceiveError("fetch-failed", "legacy registry unavailable");
|
|
6240
|
+
if (Number(response.headers.get("content-length")) > limit) throw new ShareReceiveError("max-bytes-exceeded", "sealed envelope exceeds the size limit");
|
|
6241
|
+
const reader = response.body?.getReader();
|
|
6242
|
+
if (reader === void 0) throw new ShareReceiveError("fetch-failed", "legacy registry returned no body");
|
|
6243
|
+
const chunks = [];
|
|
6244
|
+
let length3 = 0;
|
|
6245
|
+
for (; ; ) {
|
|
6246
|
+
const part = await reader.read();
|
|
6247
|
+
if (part.done) break;
|
|
6248
|
+
length3 += part.value.length;
|
|
6249
|
+
if (length3 > limit) {
|
|
6250
|
+
await reader.cancel();
|
|
6251
|
+
throw new ShareReceiveError("max-bytes-exceeded", "sealed envelope exceeds the size limit");
|
|
6252
|
+
}
|
|
6253
|
+
chunks.push(part.value);
|
|
6254
|
+
}
|
|
6255
|
+
sealed = new Uint8Array(length3);
|
|
6256
|
+
let offset = 0;
|
|
6257
|
+
for (const chunk of chunks) {
|
|
6258
|
+
sealed.set(chunk, offset);
|
|
6259
|
+
offset += chunk.length;
|
|
6260
|
+
}
|
|
6261
|
+
}
|
|
6262
|
+
options.signal?.throwIfAborted();
|
|
6263
|
+
if (!(sealed instanceof Uint8Array) || sealed.length > limit) throw new ShareReceiveError("max-bytes-exceeded", "sealed envelope exceeds the size limit");
|
|
6264
|
+
if (await computeCid(sealed) !== parsed.ciphertextCid) throw new ShareReceiveError("cid-mismatch", "legacy envelope CID does not match");
|
|
6265
|
+
let plaintext;
|
|
6266
|
+
try {
|
|
6267
|
+
plaintext = await open(sealed, parsed.key32);
|
|
6268
|
+
} catch {
|
|
6269
|
+
throw new ShareReceiveError("decrypt-failed", "legacy envelope could not be opened");
|
|
6270
|
+
}
|
|
6271
|
+
let envelope;
|
|
6272
|
+
try {
|
|
6273
|
+
const encoded = new TextDecoder("utf-8", { fatal: true }).decode(plaintext);
|
|
6274
|
+
const value = JSON.parse(encoded);
|
|
6275
|
+
if (canonicalize(value) !== encoded) throw new Error("non-canonical envelope");
|
|
6276
|
+
envelope = shareEnvelopeV2Schema.parse(value);
|
|
6277
|
+
} catch {
|
|
6278
|
+
throw new ShareReceiveError("envelope-invalid", "legacy envelope is invalid");
|
|
6279
|
+
}
|
|
6280
|
+
return { envelope, origin, cid: parsed.ciphertextCid };
|
|
6281
|
+
} finally {
|
|
6282
|
+
parsed.key32.fill(0);
|
|
6283
|
+
}
|
|
6284
|
+
}
|
|
6285
|
+
function evidenceMatchesV2(envelope, evidence) {
|
|
6286
|
+
const actions = new Set(evidence.actions.map((action) => action === "read" ? "tinycloud.kv/get" : action === "list" ? "tinycloud.kv/list" : action === "edit" ? "tinycloud.kv/put" : action));
|
|
6287
|
+
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}/`);
|
|
6288
|
+
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);
|
|
6289
|
+
}
|
|
6290
|
+
async function verifyLegacyV2(envelope, options) {
|
|
6291
|
+
try {
|
|
6292
|
+
if (!await verifyEnvelopeV2(envelope, { expectedSignerDid: envelope.signature.signerDid })) throw new Error("signature");
|
|
6293
|
+
} catch {
|
|
6294
|
+
throw new ShareReceiveError("signature-invalid", "legacy envelope signature is invalid");
|
|
6295
|
+
}
|
|
6296
|
+
const expiry = Date.parse(envelope.expiry);
|
|
6297
|
+
if (!Number.isFinite(expiry)) throw new ShareReceiveError("envelope-invalid", "legacy share expiry is invalid");
|
|
6298
|
+
if (expiry <= (options.now?.() ?? Date.now())) throw new ShareReceiveError("expired", "legacy share has expired", { expiresAt: envelope.expiry });
|
|
6299
|
+
if (envelope.authorizationTarget.kind === "policy") {
|
|
6300
|
+
if (options.trustedPolicyAuthority === void 0) throw new ShareReceiveError("envelope-invalid", "a legacy policy authority is required");
|
|
6301
|
+
let evidence;
|
|
6302
|
+
try {
|
|
6303
|
+
evidence = await options.trustedPolicyAuthority.resolve({ policyCid: envelope.authorizationTarget.policyCid, envelope });
|
|
6304
|
+
} catch {
|
|
6305
|
+
}
|
|
6306
|
+
if (evidence === void 0 || !evidenceMatchesV2(envelope, evidence)) throw new ShareReceiveError("capability-invalid", "legacy policy evidence does not match");
|
|
6307
|
+
if (options.trustedSignerDid !== void 0 && options.trustedSignerDid !== evidence.signerDid) throw new ShareReceiveError("signature-invalid", "legacy signer is not trusted");
|
|
6308
|
+
} 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) {
|
|
6309
|
+
throw new ShareReceiveError("capability-invalid", "legacy recipient authority does not match");
|
|
6310
|
+
}
|
|
6311
|
+
}
|
|
6099
6312
|
async function inspectShare(link, options = {}) {
|
|
6313
|
+
if (isLegacyV2Link(link)) {
|
|
6314
|
+
const legacy = await resolveLegacyV2(link, options);
|
|
6315
|
+
await verifyLegacyV2(legacy.envelope, options);
|
|
6316
|
+
options.onResolvedAddressedEnvelope?.(legacy.envelope, legacy.cid);
|
|
6317
|
+
return { metadata: metadataFor(legacy.envelope, legacy.origin), link: { origin: legacy.origin, cid: legacy.cid, kind: "compact" } };
|
|
6318
|
+
}
|
|
6100
6319
|
const resolved = await resolvePolicyShare(link, options);
|
|
6101
6320
|
return { metadata: metadataFor(resolved.envelope, resolved.origin), link: { origin: resolved.origin, cid: resolved.cid, kind: "policy" } };
|
|
6102
6321
|
}
|
|
6103
6322
|
async function receiveShare(link, options = {}) {
|
|
6323
|
+
if (isLegacyV2Link(link)) return receiveLegacyV2(link, options);
|
|
6104
6324
|
const resolved = await resolvePolicyShare(link, options);
|
|
6105
6325
|
return { state: "authorization-required", method: resolved.envelope.recipientMatcher.kind === "recipientDid" ? "openkey-device" : "email-claim" };
|
|
6106
6326
|
}
|
|
6327
|
+
function isLegacyV2Link(link) {
|
|
6328
|
+
try {
|
|
6329
|
+
const url = new URL(link);
|
|
6330
|
+
return /^\/s\/bafkrei[a-z2-7]{52}$/.test(url.pathname) && url.hash.startsWith("#k=");
|
|
6331
|
+
} catch {
|
|
6332
|
+
return false;
|
|
6333
|
+
}
|
|
6334
|
+
}
|
|
6335
|
+
async function receiveLegacyV2(link, options) {
|
|
6336
|
+
const resolved = await resolveLegacyV2(link, options);
|
|
6337
|
+
const { envelope } = resolved;
|
|
6338
|
+
await verifyLegacyV2(envelope, options);
|
|
6339
|
+
options.onResolvedAddressedEnvelope?.(envelope, resolved.cid);
|
|
6340
|
+
const method = envelope.recipientMatcher.kind === "recipientDid" ? "openkey-device" : "email-claim";
|
|
6341
|
+
if (options.authorization === void 0) return { state: "authorization-required", method };
|
|
6342
|
+
const result = options.authorizationResumeToken === void 0 ? await options.authorization.begin({ envelope, method }) : await options.authorization.resume({
|
|
6343
|
+
envelope,
|
|
6344
|
+
method,
|
|
6345
|
+
resumeToken: options.authorizationResumeToken,
|
|
6346
|
+
...options.authorizationProof === void 0 ? {} : { proof: options.authorizationProof }
|
|
6347
|
+
});
|
|
6348
|
+
if (result.state === "authorization-required") return result;
|
|
6349
|
+
if (result.state === "denied") throw new ShareReceiveError("authorization-denied", "legacy share authorization was denied");
|
|
6350
|
+
if (result.value === null || typeof result.value !== "object" || !(result.value.bytes instanceof Uint8Array) || result.value.proof === void 0 || options.authorization.verifyResult === void 0) {
|
|
6351
|
+
throw new ShareReceiveError("content-integrity-failed", "legacy content lacks a verifiable proof");
|
|
6352
|
+
}
|
|
6353
|
+
const authorized = result.value;
|
|
6354
|
+
try {
|
|
6355
|
+
if (!await options.authorization.verifyResult({ envelope, value: authorized, proof: authorized.proof })) throw new Error("proof");
|
|
6356
|
+
} catch {
|
|
6357
|
+
throw new ShareReceiveError("content-integrity-failed", "legacy content proof is invalid");
|
|
6358
|
+
}
|
|
6359
|
+
const bytes3 = authorized.bytes.slice();
|
|
6360
|
+
if (bytes3.length > (options.maxContentBlobBytes ?? DEFAULT_MAX_CONTENT_BLOB_BYTES)) throw new ShareReceiveError("max-bytes-exceeded", "legacy content exceeds the size limit");
|
|
6361
|
+
const bodyDigest = toBase64Url(new Uint8Array(await crypto.subtle.digest("SHA-256", bytes3)));
|
|
6362
|
+
if (authorized.bodyDigest !== bodyDigest || authorized.contentSourceDigest !== envelope.contentSourceDigest || authorized.binding.shareId !== envelope.shareId || authorized.binding.delegationCid !== envelope.delegationCid || authorized.binding.authorityMaterialHandle !== envelope.authorityMaterialHandle || authorized.binding.authorityMaterialDigest !== envelope.authorityMaterialDigest || authorized.binding.resource.kind !== envelope.resource.kind || authorized.binding.resource.path !== envelope.resource.path || authorized.binding.action !== void 0 && !envelope.actions.some((action) => action === authorized.binding.action || `tinycloud.kv/${action}` === authorized.binding.action) || envelope.metadata.byteLength !== void 0 && envelope.metadata.byteLength !== bytes3.length) {
|
|
6363
|
+
throw new ShareReceiveError("content-integrity-failed", "legacy content binding does not match");
|
|
6364
|
+
}
|
|
6365
|
+
let text;
|
|
6366
|
+
try {
|
|
6367
|
+
text = new TextDecoder("utf-8", { fatal: true }).decode(bytes3);
|
|
6368
|
+
} catch {
|
|
6369
|
+
}
|
|
6370
|
+
return { metadata: metadataFor(envelope, resolved.origin), link: { origin: resolved.origin, cid: resolved.cid, kind: "compact" }, bytes: bytes3, ...text === void 0 ? {} : { text } };
|
|
6371
|
+
}
|
|
6107
6372
|
|
|
6108
6373
|
// src/publish.ts
|
|
6109
6374
|
var SHARE_CONTENT_LIMIT = 100 * 1024 * 1024;
|
|
@@ -6638,7 +6903,10 @@ async function publishTargetShare(input) {
|
|
|
6638
6903
|
expiresAt,
|
|
6639
6904
|
expiryWasExplicit: input.expiryWasExplicit ?? input.expiresAt !== void 0,
|
|
6640
6905
|
origin: input.origin,
|
|
6641
|
-
...input.notify === void 0 ? {} : { notify: input.notify }
|
|
6906
|
+
...input.notify === void 0 ? {} : { notify: input.notify },
|
|
6907
|
+
...input.credentialDescriptor === void 0 ? {} : { credentialDescriptor: input.credentialDescriptor },
|
|
6908
|
+
...input.issuerDid === void 0 ? {} : { issuerDid: input.issuerDid },
|
|
6909
|
+
...input.issuerKid === void 0 ? {} : { issuerKid: input.issuerKid }
|
|
6642
6910
|
});
|
|
6643
6911
|
}
|
|
6644
6912
|
|
|
@@ -6726,7 +6994,7 @@ async function createPolicy(options, capabilities) {
|
|
|
6726
6994
|
function addressedPlan(options) {
|
|
6727
6995
|
const capabilities = sortCanonical([
|
|
6728
6996
|
{ kind: "kv", resource: options.contentSource.kvResource, selector: options.resource.kind, actions: [...options.policyActions] },
|
|
6729
|
-
{ kind: "encryption", resource: options.contentSource.encryptionNetwork
|
|
6997
|
+
{ kind: "encryption", resource: options.contentSource.encryptionKeyPath === void 0 ? options.contentSource.encryptionNetwork : `${options.contentSource.encryptionNetwork}/${options.contentSource.encryptionKeyPath}`, action: "tinycloud.encryption/decrypt" }
|
|
6730
6998
|
]);
|
|
6731
6999
|
return {
|
|
6732
7000
|
expiry: rfc3339Seconds(options.expiresAt),
|
|
@@ -7056,6 +7324,7 @@ async function addressedShareBodyUpperBound(input) {
|
|
|
7056
7324
|
kvResource: `${input.spaceId}/kv/${path}`,
|
|
7057
7325
|
selector: input.resource.kind,
|
|
7058
7326
|
encryptionNetwork: input.encryptionNetwork,
|
|
7327
|
+
...input.encryptionKeyPath === void 0 ? {} : { encryptionKeyPath: input.encryptionKeyPath },
|
|
7059
7328
|
encryptedSymmetricKeyDigestHex: "f".repeat(64),
|
|
7060
7329
|
keyVersion: Number.MAX_SAFE_INTEGER,
|
|
7061
7330
|
mode: "immutable",
|
|
@@ -8441,41 +8710,18 @@ function bytes2(value, label) {
|
|
|
8441
8710
|
if (decoded.length !== 64 || toBase64Url(decoded) !== value) throw new Error(`${label} is invalid`);
|
|
8442
8711
|
return decoded;
|
|
8443
8712
|
}
|
|
8444
|
-
var BASE64_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
|
8445
8713
|
function toBase64(bytes3) {
|
|
8446
|
-
|
|
8447
|
-
for (let i = 0; i < bytes3.length; i += 3) {
|
|
8448
|
-
const b0 = bytes3[i];
|
|
8449
|
-
const b1 = i + 1 < bytes3.length ? bytes3[i + 1] : 0;
|
|
8450
|
-
const b2 = i + 2 < bytes3.length ? bytes3[i + 2] : 0;
|
|
8451
|
-
out += BASE64_ALPHABET[b0 >> 2 & 63];
|
|
8452
|
-
out += BASE64_ALPHABET[(b0 << 4 | b1 >> 4) & 63];
|
|
8453
|
-
out += i + 1 < bytes3.length ? BASE64_ALPHABET[(b1 << 2 | b2 >> 6) & 63] : "=";
|
|
8454
|
-
out += i + 2 < bytes3.length ? BASE64_ALPHABET[b2 & 63] : "=";
|
|
8455
|
-
}
|
|
8456
|
-
return out;
|
|
8714
|
+
return toBase64Url(bytes3).replace(/-/g, "+").replace(/_/g, "/").padEnd(Math.ceil(bytes3.length / 3) * 4, "=");
|
|
8457
8715
|
}
|
|
8458
8716
|
function fromBase64(value, label) {
|
|
8459
8717
|
if (!/^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/.test(value)) {
|
|
8460
8718
|
throw new Error(`${label} is invalid`);
|
|
8461
8719
|
}
|
|
8462
|
-
|
|
8463
|
-
|
|
8464
|
-
|
|
8465
|
-
|
|
8466
|
-
|
|
8467
|
-
const v1 = BASE64_ALPHABET.indexOf(value[i + 1]);
|
|
8468
|
-
const v2 = value[i + 2] === "=" ? 0 : BASE64_ALPHABET.indexOf(value[i + 2]);
|
|
8469
|
-
const v3 = value[i + 3] === "=" ? 0 : BASE64_ALPHABET.indexOf(value[i + 3]);
|
|
8470
|
-
const b0 = v0 << 2 | v1 >> 4;
|
|
8471
|
-
const b1 = (v1 & 15) << 4 | v2 >> 2;
|
|
8472
|
-
const b2 = (v2 & 3) << 6 | v3;
|
|
8473
|
-
if (outIdx < out.length) out[outIdx++] = b0;
|
|
8474
|
-
if (outIdx < out.length) out[outIdx++] = b1;
|
|
8475
|
-
if (outIdx < out.length) out[outIdx++] = b2;
|
|
8476
|
-
}
|
|
8477
|
-
if (toBase64(out) !== value) throw new Error(`${label} is invalid`);
|
|
8478
|
-
return out;
|
|
8720
|
+
try {
|
|
8721
|
+
return fromBase64Url(value.replace(/=+$/, "").replace(/\+/g, "-").replace(/\//g, "_"));
|
|
8722
|
+
} catch {
|
|
8723
|
+
throw new Error(`${label} is invalid`);
|
|
8724
|
+
}
|
|
8479
8725
|
}
|
|
8480
8726
|
async function digestBytes2(value) {
|
|
8481
8727
|
return toBase64Url(new Uint8Array(await crypto.subtle.digest("SHA-256", value)));
|
|
@@ -8592,16 +8838,16 @@ function matchesInitialCiphertextDigest(bytes3, expected) {
|
|
|
8592
8838
|
function canonicalHashHex(value) {
|
|
8593
8839
|
return hex3(sha2566(new TextEncoder().encode(canonicalize(value))));
|
|
8594
8840
|
}
|
|
8595
|
-
async function aesGcmDecrypt(key, blob) {
|
|
8841
|
+
async function aesGcmDecrypt(key, blob, aad) {
|
|
8596
8842
|
if (key.length !== 32 || blob.length < 28) throw new Error("encrypted content is malformed");
|
|
8597
8843
|
const cryptoKey = await crypto.subtle.importKey("raw", key, "AES-GCM", false, ["decrypt"]);
|
|
8598
|
-
return new Uint8Array(await crypto.subtle.decrypt({ name: "AES-GCM", iv: blob.slice(0, 12) }, cryptoKey, blob.slice(12)));
|
|
8844
|
+
return new Uint8Array(await crypto.subtle.decrypt({ name: "AES-GCM", iv: blob.slice(0, 12), ...aad && { additionalData: aad } }, cryptoKey, blob.slice(12)));
|
|
8599
8845
|
}
|
|
8600
|
-
async function aesGcmEncrypt(key, plaintext) {
|
|
8846
|
+
async function aesGcmEncrypt(key, plaintext, aad) {
|
|
8601
8847
|
if (key.length !== 32) throw new Error("encrypted content key is malformed");
|
|
8602
8848
|
const nonce = crypto.getRandomValues(new Uint8Array(12));
|
|
8603
8849
|
const cryptoKey = await crypto.subtle.importKey("raw", key, "AES-GCM", false, ["encrypt"]);
|
|
8604
|
-
const ciphertext = new Uint8Array(await crypto.subtle.encrypt({ name: "AES-GCM", iv: nonce }, cryptoKey, plaintext));
|
|
8850
|
+
const ciphertext = new Uint8Array(await crypto.subtle.encrypt({ name: "AES-GCM", iv: nonce, ...aad && { additionalData: aad } }, cryptoKey, plaintext));
|
|
8605
8851
|
const output = new Uint8Array(nonce.length + ciphertext.length);
|
|
8606
8852
|
output.set(nonce);
|
|
8607
8853
|
output.set(ciphertext, nonce.length);
|
|
@@ -8616,8 +8862,9 @@ function parseV3InlineEncryptedEnvelope(bytes3, expected) {
|
|
|
8616
8862
|
}
|
|
8617
8863
|
if (value === null || typeof value !== "object" || Array.isArray(value)) throw new Error("encrypted content envelope is malformed");
|
|
8618
8864
|
const record = value;
|
|
8619
|
-
const allowed = ["v", "networkId", "alg", "keyVersion", "encryptedSymmetricKey", "encryptedSymmetricKeyHash", "ciphertext", "metadata"];
|
|
8620
|
-
|
|
8865
|
+
const allowed = ["v", "networkId", "keyPath", "alg", "keyVersion", "encryptedSymmetricKey", "encryptedSymmetricKeyHash", "ciphertext", "aad", "metadata"];
|
|
8866
|
+
const source = expected.contentSource;
|
|
8867
|
+
if (Object.keys(record).some((key) => !allowed.includes(key)) || record.v !== (source.encryptionKeyPath === void 0 ? 1 : 2) || record.networkId !== source.encryptionNetwork || record.networkId !== expected.encryptionNetwork || record.keyPath !== source.encryptionKeyPath || record.keyPath !== void 0 && record.keyPath !== `share/${expected.shareId}` || record.alg !== "x25519-aes256gcm/v1" || record.keyVersion !== source.keyVersion || typeof record.encryptedSymmetricKey !== "string" || typeof record.encryptedSymmetricKeyHash !== "string" || hex3(sha2566(fromBase64(record.encryptedSymmetricKey, "v3 encrypted symmetric key"))) !== source.encryptedSymmetricKeyDigestHex || record.encryptedSymmetricKeyHash !== canonicalHashHex(record.encryptedSymmetricKey) || typeof record.ciphertext !== "string" || record.aad !== void 0 && typeof record.aad !== "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"))) {
|
|
8621
8868
|
throw new Error("encrypted content envelope binding is invalid");
|
|
8622
8869
|
}
|
|
8623
8870
|
return record;
|
|
@@ -8738,7 +8985,7 @@ var ShareRecipientClient = class {
|
|
|
8738
8985
|
this.session = { sessionId: delegation.sessionCid, expiresAt: new Date(compact.payload.exp * 1e3).toISOString(), actions: envelope.actions, resource: envelope.resource };
|
|
8739
8986
|
return this.session;
|
|
8740
8987
|
}
|
|
8741
|
-
/** Decrypt
|
|
8988
|
+
/** Decrypt unscoped v1 or path-bound v2 content, independently checking canonical and registered raw-key digests. */
|
|
8742
8989
|
async decryptV3Content(bytes3) {
|
|
8743
8990
|
const envelope = this.options.envelope;
|
|
8744
8991
|
const signer = this.nativeSigner ?? this.signer;
|
|
@@ -8746,18 +8993,22 @@ var ShareRecipientClient = class {
|
|
|
8746
8993
|
assertArtifactBinding(artifactBindingView(envelope));
|
|
8747
8994
|
if (!matchesInitialCiphertextDigest(bytes3, envelope.contentSource.initialCiphertextDigestHex)) throw new Error("v3 owner KV ciphertext digest mismatch");
|
|
8748
8995
|
const encrypted = parseV3InlineEncryptedEnvelope(bytes3, envelope);
|
|
8996
|
+
const ciphertext = fromBase64(encrypted.ciphertext, "v3 encrypted content");
|
|
8997
|
+
if (ciphertext.length < 28) throw new Error("encrypted content is malformed");
|
|
8998
|
+
const aad = encrypted.aad === void 0 ? void 0 : fromBase64(encrypted.aad, "v3 encrypted content AAD");
|
|
8749
8999
|
const receiverPrivateKey = crypto.getRandomValues(new Uint8Array(32));
|
|
8750
9000
|
const receiverPublicKey = toBase64(x25519.getPublicKey(receiverPrivateKey));
|
|
8751
9001
|
const receiverPublicKeyHash = canonicalHashHex(receiverPublicKey);
|
|
8752
|
-
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 };
|
|
9002
|
+
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 };
|
|
8753
9003
|
const bodyHash = hex3(sha2566(new TextEncoder().encode(canonicalize(body))));
|
|
8754
9004
|
const session = verifyCompactUcanAuthorization(this.v3Authorization, this.session.sessionId);
|
|
8755
9005
|
const now = Math.floor(Date.now() / 1e3);
|
|
9006
|
+
const decryptResource = encrypted.keyPath === void 0 ? encrypted.networkId : `${encrypted.networkId}/${encrypted.keyPath}`;
|
|
8756
9007
|
const invocation = await signCompactUcanAuthorization({
|
|
8757
9008
|
issuerDid: this.options.holderDid,
|
|
8758
9009
|
audienceDid: this.v3NodeAudience,
|
|
8759
|
-
attenuation: { [
|
|
8760
|
-
facts: [{ type: body.type, targetNode: body.targetNode, networkId: body.networkId, bodyHash, encryptedSymmetricKeyHash: body.encryptedSymmetricKeyHash, receiverPublicKeyHash, alg: body.alg, keyVersion: body.keyVersion }],
|
|
9010
|
+
attenuation: { [decryptResource]: { "tinycloud.encryption/decrypt": [{}] } },
|
|
9011
|
+
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 }],
|
|
8761
9012
|
proofs: [session.cid],
|
|
8762
9013
|
notBefore: now,
|
|
8763
9014
|
expiresAt: Math.min(now + 60, session.payload.exp),
|
|
@@ -8781,7 +9032,7 @@ var ShareRecipientClient = class {
|
|
|
8781
9032
|
const symmetricKey = await aesGcmDecrypt(shared, wrapped.slice(33));
|
|
8782
9033
|
shared.fill(0);
|
|
8783
9034
|
if (symmetricKey.length !== 32) throw new Error("v3 content key is malformed");
|
|
8784
|
-
const plaintext = await aesGcmDecrypt(symmetricKey,
|
|
9035
|
+
const plaintext = await aesGcmDecrypt(symmetricKey, ciphertext, aad);
|
|
8785
9036
|
this.v3ContentKey?.fill(0);
|
|
8786
9037
|
this.v3ContentKey = symmetricKey;
|
|
8787
9038
|
this.v3ContentEnvelope = encrypted;
|
|
@@ -8793,7 +9044,9 @@ var ShareRecipientClient = class {
|
|
|
8793
9044
|
/** Re-encrypt edited v3 content with the admitted content key. */
|
|
8794
9045
|
async encryptV3Content(bytes3, mediaType) {
|
|
8795
9046
|
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");
|
|
8796
|
-
|
|
9047
|
+
const envelope = this.v3ContentEnvelope;
|
|
9048
|
+
const aad = envelope.aad === void 0 ? void 0 : fromBase64(envelope.aad, "v3 encrypted content AAD");
|
|
9049
|
+
return new TextEncoder().encode(canonicalize({ ...envelope, ciphertext: toBase64(await aesGcmEncrypt(this.v3ContentKey, bytes3, aad)), metadata: { ...envelope.metadata ?? {}, contentType: mediaType } }));
|
|
8797
9050
|
}
|
|
8798
9051
|
async resumeWithProof(envelope, resumeToken, proof) {
|
|
8799
9052
|
void envelope;
|