@tinycloud/share-sdk 1.4.1-beta.1 → 1.4.1-beta.3

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.
@@ -226,7 +226,7 @@ declare class ShareRecipientClient {
226
226
  authorize(envelope: ShareEnvelopeV2): Promise<ShareAuthorizedContent>;
227
227
  establishPolicySession(): Promise<SharePolicySession>;
228
228
  private establishV3;
229
- /** Decrypt a v3 KV value through a fresh recipient-signed decrypt invocation. */
229
+ /** Decrypt unscoped v1 or path-bound v2 content, independently checking canonical and registered raw-key digests. */
230
230
  decryptV3Content(bytes: Uint8Array): Promise<{
231
231
  readonly bytes: Uint8Array;
232
232
  readonly mediaType: string;
@@ -226,7 +226,7 @@ declare class ShareRecipientClient {
226
226
  authorize(envelope: ShareEnvelopeV2): Promise<ShareAuthorizedContent>;
227
227
  establishPolicySession(): Promise<SharePolicySession>;
228
228
  private establishV3;
229
- /** Decrypt a v3 KV value through a fresh recipient-signed decrypt invocation. */
229
+ /** Decrypt unscoped v1 or path-bound v2 content, independently checking canonical and registered raw-key digests. */
230
230
  decryptV3Content(bytes: Uint8Array): Promise<{
231
231
  readonly bytes: Uint8Array;
232
232
  readonly mediaType: string;
@@ -6127,41 +6127,18 @@ function object2(value, label) {
6127
6127
  if (typeof value !== "object" || value === null || Array.isArray(value)) throw new Error(`${label} is invalid`);
6128
6128
  return value;
6129
6129
  }
6130
- var BASE64_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
6131
6130
  function toBase64(bytes) {
6132
- let out = "";
6133
- for (let i = 0; i < bytes.length; i += 3) {
6134
- const b0 = bytes[i];
6135
- const b1 = i + 1 < bytes.length ? bytes[i + 1] : 0;
6136
- const b2 = i + 2 < bytes.length ? bytes[i + 2] : 0;
6137
- out += BASE64_ALPHABET[b0 >> 2 & 63];
6138
- out += BASE64_ALPHABET[(b0 << 4 | b1 >> 4) & 63];
6139
- out += i + 1 < bytes.length ? BASE64_ALPHABET[(b1 << 2 | b2 >> 6) & 63] : "=";
6140
- out += i + 2 < bytes.length ? BASE64_ALPHABET[b2 & 63] : "=";
6141
- }
6142
- return out;
6131
+ return toBase64Url(bytes).replace(/-/g, "+").replace(/_/g, "/").padEnd(Math.ceil(bytes.length / 3) * 4, "=");
6143
6132
  }
6144
6133
  function fromBase64(value, label) {
6145
6134
  if (!/^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/.test(value)) {
6146
6135
  throw new Error(`${label} is invalid`);
6147
6136
  }
6148
- const padding = value.endsWith("==") ? 2 : value.endsWith("=") ? 1 : 0;
6149
- const out = new Uint8Array(value.length / 4 * 3 - padding);
6150
- let outIdx = 0;
6151
- for (let i = 0; i < value.length; i += 4) {
6152
- const v0 = BASE64_ALPHABET.indexOf(value[i]);
6153
- const v1 = BASE64_ALPHABET.indexOf(value[i + 1]);
6154
- const v2 = value[i + 2] === "=" ? 0 : BASE64_ALPHABET.indexOf(value[i + 2]);
6155
- const v3 = value[i + 3] === "=" ? 0 : BASE64_ALPHABET.indexOf(value[i + 3]);
6156
- const b0 = v0 << 2 | v1 >> 4;
6157
- const b1 = (v1 & 15) << 4 | v2 >> 2;
6158
- const b2 = (v2 & 3) << 6 | v3;
6159
- if (outIdx < out.length) out[outIdx++] = b0;
6160
- if (outIdx < out.length) out[outIdx++] = b1;
6161
- if (outIdx < out.length) out[outIdx++] = b2;
6162
- }
6163
- if (toBase64(out) !== value) throw new Error(`${label} is invalid`);
6164
- return out;
6137
+ try {
6138
+ return fromBase64Url(value.replace(/=+$/, "").replace(/\+/g, "-").replace(/\//g, "_"));
6139
+ } catch {
6140
+ throw new Error(`${label} is invalid`);
6141
+ }
6165
6142
  }
6166
6143
  async function digestBytes(value) {
6167
6144
  return toBase64Url(new Uint8Array(await crypto.subtle.digest("SHA-256", value)));
@@ -6272,16 +6249,16 @@ function matchesInitialCiphertextDigest(bytes, expected) {
6272
6249
  function canonicalHashHex(value) {
6273
6250
  return hex2((0, import_sha2562.sha256)(new TextEncoder().encode(canonicalize(value))));
6274
6251
  }
6275
- async function aesGcmDecrypt(key, blob) {
6252
+ async function aesGcmDecrypt(key, blob, aad) {
6276
6253
  if (key.length !== 32 || blob.length < 28) throw new Error("encrypted content is malformed");
6277
6254
  const cryptoKey = await crypto.subtle.importKey("raw", key, "AES-GCM", false, ["decrypt"]);
6278
- return new Uint8Array(await crypto.subtle.decrypt({ name: "AES-GCM", iv: blob.slice(0, 12) }, cryptoKey, blob.slice(12)));
6255
+ return new Uint8Array(await crypto.subtle.decrypt({ name: "AES-GCM", iv: blob.slice(0, 12), ...aad && { additionalData: aad } }, cryptoKey, blob.slice(12)));
6279
6256
  }
6280
- async function aesGcmEncrypt(key, plaintext) {
6257
+ async function aesGcmEncrypt(key, plaintext, aad) {
6281
6258
  if (key.length !== 32) throw new Error("encrypted content key is malformed");
6282
6259
  const nonce = crypto.getRandomValues(new Uint8Array(12));
6283
6260
  const cryptoKey = await crypto.subtle.importKey("raw", key, "AES-GCM", false, ["encrypt"]);
6284
- const ciphertext = new Uint8Array(await crypto.subtle.encrypt({ name: "AES-GCM", iv: nonce }, cryptoKey, plaintext));
6261
+ const ciphertext = new Uint8Array(await crypto.subtle.encrypt({ name: "AES-GCM", iv: nonce, ...aad && { additionalData: aad } }, cryptoKey, plaintext));
6285
6262
  const output = new Uint8Array(nonce.length + ciphertext.length);
6286
6263
  output.set(nonce);
6287
6264
  output.set(ciphertext, nonce.length);
@@ -6296,8 +6273,9 @@ function parseV3InlineEncryptedEnvelope(bytes, expected) {
6296
6273
  }
6297
6274
  if (value === null || typeof value !== "object" || Array.isArray(value)) throw new Error("encrypted content envelope is malformed");
6298
6275
  const record = value;
6299
- const allowed = ["v", "networkId", "keyPath", "alg", "keyVersion", "encryptedSymmetricKey", "encryptedSymmetricKeyHash", "ciphertext", "metadata"];
6300
- 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"))) {
6276
+ const allowed = ["v", "networkId", "keyPath", "alg", "keyVersion", "encryptedSymmetricKey", "encryptedSymmetricKeyHash", "ciphertext", "aad", "metadata"];
6277
+ const source = expected.contentSource;
6278
+ 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" || hex2((0, import_sha2562.sha256)(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"))) {
6301
6279
  throw new Error("encrypted content envelope binding is invalid");
6302
6280
  }
6303
6281
  return record;
@@ -6418,7 +6396,7 @@ var ShareRecipientClient = class {
6418
6396
  this.session = { sessionId: delegation.sessionCid, expiresAt: new Date(compact.payload.exp * 1e3).toISOString(), actions: envelope.actions, resource: envelope.resource };
6419
6397
  return this.session;
6420
6398
  }
6421
- /** Decrypt a v3 KV value through a fresh recipient-signed decrypt invocation. */
6399
+ /** Decrypt unscoped v1 or path-bound v2 content, independently checking canonical and registered raw-key digests. */
6422
6400
  async decryptV3Content(bytes) {
6423
6401
  const envelope = this.options.envelope;
6424
6402
  const signer = this.nativeSigner ?? this.signer;
@@ -6426,6 +6404,9 @@ var ShareRecipientClient = class {
6426
6404
  assertArtifactBinding(artifactBindingView(envelope));
6427
6405
  if (!matchesInitialCiphertextDigest(bytes, envelope.contentSource.initialCiphertextDigestHex)) throw new Error("v3 owner KV ciphertext digest mismatch");
6428
6406
  const encrypted = parseV3InlineEncryptedEnvelope(bytes, envelope);
6407
+ const ciphertext = fromBase64(encrypted.ciphertext, "v3 encrypted content");
6408
+ if (ciphertext.length < 28) throw new Error("encrypted content is malformed");
6409
+ const aad = encrypted.aad === void 0 ? void 0 : fromBase64(encrypted.aad, "v3 encrypted content AAD");
6429
6410
  const receiverPrivateKey = crypto.getRandomValues(new Uint8Array(32));
6430
6411
  const receiverPublicKey = toBase64(import_ed255194.x25519.getPublicKey(receiverPrivateKey));
6431
6412
  const receiverPublicKeyHash = canonicalHashHex(receiverPublicKey);
@@ -6462,7 +6443,7 @@ var ShareRecipientClient = class {
6462
6443
  const symmetricKey = await aesGcmDecrypt(shared, wrapped.slice(33));
6463
6444
  shared.fill(0);
6464
6445
  if (symmetricKey.length !== 32) throw new Error("v3 content key is malformed");
6465
- const plaintext = await aesGcmDecrypt(symmetricKey, fromBase64(encrypted.ciphertext, "v3 encrypted content"));
6446
+ const plaintext = await aesGcmDecrypt(symmetricKey, ciphertext, aad);
6466
6447
  this.v3ContentKey?.fill(0);
6467
6448
  this.v3ContentKey = symmetricKey;
6468
6449
  this.v3ContentEnvelope = encrypted;
@@ -6474,7 +6455,9 @@ var ShareRecipientClient = class {
6474
6455
  /** Re-encrypt edited v3 content with the admitted content key. */
6475
6456
  async encryptV3Content(bytes, mediaType) {
6476
6457
  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");
6477
- return new TextEncoder().encode(canonicalize({ ...this.v3ContentEnvelope, ciphertext: toBase64Url(await aesGcmEncrypt(this.v3ContentKey, bytes)), metadata: { ...this.v3ContentEnvelope.metadata ?? {}, contentType: mediaType } }));
6458
+ const envelope = this.v3ContentEnvelope;
6459
+ const aad = envelope.aad === void 0 ? void 0 : fromBase64(envelope.aad, "v3 encrypted content AAD");
6460
+ return new TextEncoder().encode(canonicalize({ ...envelope, ciphertext: toBase64(await aesGcmEncrypt(this.v3ContentKey, bytes, aad)), metadata: { ...envelope.metadata ?? {}, contentType: mediaType } }));
6478
6461
  }
6479
6462
  async resumeWithProof(envelope, resumeToken, proof) {
6480
6463
  void envelope;