@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.
package/dist/index.cjs CHANGED
@@ -8812,41 +8812,18 @@ function bytes2(value, label) {
8812
8812
  if (decoded.length !== 64 || toBase64Url(decoded) !== value) throw new Error(`${label} is invalid`);
8813
8813
  return decoded;
8814
8814
  }
8815
- var BASE64_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
8816
8815
  function toBase64(bytes3) {
8817
- let out = "";
8818
- for (let i = 0; i < bytes3.length; i += 3) {
8819
- const b0 = bytes3[i];
8820
- const b1 = i + 1 < bytes3.length ? bytes3[i + 1] : 0;
8821
- const b2 = i + 2 < bytes3.length ? bytes3[i + 2] : 0;
8822
- out += BASE64_ALPHABET[b0 >> 2 & 63];
8823
- out += BASE64_ALPHABET[(b0 << 4 | b1 >> 4) & 63];
8824
- out += i + 1 < bytes3.length ? BASE64_ALPHABET[(b1 << 2 | b2 >> 6) & 63] : "=";
8825
- out += i + 2 < bytes3.length ? BASE64_ALPHABET[b2 & 63] : "=";
8826
- }
8827
- return out;
8816
+ return toBase64Url(bytes3).replace(/-/g, "+").replace(/_/g, "/").padEnd(Math.ceil(bytes3.length / 3) * 4, "=");
8828
8817
  }
8829
8818
  function fromBase64(value, label) {
8830
8819
  if (!/^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/.test(value)) {
8831
8820
  throw new Error(`${label} is invalid`);
8832
8821
  }
8833
- const padding = value.endsWith("==") ? 2 : value.endsWith("=") ? 1 : 0;
8834
- const out = new Uint8Array(value.length / 4 * 3 - padding);
8835
- let outIdx = 0;
8836
- for (let i = 0; i < value.length; i += 4) {
8837
- const v0 = BASE64_ALPHABET.indexOf(value[i]);
8838
- const v1 = BASE64_ALPHABET.indexOf(value[i + 1]);
8839
- const v2 = value[i + 2] === "=" ? 0 : BASE64_ALPHABET.indexOf(value[i + 2]);
8840
- const v3 = value[i + 3] === "=" ? 0 : BASE64_ALPHABET.indexOf(value[i + 3]);
8841
- const b0 = v0 << 2 | v1 >> 4;
8842
- const b1 = (v1 & 15) << 4 | v2 >> 2;
8843
- const b2 = (v2 & 3) << 6 | v3;
8844
- if (outIdx < out.length) out[outIdx++] = b0;
8845
- if (outIdx < out.length) out[outIdx++] = b1;
8846
- if (outIdx < out.length) out[outIdx++] = b2;
8847
- }
8848
- if (toBase64(out) !== value) throw new Error(`${label} is invalid`);
8849
- return out;
8822
+ try {
8823
+ return fromBase64Url(value.replace(/=+$/, "").replace(/\+/g, "-").replace(/\//g, "_"));
8824
+ } catch {
8825
+ throw new Error(`${label} is invalid`);
8826
+ }
8850
8827
  }
8851
8828
  async function digestBytes2(value) {
8852
8829
  return toBase64Url(new Uint8Array(await crypto.subtle.digest("SHA-256", value)));
@@ -8963,16 +8940,16 @@ function matchesInitialCiphertextDigest(bytes3, expected) {
8963
8940
  function canonicalHashHex(value) {
8964
8941
  return hex3((0, import_sha2565.sha256)(new TextEncoder().encode(canonicalize(value))));
8965
8942
  }
8966
- async function aesGcmDecrypt(key, blob) {
8943
+ async function aesGcmDecrypt(key, blob, aad) {
8967
8944
  if (key.length !== 32 || blob.length < 28) throw new Error("encrypted content is malformed");
8968
8945
  const cryptoKey = await crypto.subtle.importKey("raw", key, "AES-GCM", false, ["decrypt"]);
8969
- return new Uint8Array(await crypto.subtle.decrypt({ name: "AES-GCM", iv: blob.slice(0, 12) }, cryptoKey, blob.slice(12)));
8946
+ return new Uint8Array(await crypto.subtle.decrypt({ name: "AES-GCM", iv: blob.slice(0, 12), ...aad && { additionalData: aad } }, cryptoKey, blob.slice(12)));
8970
8947
  }
8971
- async function aesGcmEncrypt(key, plaintext) {
8948
+ async function aesGcmEncrypt(key, plaintext, aad) {
8972
8949
  if (key.length !== 32) throw new Error("encrypted content key is malformed");
8973
8950
  const nonce = crypto.getRandomValues(new Uint8Array(12));
8974
8951
  const cryptoKey = await crypto.subtle.importKey("raw", key, "AES-GCM", false, ["encrypt"]);
8975
- const ciphertext = new Uint8Array(await crypto.subtle.encrypt({ name: "AES-GCM", iv: nonce }, cryptoKey, plaintext));
8952
+ const ciphertext = new Uint8Array(await crypto.subtle.encrypt({ name: "AES-GCM", iv: nonce, ...aad && { additionalData: aad } }, cryptoKey, plaintext));
8976
8953
  const output = new Uint8Array(nonce.length + ciphertext.length);
8977
8954
  output.set(nonce);
8978
8955
  output.set(ciphertext, nonce.length);
@@ -8987,8 +8964,9 @@ function parseV3InlineEncryptedEnvelope(bytes3, expected) {
8987
8964
  }
8988
8965
  if (value === null || typeof value !== "object" || Array.isArray(value)) throw new Error("encrypted content envelope is malformed");
8989
8966
  const record = value;
8990
- const allowed = ["v", "networkId", "keyPath", "alg", "keyVersion", "encryptedSymmetricKey", "encryptedSymmetricKeyHash", "ciphertext", "metadata"];
8991
- 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"))) {
8967
+ const allowed = ["v", "networkId", "keyPath", "alg", "keyVersion", "encryptedSymmetricKey", "encryptedSymmetricKeyHash", "ciphertext", "aad", "metadata"];
8968
+ const source = expected.contentSource;
8969
+ 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((0, import_sha2565.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"))) {
8992
8970
  throw new Error("encrypted content envelope binding is invalid");
8993
8971
  }
8994
8972
  return record;
@@ -9109,7 +9087,7 @@ var ShareRecipientClient = class {
9109
9087
  this.session = { sessionId: delegation.sessionCid, expiresAt: new Date(compact.payload.exp * 1e3).toISOString(), actions: envelope.actions, resource: envelope.resource };
9110
9088
  return this.session;
9111
9089
  }
9112
- /** Decrypt a v3 KV value through a fresh recipient-signed decrypt invocation. */
9090
+ /** Decrypt unscoped v1 or path-bound v2 content, independently checking canonical and registered raw-key digests. */
9113
9091
  async decryptV3Content(bytes3) {
9114
9092
  const envelope = this.options.envelope;
9115
9093
  const signer = this.nativeSigner ?? this.signer;
@@ -9117,6 +9095,9 @@ var ShareRecipientClient = class {
9117
9095
  assertArtifactBinding(artifactBindingView(envelope));
9118
9096
  if (!matchesInitialCiphertextDigest(bytes3, envelope.contentSource.initialCiphertextDigestHex)) throw new Error("v3 owner KV ciphertext digest mismatch");
9119
9097
  const encrypted = parseV3InlineEncryptedEnvelope(bytes3, envelope);
9098
+ const ciphertext = fromBase64(encrypted.ciphertext, "v3 encrypted content");
9099
+ if (ciphertext.length < 28) throw new Error("encrypted content is malformed");
9100
+ const aad = encrypted.aad === void 0 ? void 0 : fromBase64(encrypted.aad, "v3 encrypted content AAD");
9120
9101
  const receiverPrivateKey = crypto.getRandomValues(new Uint8Array(32));
9121
9102
  const receiverPublicKey = toBase64(import_ed255195.x25519.getPublicKey(receiverPrivateKey));
9122
9103
  const receiverPublicKeyHash = canonicalHashHex(receiverPublicKey);
@@ -9153,7 +9134,7 @@ var ShareRecipientClient = class {
9153
9134
  const symmetricKey = await aesGcmDecrypt(shared, wrapped.slice(33));
9154
9135
  shared.fill(0);
9155
9136
  if (symmetricKey.length !== 32) throw new Error("v3 content key is malformed");
9156
- const plaintext = await aesGcmDecrypt(symmetricKey, fromBase64(encrypted.ciphertext, "v3 encrypted content"));
9137
+ const plaintext = await aesGcmDecrypt(symmetricKey, ciphertext, aad);
9157
9138
  this.v3ContentKey?.fill(0);
9158
9139
  this.v3ContentKey = symmetricKey;
9159
9140
  this.v3ContentEnvelope = encrypted;
@@ -9165,7 +9146,9 @@ var ShareRecipientClient = class {
9165
9146
  /** Re-encrypt edited v3 content with the admitted content key. */
9166
9147
  async encryptV3Content(bytes3, mediaType) {
9167
9148
  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");
9168
- return new TextEncoder().encode(canonicalize({ ...this.v3ContentEnvelope, ciphertext: toBase64Url(await aesGcmEncrypt(this.v3ContentKey, bytes3)), metadata: { ...this.v3ContentEnvelope.metadata ?? {}, contentType: mediaType } }));
9149
+ const envelope = this.v3ContentEnvelope;
9150
+ const aad = envelope.aad === void 0 ? void 0 : fromBase64(envelope.aad, "v3 encrypted content AAD");
9151
+ return new TextEncoder().encode(canonicalize({ ...envelope, ciphertext: toBase64(await aesGcmEncrypt(this.v3ContentKey, bytes3, aad)), metadata: { ...envelope.metadata ?? {}, contentType: mediaType } }));
9169
9152
  }
9170
9153
  async resumeWithProof(envelope, resumeToken, proof) {
9171
9154
  void envelope;