/**
 * @license
 * Copyright 2022-2026 Matter.js Authors
 * SPDX-License-Identifier: Apache-2.0
 */
import {
  Bytes,
  Construction,
  Crypto,
  Environmental,
  ImplementationError,
  InternalError,
  Logger,
  PrivateKey,
  StorageContext,
  StorageManager,
  Time,
  asyncNew,
  toHex
} from "#general";
import { jsToMatterDate } from "./kinds/definitions/asn.js";
import { Icac } from "./kinds/Icac.js";
import { Noc } from "./kinds/Noc.js";
import { Rcac } from "./kinds/Rcac.js";
const logger = Logger.get("CertificateAuthority");
class CertificateAuthority {
  #crypto;
  #rootCertId = BigInt(0);
  #rootKeyPair;
  #rootKeyIdentifier;
  #rootCertBytes;
  #nextCertificateId = BigInt(1);
  #construction;
  #icacProps;
  get crypto() {
    return this.#crypto;
  }
  get construction() {
    return this.#construction;
  }
  static async create(crypto, options, generateIntermediateCert) {
    return asyncNew(CertificateAuthority, crypto, options, generateIntermediateCert);
  }
  constructor(crypto, options, generateIntermediateCert) {
    this.#crypto = crypto;
    this.#construction = Construction(this, async () => {
      if (typeof options === "boolean") {
        generateIntermediateCert = options;
        options = void 0;
      }
      const certValues = options instanceof StorageContext ? await options.values() : options ?? {};
      const requireIcac = generateIntermediateCert ?? this.#isValidStoredIcacCertificate(certValues);
      if (this.#isValidStoredRootCertificate(certValues)) {
        this.#loadFromStorage(certValues, requireIcac);
        logger.info(
          `Loaded stored credentials with ID ${this.#rootCertId}${this.#icacProps !== void 0 ? ` and ICAC with ID ${this.#icacProps.certId}` : ""}`
        );
        return;
      }
      this.#rootKeyPair = await this.#crypto.createKeyPair();
      this.#rootKeyIdentifier = Bytes.of(await this.#crypto.computeHash(this.#rootKeyPair.publicKey)).slice(
        0,
        20
      );
      this.#rootCertBytes = await this.#generateRootCert();
      if (requireIcac) {
        this.#icacProps = await this.#generateIcacProps();
      }
      logger.info(
        `Created new credentials with ID ${this.#rootCertId}${this.#icacProps !== void 0 ? ` and ICAC with ID ${this.#icacProps.certId}` : ""}`
      );
      if (options instanceof StorageContext) {
        await options.set(this.#buildStorageData());
      }
    });
  }
  static [Environmental.create](env) {
    const storage = env.get(StorageManager).createContext("certificates");
    const instance = new CertificateAuthority(env.get(Crypto), storage);
    env.set(CertificateAuthority, instance);
    return instance;
  }
  get rootCert() {
    return this.#construction.assert("root cert", this.#rootCertBytes);
  }
  get icacCert() {
    if (!this.#icacProps) {
      return void 0;
    }
    return this.#construction.assert("icac cert", this.#icacProps.certBytes);
  }
  get config() {
    return {
      rootCertId: this.#rootCertId,
      rootKeyIdentifier: this.construction.assert("root key identifier", this.#rootKeyIdentifier),
      rootCertBytes: this.construction.assert("root cert bytes", this.#rootCertBytes),
      nextCertificateId: this.#nextCertificateId,
      ...this.#icacProps !== void 0 ? {
        rootKeyPair: this.#rootKeyPair?.keyPair,
        // rootKeyPair is optional when using ICAC
        icacCertId: this.#icacProps.certId,
        icacKeyPair: this.construction.assert("icac key pair", this.#icacProps.keyPair).keyPair,
        icacKeyIdentifier: this.construction.assert("icac key identifier", this.#icacProps.keyIdentifier),
        icacCertBytes: this.construction.assert("icac cert bytes", this.#icacProps.certBytes)
      } : {
        rootKeyPair: this.construction.assert("root key pair", this.#rootKeyPair).keyPair
      }
    };
  }
  async #generateRootCert() {
    const now = Time.now;
    const cert = new Rcac({
      serialNumber: Bytes.fromHex(toHex(this.#rootCertId)),
      signatureAlgorithm: 1,
      publicKeyAlgorithm: 1,
      ellipticCurveIdentifier: 1,
      issuer: { rcacId: this.#rootCertId },
      notBefore: jsToMatterDate(now, -1),
      notAfter: jsToMatterDate(now, 10),
      subject: { rcacId: this.#rootCertId },
      ellipticCurvePublicKey: this.#initializedRootKeyPair.publicKey,
      extensions: {
        basicConstraints: { isCa: true },
        keyUsage: {
          keyCertSign: true,
          cRLSign: true
        },
        subjectKeyIdentifier: this.#initializedRootKeyIdentifier,
        authorityKeyIdentifier: this.#initializedRootKeyIdentifier
      }
    });
    await cert.sign(this.#crypto, this.#initializedRootKeyPair);
    return cert.asSignedTlv();
  }
  async #generateIcacProps() {
    const certId = this.#nextCertificateId++;
    const keyPair = await this.#crypto.createKeyPair();
    const keyIdentifier = Bytes.of(await this.#crypto.computeHash(keyPair.publicKey)).slice(0, 20);
    const now = Time.now;
    const cert = new Icac({
      serialNumber: Bytes.fromHex(toHex(certId)),
      signatureAlgorithm: 1,
      publicKeyAlgorithm: 1,
      ellipticCurveIdentifier: 1,
      issuer: { rcacId: this.#rootCertId },
      notBefore: jsToMatterDate(now, -1),
      notAfter: jsToMatterDate(now, 10),
      subject: { icacId: certId },
      ellipticCurvePublicKey: keyPair.publicKey,
      extensions: {
        basicConstraints: { isCa: true },
        keyUsage: {
          keyCertSign: true,
          cRLSign: true
        },
        subjectKeyIdentifier: keyIdentifier,
        authorityKeyIdentifier: this.#initializedRootKeyIdentifier
      }
    });
    await cert.sign(this.#crypto, this.#initializedRootKeyPair);
    return {
      certId,
      keyPair,
      keyIdentifier,
      certBytes: cert.asSignedTlv()
    };
  }
  async generateNoc(publicKey, fabricId, nodeId, caseAuthenticatedTags) {
    const now = Time.now;
    const certId = this.#nextCertificateId++;
    const { issuer, signingKey, authorityKeyId } = this.#getSigningParameters();
    const cert = new Noc({
      serialNumber: Bytes.fromHex(toHex(certId)),
      signatureAlgorithm: 1,
      publicKeyAlgorithm: 1,
      ellipticCurveIdentifier: 1,
      issuer,
      notBefore: jsToMatterDate(now, -1),
      notAfter: jsToMatterDate(now, 10),
      subject: { fabricId, nodeId, caseAuthenticatedTags },
      ellipticCurvePublicKey: publicKey,
      extensions: {
        basicConstraints: { isCa: false },
        keyUsage: {
          digitalSignature: true
        },
        extendedKeyUsage: [2, 1],
        subjectKeyIdentifier: Bytes.of(await this.#crypto.computeHash(publicKey)).slice(0, 20),
        authorityKeyIdentifier: authorityKeyId
      }
    });
    await cert.sign(this.#crypto, signingKey);
    return cert.asSignedTlv();
  }
  get #initializedRootKeyPair() {
    if (this.#rootKeyPair === void 0) {
      throw new InternalError("CA private key is not installed");
    }
    return this.#rootKeyPair;
  }
  get #initializedRootKeyIdentifier() {
    if (this.#rootKeyIdentifier === void 0) {
      throw new InternalError("CA key identifier is not installed");
    }
    return this.#rootKeyIdentifier;
  }
  #isValidStoredRootCertificate(certValues) {
    return (typeof certValues.rootCertId === "number" || typeof certValues.rootCertId === "bigint") && (certValues.rootKeyPair === void 0 || Bytes.isBytes(certValues.rootKeyPair) || typeof certValues.rootKeyPair === "object") && Bytes.isBytes(certValues.rootKeyIdentifier) && Bytes.isBytes(certValues.rootCertBytes) && (typeof certValues.nextCertificateId === "number" || typeof certValues.nextCertificateId === "bigint");
  }
  #isValidStoredIcacCertificate(certValues) {
    return (typeof certValues.icacCertId === "number" || typeof certValues.icacCertId === "bigint") && (Bytes.isBytes(certValues.icacKeyPair) || typeof certValues.icacKeyPair === "object") && Bytes.isBytes(certValues.icacKeyIdentifier) && Bytes.isBytes(certValues.icacCertBytes);
  }
  #loadFromStorage(certValues, requireIcac) {
    this.#rootCertId = BigInt(certValues.rootCertId);
    if (certValues.rootKeyPair !== void 0) {
      this.#rootKeyPair = PrivateKey(certValues.rootKeyPair);
    }
    this.#rootKeyIdentifier = certValues.rootKeyIdentifier;
    this.#rootCertBytes = certValues.rootCertBytes;
    this.#nextCertificateId = BigInt(certValues.nextCertificateId);
    const hasIcac = this.#isValidStoredIcacCertificate(certValues);
    if (requireIcac !== void 0 && requireIcac !== hasIcac) {
      throw new ImplementationError(
        `Stored credentials contain ICAC certificate: ${hasIcac}, but configuration expected it to be ${requireIcac}`
      );
    }
    if (hasIcac) {
      this.#icacProps = {
        certId: BigInt(certValues.icacCertId),
        keyPair: PrivateKey(certValues.icacKeyPair),
        keyIdentifier: certValues.icacKeyIdentifier,
        certBytes: certValues.icacCertBytes
      };
    } else {
      if (this.#rootKeyPair === void 0) {
        throw new ImplementationError(
          "rootKeyPair is required when not using ICAC (2-tier PKI requires RCAC private key to sign NOCs)"
        );
      }
    }
  }
  #buildStorageData() {
    return {
      rootCertId: this.#rootCertId,
      rootKeyIdentifier: this.#initializedRootKeyIdentifier,
      rootCertBytes: this.#initializedRootCertBytes,
      nextCertificateId: this.#nextCertificateId,
      ...this.#icacProps ? {
        rootKeyPair: this.#rootKeyPair?.keyPair,
        // rootKeyPair is optional when using ICAC
        icacCertId: this.#icacProps.certId,
        icacKeyPair: this.#icacProps.keyPair.keyPair,
        icacKeyIdentifier: this.#icacProps.keyIdentifier,
        icacCertBytes: this.#icacProps.certBytes
      } : {
        rootKeyPair: this.#initializedRootKeyPair.keyPair
      }
    };
  }
  #getSigningParameters() {
    if (this.#icacProps) {
      return {
        issuer: { icacId: this.#icacProps.certId },
        signingKey: this.#icacProps.keyPair,
        authorityKeyId: this.#icacProps.keyIdentifier
      };
    }
    return {
      issuer: { rcacId: this.#rootCertId },
      signingKey: this.#initializedRootKeyPair,
      authorityKeyId: this.#initializedRootKeyIdentifier
    };
  }
  get #initializedRootCertBytes() {
    if (this.#rootCertBytes === void 0) {
      throw new InternalError("Root certificate is not installed");
    }
    return this.#rootCertBytes;
  }
}
export {
  CertificateAuthority
};
//# sourceMappingURL=CertificateAuthority.js.map
