constructor()

in src/core/crypto.js [1022:1184]


  constructor(dict, fileId, password) {
    const filter = dict.get("Filter");
    if (!isName(filter, "Standard")) {
      throw new FormatError("unknown encryption method");
    }
    this.filterName = filter.name;
    this.dict = dict;
    const algorithm = dict.get("V");
    if (
      !Number.isInteger(algorithm) ||
      (algorithm !== 1 && algorithm !== 2 && algorithm !== 4 && algorithm !== 5)
    ) {
      throw new FormatError("unsupported encryption algorithm");
    }
    this.algorithm = algorithm;
    let keyLength = dict.get("Length");
    if (!keyLength) {
      // Spec asks to rely on encryption dictionary's Length entry, however
      // some PDFs don't have it. Trying to recover.
      if (algorithm <= 3) {
        // For 1 and 2 it's fixed to 40-bit, for 3 40-bit is a minimal value.
        keyLength = 40;
      } else {
        // Trying to find default handler -- it usually has Length.
        const cfDict = dict.get("CF");
        const streamCryptoName = dict.get("StmF");
        if (cfDict instanceof Dict && streamCryptoName instanceof Name) {
          cfDict.suppressEncryption = true; // See comment below.
          const handlerDict = cfDict.get(streamCryptoName.name);
          keyLength = handlerDict?.get("Length") || 128;
          if (keyLength < 40) {
            // Sometimes it's incorrect value of bits, generators specify
            // bytes.
            keyLength <<= 3;
          }
        }
      }
    }
    if (!Number.isInteger(keyLength) || keyLength < 40 || keyLength % 8 !== 0) {
      throw new FormatError("invalid key length");
    }

    const ownerBytes = stringToBytes(dict.get("O")),
      userBytes = stringToBytes(dict.get("U"));
    // prepare keys
    const ownerPassword = ownerBytes.subarray(0, 32);
    const userPassword = userBytes.subarray(0, 32);
    const flags = dict.get("P");
    const revision = dict.get("R");
    // meaningful when V is 4 or 5
    const encryptMetadata =
      (algorithm === 4 || algorithm === 5) &&
      dict.get("EncryptMetadata") !== false;
    this.encryptMetadata = encryptMetadata;

    const fileIdBytes = stringToBytes(fileId);
    let passwordBytes;
    if (password) {
      if (revision === 6) {
        try {
          password = utf8StringToString(password);
        } catch {
          warn(
            "CipherTransformFactory: Unable to convert UTF8 encoded password."
          );
        }
      }
      passwordBytes = stringToBytes(password);
    }

    let encryptionKey;
    if (algorithm !== 5) {
      encryptionKey = this.#prepareKeyData(
        fileIdBytes,
        passwordBytes,
        ownerPassword,
        userPassword,
        flags,
        revision,
        keyLength,
        encryptMetadata
      );
    } else {
      const ownerValidationSalt = ownerBytes.subarray(32, 40);
      const ownerKeySalt = ownerBytes.subarray(40, 48);
      const uBytes = userBytes.subarray(0, 48);
      const userValidationSalt = userBytes.subarray(32, 40);
      const userKeySalt = userBytes.subarray(40, 48);
      const ownerEncryption = stringToBytes(dict.get("OE"));
      const userEncryption = stringToBytes(dict.get("UE"));
      const perms = stringToBytes(dict.get("Perms"));
      encryptionKey = this.#createEncryptionKey20(
        revision,
        passwordBytes,
        ownerPassword,
        ownerValidationSalt,
        ownerKeySalt,
        uBytes,
        userPassword,
        userValidationSalt,
        userKeySalt,
        ownerEncryption,
        userEncryption,
        perms
      );
    }
    if (!encryptionKey) {
      if (!password) {
        throw new PasswordException(
          "No password given",
          PasswordResponses.NEED_PASSWORD
        );
      }
      // Attempting use the password as an owner password
      const decodedPassword = this.#decodeUserPassword(
        passwordBytes,
        ownerPassword,
        revision,
        keyLength
      );
      encryptionKey = this.#prepareKeyData(
        fileIdBytes,
        decodedPassword,
        ownerPassword,
        userPassword,
        flags,
        revision,
        keyLength,
        encryptMetadata
      );
    }

    if (!encryptionKey) {
      throw new PasswordException(
        "Incorrect Password",
        PasswordResponses.INCORRECT_PASSWORD
      );
    }

    if (algorithm === 4 && encryptionKey.length < 16) {
      // Extend key to 16 byte minimum (undocumented),
      // fixes issue19484_1.pdf and issue19484_2.pdf.
      this.encryptionKey = new Uint8Array(16);
      this.encryptionKey.set(encryptionKey);
    } else {
      this.encryptionKey = encryptionKey;
    }

    if (algorithm >= 4) {
      const cf = dict.get("CF");
      if (cf instanceof Dict) {
        // The 'CF' dictionary itself should not be encrypted, and by setting
        // `suppressEncryption` we can prevent an infinite loop inside of
        // `XRef_fetchUncompressed` if the dictionary contains indirect
        // objects (fixes issue7665.pdf).
        cf.suppressEncryption = true;
      }
      this.cf = cf;
      this.stmf = dict.get("StmF") || Name.get("Identity");
      this.strf = dict.get("StrF") || Name.get("Identity");
      this.eff = dict.get("EFF") || this.stmf;
    }
  }