Merge pull request #16959 from Snuffleupagus/rm-CipherTransformFactory-closure

Remove the closure from the `CipherTransformFactory` class
This commit is contained in:
Tim van der Meij 2023-09-17 11:42:20 +02:00 committed by GitHub
commit 98671c3618
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

View File

@ -1418,14 +1418,14 @@ class CipherTransform {
} }
} }
const CipherTransformFactory = (function CipherTransformFactoryClosure() { class CipherTransformFactory {
const defaultPasswordBytes = new Uint8Array([ static #defaultPasswordBytes = new Uint8Array([
0x28, 0xbf, 0x4e, 0x5e, 0x4e, 0x75, 0x8a, 0x41, 0x64, 0x00, 0x4e, 0x56, 0x28, 0xbf, 0x4e, 0x5e, 0x4e, 0x75, 0x8a, 0x41, 0x64, 0x00, 0x4e, 0x56,
0xff, 0xfa, 0x01, 0x08, 0x2e, 0x2e, 0x00, 0xb6, 0xd0, 0x68, 0x3e, 0x80, 0xff, 0xfa, 0x01, 0x08, 0x2e, 0x2e, 0x00, 0xb6, 0xd0, 0x68, 0x3e, 0x80,
0x2f, 0x0c, 0xa9, 0xfe, 0x64, 0x53, 0x69, 0x7a, 0x2f, 0x0c, 0xa9, 0xfe, 0x64, 0x53, 0x69, 0x7a,
]); ]);
function createEncryptionKey20( #createEncryptionKey20(
revision, revision,
password, password,
ownerPassword, ownerPassword,
@ -1471,7 +1471,7 @@ const CipherTransformFactory = (function CipherTransformFactoryClosure() {
return null; return null;
} }
function prepareKeyData( #prepareKeyData(
fileId, fileId,
password, password,
ownerPassword, ownerPassword,
@ -1494,7 +1494,7 @@ const CipherTransformFactory = (function CipherTransformFactoryClosure() {
} }
j = 0; j = 0;
while (i < 32) { while (i < 32) {
hashData[i++] = defaultPasswordBytes[j++]; hashData[i++] = CipherTransformFactory.#defaultPasswordBytes[j++];
} }
// as now the padded password in the hashData[0..i] // as now the padded password in the hashData[0..i]
for (j = 0, n = ownerPassword.length; j < n; ++j) { for (j = 0, n = ownerPassword.length; j < n; ++j) {
@ -1525,7 +1525,7 @@ const CipherTransformFactory = (function CipherTransformFactoryClosure() {
if (revision >= 3) { if (revision >= 3) {
for (i = 0; i < 32; ++i) { for (i = 0; i < 32; ++i) {
hashData[i] = defaultPasswordBytes[i]; hashData[i] = CipherTransformFactory.#defaultPasswordBytes[i];
} }
for (j = 0, n = fileId.length; j < n; ++j) { for (j = 0, n = fileId.length; j < n; ++j) {
hashData[i++] = fileId[j]; hashData[i++] = fileId[j];
@ -1548,7 +1548,9 @@ const CipherTransformFactory = (function CipherTransformFactoryClosure() {
} }
} else { } else {
cipher = new ARCFourCipher(encryptionKey); cipher = new ARCFourCipher(encryptionKey);
checkData = cipher.encryptBlock(defaultPasswordBytes); checkData = cipher.encryptBlock(
CipherTransformFactory.#defaultPasswordBytes
);
for (j = 0, n = checkData.length; j < n; ++j) { for (j = 0, n = checkData.length; j < n; ++j) {
if (userPassword[j] !== checkData[j]) { if (userPassword[j] !== checkData[j]) {
return null; return null;
@ -1558,7 +1560,7 @@ const CipherTransformFactory = (function CipherTransformFactoryClosure() {
return encryptionKey; return encryptionKey;
} }
function decodeUserPassword(password, ownerPassword, revision, keyLength) { #decodeUserPassword(password, ownerPassword, revision, keyLength) {
const hashData = new Uint8Array(32); const hashData = new Uint8Array(32);
let i = 0; let i = 0;
const n = Math.min(32, password.length); const n = Math.min(32, password.length);
@ -1567,7 +1569,7 @@ const CipherTransformFactory = (function CipherTransformFactoryClosure() {
} }
let j = 0; let j = 0;
while (i < 32) { while (i < 32) {
hashData[i++] = defaultPasswordBytes[j++]; hashData[i++] = CipherTransformFactory.#defaultPasswordBytes[j++];
} }
let hash = calculateMD5(hashData, 0, i); let hash = calculateMD5(hashData, 0, i);
const keyLengthInBytes = keyLength >> 3; const keyLengthInBytes = keyLength >> 3;
@ -1595,9 +1597,7 @@ const CipherTransformFactory = (function CipherTransformFactoryClosure() {
return userPassword; return userPassword;
} }
const identityName = Name.get("Identity"); #buildObjectKey(num, gen, encryptionKey, isAes = false) {
function buildObjectKey(num, gen, encryptionKey, isAes = false) {
const key = new Uint8Array(encryptionKey.length + 9); const key = new Uint8Array(encryptionKey.length + 9);
const n = encryptionKey.length; const n = encryptionKey.length;
let i; let i;
@ -1619,242 +1619,229 @@ const CipherTransformFactory = (function CipherTransformFactoryClosure() {
return hash.subarray(0, Math.min(encryptionKey.length + 5, 16)); return hash.subarray(0, Math.min(encryptionKey.length + 5, 16));
} }
function buildCipherConstructor(cf, name, num, gen, key) { #buildCipherConstructor(cf, name, num, gen, key) {
if (!(name instanceof Name)) { if (!(name instanceof Name)) {
throw new FormatError("Invalid crypt filter name."); throw new FormatError("Invalid crypt filter name.");
} }
const self = this;
const cryptFilter = cf.get(name.name); const cryptFilter = cf.get(name.name);
let cfm; const cfm = cryptFilter?.get("CFM");
if (cryptFilter !== null && cryptFilter !== undefined) {
cfm = cryptFilter.get("CFM");
}
if (!cfm || cfm.name === "None") { if (!cfm || cfm.name === "None") {
return function cipherTransformFactoryBuildCipherConstructorNone() { return function () {
return new NullCipher(); return new NullCipher();
}; };
} }
if (cfm.name === "V2") { if (cfm.name === "V2") {
return function cipherTransformFactoryBuildCipherConstructorV2() { return function () {
return new ARCFourCipher( return new ARCFourCipher(
buildObjectKey(num, gen, key, /* isAes = */ false) self.#buildObjectKey(num, gen, key, /* isAes = */ false)
); );
}; };
} }
if (cfm.name === "AESV2") { if (cfm.name === "AESV2") {
return function cipherTransformFactoryBuildCipherConstructorAESV2() { return function () {
return new AES128Cipher( return new AES128Cipher(
buildObjectKey(num, gen, key, /* isAes = */ true) self.#buildObjectKey(num, gen, key, /* isAes = */ true)
); );
}; };
} }
if (cfm.name === "AESV3") { if (cfm.name === "AESV3") {
return function cipherTransformFactoryBuildCipherConstructorAESV3() { return function () {
return new AES256Cipher(key); return new AES256Cipher(key);
}; };
} }
throw new FormatError("Unknown crypto method"); throw new FormatError("Unknown crypto method");
} }
// eslint-disable-next-line no-shadow constructor(dict, fileId, password) {
class CipherTransformFactory { const filter = dict.get("Filter");
constructor(dict, fileId, password) { if (!isName(filter, "Standard")) {
const filter = dict.get("Filter"); throw new FormatError("unknown encryption method");
if (!isName(filter, "Standard")) { }
throw new FormatError("unknown encryption method"); this.filterName = filter.name;
} this.dict = dict;
this.filterName = filter.name; const algorithm = dict.get("V");
this.dict = dict; if (
const algorithm = dict.get("V"); !Number.isInteger(algorithm) ||
if ( (algorithm !== 1 && algorithm !== 2 && algorithm !== 4 && algorithm !== 5)
!Number.isInteger(algorithm) || ) {
(algorithm !== 1 && throw new FormatError("unsupported encryption algorithm");
algorithm !== 2 && }
algorithm !== 4 && this.algorithm = algorithm;
algorithm !== 5) let keyLength = dict.get("Length");
) { if (!keyLength) {
throw new FormatError("unsupported encryption algorithm"); // Spec asks to rely on encryption dictionary's Length entry, however
} // some PDFs don't have it. Trying to recover.
this.algorithm = algorithm; if (algorithm <= 3) {
let keyLength = dict.get("Length"); // For 1 and 2 it's fixed to 40-bit, for 3 40-bit is a minimal value.
if (!keyLength) { keyLength = 40;
// 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 = prepareKeyData(
fileIdBytes,
passwordBytes,
ownerPassword,
userPassword,
flags,
revision,
keyLength,
encryptMetadata
);
} else { } else {
const ownerValidationSalt = ownerBytes.subarray(32, 40); // Trying to find default handler -- it usually has Length.
const ownerKeySalt = ownerBytes.subarray(40, 48); const cfDict = dict.get("CF");
const uBytes = userBytes.subarray(0, 48); const streamCryptoName = dict.get("StmF");
const userValidationSalt = userBytes.subarray(32, 40); if (cfDict instanceof Dict && streamCryptoName instanceof Name) {
const userKeySalt = userBytes.subarray(40, 48); cfDict.suppressEncryption = true; // See comment below.
const ownerEncryption = stringToBytes(dict.get("OE")); const handlerDict = cfDict.get(streamCryptoName.name);
const userEncryption = stringToBytes(dict.get("UE")); keyLength = handlerDict?.get("Length") || 128;
const perms = stringToBytes(dict.get("Perms")); if (keyLength < 40) {
encryptionKey = createEncryptionKey20( // Sometimes it's incorrect value of bits, generators specify
revision, // bytes.
passwordBytes, keyLength <<= 3;
ownerPassword, }
ownerValidationSalt,
ownerKeySalt,
uBytes,
userPassword,
userValidationSalt,
userKeySalt,
ownerEncryption,
userEncryption,
perms
);
}
if (!encryptionKey && !password) {
throw new PasswordException(
"No password given",
PasswordResponses.NEED_PASSWORD
);
} else if (!encryptionKey && password) {
// Attempting use the password as an owner password
const decodedPassword = decodeUserPassword(
passwordBytes,
ownerPassword,
revision,
keyLength
);
encryptionKey = prepareKeyData(
fileIdBytes,
decodedPassword,
ownerPassword,
userPassword,
flags,
revision,
keyLength,
encryptMetadata
);
}
if (!encryptionKey) {
throw new PasswordException(
"Incorrect Password",
PasswordResponses.INCORRECT_PASSWORD
);
}
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") || identityName;
this.strf = dict.get("StrF") || identityName;
this.eff = dict.get("EFF") || this.stmf;
} }
} }
if (!Number.isInteger(keyLength) || keyLength < 40 || keyLength % 8 !== 0) {
throw new FormatError("invalid key length");
}
createCipherTransform(num, gen) { const ownerBytes = stringToBytes(dict.get("O")),
if (this.algorithm === 4 || this.algorithm === 5) { userBytes = stringToBytes(dict.get("U"));
return new CipherTransform( // prepare keys
buildCipherConstructor( const ownerPassword = ownerBytes.subarray(0, 32);
this.cf, const userPassword = userBytes.subarray(0, 32);
this.strf, const flags = dict.get("P");
num, const revision = dict.get("R");
gen, // meaningful when V is 4 or 5
this.encryptionKey const encryptMetadata =
), (algorithm === 4 || algorithm === 5) &&
buildCipherConstructor( dict.get("EncryptMetadata") !== false;
this.cf, this.encryptMetadata = encryptMetadata;
this.stmf,
num, const fileIdBytes = stringToBytes(fileId);
gen, let passwordBytes;
this.encryptionKey if (password) {
) if (revision === 6) {
); try {
password = utf8StringToString(password);
} catch {
warn(
"CipherTransformFactory: Unable to convert UTF8 encoded password."
);
}
} }
// algorithms 1 and 2 passwordBytes = stringToBytes(password);
const key = buildObjectKey( }
num,
gen, let encryptionKey;
this.encryptionKey, if (algorithm !== 5) {
/* isAes = */ false encryptionKey = this.#prepareKeyData(
fileIdBytes,
passwordBytes,
ownerPassword,
userPassword,
flags,
revision,
keyLength,
encryptMetadata
); );
const cipherConstructor = function buildCipherCipherConstructor() { } else {
return new ARCFourCipher(key); const ownerValidationSalt = ownerBytes.subarray(32, 40);
}; const ownerKeySalt = ownerBytes.subarray(40, 48);
return new CipherTransform(cipherConstructor, cipherConstructor); 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 && !password) {
throw new PasswordException(
"No password given",
PasswordResponses.NEED_PASSWORD
);
} else if (!encryptionKey && 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
);
}
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;
} }
} }
return CipherTransformFactory; createCipherTransform(num, gen) {
})(); if (this.algorithm === 4 || this.algorithm === 5) {
return new CipherTransform(
this.#buildCipherConstructor(
this.cf,
this.strf,
num,
gen,
this.encryptionKey
),
this.#buildCipherConstructor(
this.cf,
this.stmf,
num,
gen,
this.encryptionKey
)
);
}
// algorithms 1 and 2
const key = this.#buildObjectKey(
num,
gen,
this.encryptionKey,
/* isAes = */ false
);
const cipherConstructor = function () {
return new ARCFourCipher(key);
};
return new CipherTransform(cipherConstructor, cipherConstructor);
}
}
export { export {
AES128Cipher, AES128Cipher,