/**
 * @license
 *
 * Portions copyright 2022-2023 Project CHIP Authors
 * SPDX-License-Identifier: Apache-2.0
 */
import { Environment } from "#environment/Environment.js";
import { ImplementationError } from "#MatterError.js";
import { Bytes } from "#util/Bytes.js";
import { Entropy } from "#util/Entropy.js";
import { describeList } from "#util/String.js";
import { Logger } from "../log/Logger.js";
import { Ccm } from "./aes/Ccm.js";
import { Crypto, CRYPTO_SYMMETRIC_KEY_LENGTH } from "./Crypto.js";
import { CryptoVerifyError, KeyInputError } from "./CryptoError.js";
import { EcdsaSignature } from "./EcdsaSignature.js";
import { CurveType, Key, KeyType } from "./Key.js";
const logger = Logger.get("StandardCrypto");
const SIGNATURE_ALGORITHM = {
  name: "ECDSA",
  namedCurve: "P-256",
  hash: { name: "SHA-256" }
};
const requiredCryptoMethods = ["getRandomValues"];
const requiredSubtleMethods = [
  "digest",
  "deriveBits",
  "sign",
  "verify",
  "generateKey",
  "exportKey",
  "importKey"
];
class StandardCrypto extends Crypto {
  implementationName = "JS";
  #crypto;
  #subtle;
  constructor(crypto = globalThis.crypto) {
    const { subtle } = crypto;
    assertInterface("crypto", crypto, requiredCryptoMethods);
    assertInterface("crypto.subtle", subtle, requiredSubtleMethods);
    super();
    this.#crypto = crypto;
    this.#subtle = subtle;
  }
  get subtle() {
    return this.#subtle;
  }
  static provider() {
    return new StandardCrypto();
  }
  randomBytes(length) {
    const result = new Uint8Array(length);
    this.#crypto.getRandomValues(result);
    return result;
  }
  encrypt(key, data, nonce, associatedData) {
    const ccm = Ccm(key);
    return ccm.encrypt({
      pt: Bytes.of(data),
      nonce: Bytes.of(nonce),
      adata: associatedData !== void 0 ? Bytes.of(associatedData) : void 0
    });
  }
  decrypt(key, data, nonce, associatedData) {
    const ccm = Ccm(key);
    return ccm.decrypt({
      ct: Bytes.of(data),
      nonce: Bytes.of(nonce),
      adata: associatedData !== void 0 ? Bytes.of(associatedData) : void 0
    });
  }
  computeHash(buffer, algorithm = "SHA-256") {
    if (Array.isArray(buffer)) {
      buffer = Bytes.concat(...buffer);
    }
    if (!Bytes.isBytes(buffer)) {
      const chunks = [];
      const iterator = Symbol.asyncIterator in buffer ? buffer[Symbol.asyncIterator]() : buffer;
      const collectAndHash = async () => {
        while (true) {
          const result = await iterator.next();
          if (result.done) break;
          const chunk = result.value instanceof Uint8Array ? result.value : new Uint8Array(result.value);
          chunks.push(chunk);
        }
        const combined = Bytes.concat(...chunks);
        if (combined.byteLength > 1e5) {
          logger.info(
            `Streamed hash computation used with StandardCrypto for ${algorithm} and ${Math.floor(combined.byteLength / 1024)}kB. Consider alternatives that do not load all data into memory.`
          );
        }
        return await this.#subtle.digest(algorithm, Bytes.exclusive(combined));
      };
      return collectAndHash();
    }
    return this.#subtle.digest(algorithm, Bytes.exclusive(buffer));
  }
  async createPbkdf2Key(secret, salt, iteration, keyLength) {
    const key = await this.importKey("raw", secret, "PBKDF2", false, ["deriveBits"]);
    return this.#subtle.deriveBits(
      {
        name: "PBKDF2",
        hash: "SHA-256",
        salt: Bytes.exclusive(salt),
        iterations: iteration
      },
      key,
      keyLength * 8
    );
  }
  async createHkdfKey(secret, salt, info, length = CRYPTO_SYMMETRIC_KEY_LENGTH) {
    const key = await this.importKey("raw", secret, "HKDF", false, ["deriveBits"]);
    return this.#subtle.deriveBits(
      {
        name: "HKDF",
        hash: "SHA-256",
        salt: Bytes.exclusive(salt),
        info: Bytes.exclusive(info)
      },
      key,
      8 * length
    );
  }
  signHmac(secret, data) {
    return this.importKey("raw", secret, { name: "HMAC", hash: "SHA-256" }, false, ["sign"]).then(
      (key) => this.#subtle.sign("HMAC", key, Bytes.exclusive(data))
    );
  }
  async signEcdsa(key, data) {
    if (Array.isArray(data)) {
      data = Bytes.concat(...data);
    }
    const { crv, kty, d, x, y } = key;
    key = {
      kty,
      crv,
      d,
      x,
      y,
      ext: true,
      // Required by some subtle implementations to sign
      key_ops: ["sign"]
    };
    const subtleKey = await this.importKey("jwk", key, SIGNATURE_ALGORITHM, false, ["sign"]);
    const ieeeP1363 = Bytes.of(await this.#subtle.sign(SIGNATURE_ALGORITHM, subtleKey, Bytes.exclusive(data)));
    return new EcdsaSignature(ieeeP1363);
  }
  async verifyEcdsa(key, data, signature) {
    const { crv, kty, x, y } = key;
    key = { crv, kty, x, y };
    const subtleKey = await this.importKey("jwk", key, SIGNATURE_ALGORITHM, false, ["verify"]);
    const verified = await this.#subtle.verify(
      SIGNATURE_ALGORITHM,
      subtleKey,
      Bytes.exclusive(signature.bytes),
      Bytes.exclusive(data)
    );
    if (!verified) {
      throw new CryptoVerifyError("Signature verification failed");
    }
  }
  async createKeyPair() {
    const key = await this.generateJwk();
    return Key({
      kty: KeyType.EC,
      crv: CurveType.p256,
      d: key.d,
      x: key.x,
      y: key.y
    });
  }
  async generateJwk() {
    const subtleKey = await this.#subtle.generateKey(
      {
        // We must specify either ECDH or ECDSA to get an EC key but we may use the key for either (but not for
        // both)
        name: "ECDH",
        namedCurve: "P-256"
      },
      true,
      // We must also specify usage but will drop this on export
      ["deriveKey"]
    );
    return await this.#subtle.exportKey("jwk", subtleKey.privateKey);
  }
  async generateDhSecret(key, peerKey) {
    const subtleKey = await this.importKey(
      "jwk",
      key,
      {
        name: "ECDH",
        namedCurve: "P-256"
      },
      false,
      ["deriveBits"]
    );
    const subtlePeerKey = await this.importKey(
      "jwk",
      peerKey,
      {
        name: "ECDH",
        namedCurve: "P-256"
      },
      false,
      []
    );
    return this.#subtle.deriveBits(
      {
        name: "ECDH",
        public: subtlePeerKey
      },
      subtleKey,
      256
    );
  }
  async importKey(format, keyData, algorithm, extractable, keyUsages) {
    try {
      return await this.#subtle.importKey(format, keyData, algorithm, extractable, keyUsages);
    } catch (cause) {
      throw new KeyInputError("Invalid key", { cause });
    }
  }
}
function assertInterface(name, object, requiredMethods) {
  if (typeof object !== "object" || object === null) {
    throw new ImplementationError(
      `The ${name} implementation passed to StandardCrypto is invalid (received ${typeof object})`
    );
  }
  const missingMethods = requiredMethods.filter((name2) => typeof object[name2] !== "function");
  if (missingMethods.length) {
    throw new ImplementationError(
      `The ${name} implementation passed to StandardCrypto is missing required method${missingMethods.length === 1 ? "" : "s"} ${describeList("and", ...missingMethods)}`
    );
  }
}
if ("crypto" in globalThis && globalThis.crypto?.subtle) {
  const crypto = new StandardCrypto();
  Environment.default.set(Entropy, crypto);
  Environment.default.set(Crypto, crypto);
}
export {
  StandardCrypto
};
//# sourceMappingURL=StandardCrypto.js.map
