Convert code in src/core/crypto.js to use "normal" classes

All of this code predates the existence of native JS classes, however we can now clean this up a bit. This patch thus let us remove some variable "shadowing" from the code.
This commit is contained in:
Jonas Jenwald 2021-02-26 15:51:45 +01:00
parent b884757873
commit 6b4c4f80e4

View File

@ -28,9 +28,8 @@ import {
import { isDict, isName, Name } from "./primitives.js"; import { isDict, isName, Name } from "./primitives.js";
import { DecryptStream } from "./stream.js"; import { DecryptStream } from "./stream.js";
var ARCFourCipher = (function ARCFourCipherClosure() { class ARCFourCipher {
// eslint-disable-next-line no-shadow constructor(key) {
function ARCFourCipher(key) {
this.a = 0; this.a = 0;
this.b = 0; this.b = 0;
var s = new Uint8Array(256); var s = new Uint8Array(256);
@ -50,35 +49,37 @@ var ARCFourCipher = (function ARCFourCipherClosure() {
this.s = s; this.s = s;
} }
ARCFourCipher.prototype = { encryptBlock(data) {
encryptBlock: function ARCFourCipher_encryptBlock(data) { var i,
var i, n = data.length,
n = data.length, tmp,
tmp, tmp2;
tmp2; var a = this.a,
var a = this.a, b = this.b,
b = this.b, s = this.s;
s = this.s; var output = new Uint8Array(n);
var output = new Uint8Array(n); for (i = 0; i < n; ++i) {
for (i = 0; i < n; ++i) { a = (a + 1) & 0xff;
a = (a + 1) & 0xff; tmp = s[a];
tmp = s[a]; b = (b + tmp) & 0xff;
b = (b + tmp) & 0xff; tmp2 = s[b];
tmp2 = s[b]; s[a] = tmp2;
s[a] = tmp2; s[b] = tmp;
s[b] = tmp; output[i] = data[i] ^ s[(tmp + tmp2) & 0xff];
output[i] = data[i] ^ s[(tmp + tmp2) & 0xff]; }
} this.a = a;
this.a = a; this.b = b;
this.b = b; return output;
return output; }
},
};
ARCFourCipher.prototype.decryptBlock = ARCFourCipher.prototype.encryptBlock;
ARCFourCipher.prototype.encrypt = ARCFourCipher.prototype.encryptBlock;
return ARCFourCipher; decryptBlock(data) {
})(); return this.encryptBlock(data);
}
encrypt(data) {
return this.encryptBlock(data);
}
}
var calculateMD5 = (function calculateMD5Closure() { var calculateMD5 = (function calculateMD5Closure() {
// prettier-ignore // prettier-ignore
@ -180,94 +181,93 @@ var calculateMD5 = (function calculateMD5Closure() {
return hash; return hash;
})(); })();
var Word64 = (function Word64Closure() {
// eslint-disable-next-line no-shadow class Word64 {
function Word64(highInteger, lowInteger) { constructor(highInteger, lowInteger) {
this.high = highInteger | 0; this.high = highInteger | 0;
this.low = lowInteger | 0; this.low = lowInteger | 0;
} }
Word64.prototype = {
and: function Word64_and(word) {
this.high &= word.high;
this.low &= word.low;
},
xor: function Word64_xor(word) {
this.high ^= word.high;
this.low ^= word.low;
},
or: function Word64_or(word) { and(word) {
this.high |= word.high; this.high &= word.high;
this.low |= word.low; this.low &= word.low;
}, }
shiftRight: function Word64_shiftRight(places) { xor(word) {
if (places >= 32) { this.high ^= word.high;
this.low = (this.high >>> (places - 32)) | 0; this.low ^= word.low;
this.high = 0; }
} else {
this.low = (this.low >>> places) | (this.high << (32 - places));
this.high = (this.high >>> places) | 0;
}
},
shiftLeft: function Word64_shiftLeft(places) { or(word) {
if (places >= 32) { this.high |= word.high;
this.high = this.low << (places - 32); this.low |= word.low;
this.low = 0; }
} else {
this.high = (this.high << places) | (this.low >>> (32 - places));
this.low = this.low << places;
}
},
rotateRight: function Word64_rotateRight(places) { shiftRight(places) {
var low, high; if (places >= 32) {
if (places & 32) { this.low = (this.high >>> (places - 32)) | 0;
high = this.low; this.high = 0;
low = this.high; } else {
} else { this.low = (this.low >>> places) | (this.high << (32 - places));
low = this.low; this.high = (this.high >>> places) | 0;
high = this.high; }
} }
places &= 31;
this.low = (low >>> places) | (high << (32 - places));
this.high = (high >>> places) | (low << (32 - places));
},
not: function Word64_not() { shiftLeft(places) {
this.high = ~this.high; if (places >= 32) {
this.low = ~this.low; this.high = this.low << (places - 32);
}, this.low = 0;
} else {
this.high = (this.high << places) | (this.low >>> (32 - places));
this.low = this.low << places;
}
}
add: function Word64_add(word) { rotateRight(places) {
var lowAdd = (this.low >>> 0) + (word.low >>> 0); var low, high;
var highAdd = (this.high >>> 0) + (word.high >>> 0); if (places & 32) {
if (lowAdd > 0xffffffff) { high = this.low;
highAdd += 1; low = this.high;
} } else {
this.low = lowAdd | 0; low = this.low;
this.high = highAdd | 0; high = this.high;
}, }
places &= 31;
this.low = (low >>> places) | (high << (32 - places));
this.high = (high >>> places) | (low << (32 - places));
}
copyTo: function Word64_copyTo(bytes, offset) { not() {
bytes[offset] = (this.high >>> 24) & 0xff; this.high = ~this.high;
bytes[offset + 1] = (this.high >> 16) & 0xff; this.low = ~this.low;
bytes[offset + 2] = (this.high >> 8) & 0xff; }
bytes[offset + 3] = this.high & 0xff;
bytes[offset + 4] = (this.low >>> 24) & 0xff;
bytes[offset + 5] = (this.low >> 16) & 0xff;
bytes[offset + 6] = (this.low >> 8) & 0xff;
bytes[offset + 7] = this.low & 0xff;
},
assign: function Word64_assign(word) { add(word) {
this.high = word.high; var lowAdd = (this.low >>> 0) + (word.low >>> 0);
this.low = word.low; var highAdd = (this.high >>> 0) + (word.high >>> 0);
}, if (lowAdd > 0xffffffff) {
}; highAdd += 1;
return Word64; }
})(); this.low = lowAdd | 0;
this.high = highAdd | 0;
}
copyTo(bytes, offset) {
bytes[offset] = (this.high >>> 24) & 0xff;
bytes[offset + 1] = (this.high >> 16) & 0xff;
bytes[offset + 2] = (this.high >> 8) & 0xff;
bytes[offset + 3] = this.high & 0xff;
bytes[offset + 4] = (this.low >>> 24) & 0xff;
bytes[offset + 5] = (this.low >> 16) & 0xff;
bytes[offset + 6] = (this.low >> 8) & 0xff;
bytes[offset + 7] = this.low & 0xff;
}
assign(word) {
this.high = word.high;
this.low = word.low;
}
}
var calculateSHA256 = (function calculateSHA256Closure() { var calculateSHA256 = (function calculateSHA256Closure() {
function rotr(x, n) { function rotr(x, n) {
@ -521,8 +521,7 @@ var calculateSHA512 = (function calculateSHA512Closure() {
new Word64(0x4cc5d4be, 0xcb3e42b6), new Word64(0x597f299c, 0xfc657e2a), new Word64(0x4cc5d4be, 0xcb3e42b6), new Word64(0x597f299c, 0xfc657e2a),
new Word64(0x5fcb6fab, 0x3ad6faec), new Word64(0x6c44198c, 0x4a475817)]; new Word64(0x5fcb6fab, 0x3ad6faec), new Word64(0x6c44198c, 0x4a475817)];
function hash(data, offset, length, mode384) { function hash(data, offset, length, mode384 = false) {
mode384 = !!mode384;
// initial hash values // initial hash values
var h0, h1, h2, h3, h4, h5, h6, h7; var h0, h1, h2, h3, h4, h5, h6, h7;
if (!mode384) { if (!mode384) {
@ -687,28 +686,20 @@ var calculateSHA512 = (function calculateSHA512Closure() {
return hash; return hash;
})(); })();
var calculateSHA384 = (function calculateSHA384Closure() {
function hash(data, offset, length) { function calculateSHA384(data, offset, length) {
return calculateSHA512(data, offset, length, true); return calculateSHA512(data, offset, length, /* mode384 = */ true);
}
class NullCipher {
decryptBlock(data) {
return data;
} }
return hash; encrypt(data) {
})(); return data;
var NullCipher = (function NullCipherClosure() { }
// eslint-disable-next-line no-shadow }
function NullCipher() {}
NullCipher.prototype = {
decryptBlock: function NullCipher_decryptBlock(data) {
return data;
},
encrypt: function NullCipher_encrypt(data) {
return data;
},
};
return NullCipher;
})();
class AESBaseCipher { class AESBaseCipher {
constructor() { constructor() {
@ -1262,65 +1253,44 @@ class AES256Cipher extends AESBaseCipher {
} }
} }
var PDF17 = (function PDF17Closure() { class PDF17 {
// eslint-disable-next-line no-shadow checkOwnerPassword(password, ownerValidationSalt, userBytes, ownerPassword) {
function PDF17() {} var hashData = new Uint8Array(password.length + 56);
hashData.set(password, 0);
hashData.set(ownerValidationSalt, password.length);
hashData.set(userBytes, password.length + ownerValidationSalt.length);
var result = calculateSHA256(hashData, 0, hashData.length);
return isArrayEqual(result, ownerPassword);
}
PDF17.prototype = { checkUserPassword(password, userValidationSalt, userPassword) {
checkOwnerPassword: function PDF17_checkOwnerPassword( var hashData = new Uint8Array(password.length + 8);
password, hashData.set(password, 0);
ownerValidationSalt, hashData.set(userValidationSalt, password.length);
userBytes, var result = calculateSHA256(hashData, 0, hashData.length);
ownerPassword return isArrayEqual(result, userPassword);
) { }
var hashData = new Uint8Array(password.length + 56);
hashData.set(password, 0); getOwnerKey(password, ownerKeySalt, userBytes, ownerEncryption) {
hashData.set(ownerValidationSalt, password.length); var hashData = new Uint8Array(password.length + 56);
hashData.set(userBytes, password.length + ownerValidationSalt.length); hashData.set(password, 0);
var result = calculateSHA256(hashData, 0, hashData.length); hashData.set(ownerKeySalt, password.length);
return isArrayEqual(result, ownerPassword); hashData.set(userBytes, password.length + ownerKeySalt.length);
}, var key = calculateSHA256(hashData, 0, hashData.length);
checkUserPassword: function PDF17_checkUserPassword( var cipher = new AES256Cipher(key);
password, return cipher.decryptBlock(ownerEncryption, false, new Uint8Array(16));
userValidationSalt, }
userPassword
) { getUserKey(password, userKeySalt, userEncryption) {
var hashData = new Uint8Array(password.length + 8); var hashData = new Uint8Array(password.length + 8);
hashData.set(password, 0); hashData.set(password, 0);
hashData.set(userValidationSalt, password.length); hashData.set(userKeySalt, password.length);
var result = calculateSHA256(hashData, 0, hashData.length); // `key` is the decryption key for the UE string.
return isArrayEqual(result, userPassword); var key = calculateSHA256(hashData, 0, hashData.length);
}, var cipher = new AES256Cipher(key);
getOwnerKey: function PDF17_getOwnerKey( return cipher.decryptBlock(userEncryption, false, new Uint8Array(16));
password, }
ownerKeySalt, }
userBytes,
ownerEncryption
) {
var hashData = new Uint8Array(password.length + 56);
hashData.set(password, 0);
hashData.set(ownerKeySalt, password.length);
hashData.set(userBytes, password.length + ownerKeySalt.length);
var key = calculateSHA256(hashData, 0, hashData.length);
var cipher = new AES256Cipher(key);
return cipher.decryptBlock(ownerEncryption, false, new Uint8Array(16));
},
getUserKey: function PDF17_getUserKey(
password,
userKeySalt,
userEncryption
) {
var hashData = new Uint8Array(password.length + 8);
hashData.set(password, 0);
hashData.set(userKeySalt, password.length);
// `key` is the decryption key for the UE string.
var key = calculateSHA256(hashData, 0, hashData.length);
var cipher = new AES256Cipher(key);
return cipher.decryptBlock(userEncryption, false, new Uint8Array(16));
},
};
return PDF17;
})();
var PDF20 = (function PDF20Closure() { var PDF20 = (function PDF20Closure() {
function calculatePDF20Hash(password, input, userBytes) { function calculatePDF20Hash(password, input, userBytes) {
@ -1370,13 +1340,12 @@ var PDF20 = (function PDF20Closure() {
} }
// eslint-disable-next-line no-shadow // eslint-disable-next-line no-shadow
function PDF20() {} class PDF20 {
hash(password, concatBytes, userBytes) {
PDF20.prototype = {
hash: function PDF20_hash(password, concatBytes, userBytes) {
return calculatePDF20Hash(password, concatBytes, userBytes); return calculatePDF20Hash(password, concatBytes, userBytes);
}, }
checkOwnerPassword: function PDF20_checkOwnerPassword(
checkOwnerPassword(
password, password,
ownerValidationSalt, ownerValidationSalt,
userBytes, userBytes,
@ -1388,24 +1357,17 @@ var PDF20 = (function PDF20Closure() {
hashData.set(userBytes, password.length + ownerValidationSalt.length); hashData.set(userBytes, password.length + ownerValidationSalt.length);
var result = calculatePDF20Hash(password, hashData, userBytes); var result = calculatePDF20Hash(password, hashData, userBytes);
return isArrayEqual(result, ownerPassword); return isArrayEqual(result, ownerPassword);
}, }
checkUserPassword: function PDF20_checkUserPassword(
password, checkUserPassword(password, userValidationSalt, userPassword) {
userValidationSalt,
userPassword
) {
var hashData = new Uint8Array(password.length + 8); var hashData = new Uint8Array(password.length + 8);
hashData.set(password, 0); hashData.set(password, 0);
hashData.set(userValidationSalt, password.length); hashData.set(userValidationSalt, password.length);
var result = calculatePDF20Hash(password, hashData, []); var result = calculatePDF20Hash(password, hashData, []);
return isArrayEqual(result, userPassword); return isArrayEqual(result, userPassword);
}, }
getOwnerKey: function PDF20_getOwnerKey(
password, getOwnerKey(password, ownerKeySalt, userBytes, ownerEncryption) {
ownerKeySalt,
userBytes,
ownerEncryption
) {
var hashData = new Uint8Array(password.length + 56); var hashData = new Uint8Array(password.length + 56);
hashData.set(password, 0); hashData.set(password, 0);
hashData.set(ownerKeySalt, password.length); hashData.set(ownerKeySalt, password.length);
@ -1413,12 +1375,9 @@ var PDF20 = (function PDF20Closure() {
var key = calculatePDF20Hash(password, hashData, userBytes); var key = calculatePDF20Hash(password, hashData, userBytes);
var cipher = new AES256Cipher(key); var cipher = new AES256Cipher(key);
return cipher.decryptBlock(ownerEncryption, false, new Uint8Array(16)); return cipher.decryptBlock(ownerEncryption, false, new Uint8Array(16));
}, }
getUserKey: function PDF20_getUserKey(
password, getUserKey(password, userKeySalt, userEncryption) {
userKeySalt,
userEncryption
) {
var hashData = new Uint8Array(password.length + 8); var hashData = new Uint8Array(password.length + 8);
hashData.set(password, 0); hashData.set(password, 0);
hashData.set(userKeySalt, password.length); hashData.set(userKeySalt, password.length);
@ -1426,74 +1385,73 @@ var PDF20 = (function PDF20Closure() {
var key = calculatePDF20Hash(password, hashData, []); var key = calculatePDF20Hash(password, hashData, []);
var cipher = new AES256Cipher(key); var cipher = new AES256Cipher(key);
return cipher.decryptBlock(userEncryption, false, new Uint8Array(16)); return cipher.decryptBlock(userEncryption, false, new Uint8Array(16));
}, }
}; }
return PDF20; return PDF20;
})(); })();
var CipherTransform = (function CipherTransformClosure() { class CipherTransform {
// eslint-disable-next-line no-shadow constructor(stringCipherConstructor, streamCipherConstructor) {
function CipherTransform(stringCipherConstructor, streamCipherConstructor) {
this.StringCipherConstructor = stringCipherConstructor; this.StringCipherConstructor = stringCipherConstructor;
this.StreamCipherConstructor = streamCipherConstructor; this.StreamCipherConstructor = streamCipherConstructor;
} }
CipherTransform.prototype = { createStream(stream, length) {
createStream: function CipherTransform_createStream(stream, length) { var cipher = new this.StreamCipherConstructor();
var cipher = new this.StreamCipherConstructor(); return new DecryptStream(
return new DecryptStream( stream,
stream, length,
length, function cipherTransformDecryptStream(data, finalize) {
function cipherTransformDecryptStream(data, finalize) { return cipher.decryptBlock(data, finalize);
return cipher.decryptBlock(data, finalize); }
);
}
decryptString(s) {
var cipher = new this.StringCipherConstructor();
var data = stringToBytes(s);
data = cipher.decryptBlock(data, true);
return bytesToString(data);
}
encryptString(s) {
const cipher = new this.StringCipherConstructor();
if (cipher instanceof AESBaseCipher) {
// Append some chars equal to "16 - (M mod 16)"
// where M is the string length (see section 7.6.2 in PDF specification)
// to have a final string where the length is a multiple of 16.
const strLen = s.length;
const pad = 16 - (strLen % 16);
if (pad !== 16) {
s = s.padEnd(16 * Math.ceil(strLen / 16), String.fromCharCode(pad));
}
// Generate an initialization vector
const iv = new Uint8Array(16);
if (typeof crypto !== "undefined") {
crypto.getRandomValues(iv);
} else {
for (let i = 0; i < 16; i++) {
iv[i] = Math.floor(256 * Math.random());
} }
);
},
decryptString: function CipherTransform_decryptString(s) {
var cipher = new this.StringCipherConstructor();
var data = stringToBytes(s);
data = cipher.decryptBlock(data, true);
return bytesToString(data);
},
encryptString: function CipherTransform_encryptString(s) {
const cipher = new this.StringCipherConstructor();
if (cipher instanceof AESBaseCipher) {
// Append some chars equal to "16 - (M mod 16)"
// where M is the string length (see section 7.6.2 in PDF specification)
// to have a final string where the length is a multiple of 16.
const strLen = s.length;
const pad = 16 - (strLen % 16);
if (pad !== 16) {
s = s.padEnd(16 * Math.ceil(strLen / 16), String.fromCharCode(pad));
}
// Generate an initialization vector
const iv = new Uint8Array(16);
if (typeof crypto !== "undefined") {
crypto.getRandomValues(iv);
} else {
for (let i = 0; i < 16; i++) {
iv[i] = Math.floor(256 * Math.random());
}
}
let data = stringToBytes(s);
data = cipher.encrypt(data, iv);
const buf = new Uint8Array(16 + data.length);
buf.set(iv);
buf.set(data, 16);
return bytesToString(buf);
} }
let data = stringToBytes(s); let data = stringToBytes(s);
data = cipher.encrypt(data); data = cipher.encrypt(data, iv);
return bytesToString(data);
}, const buf = new Uint8Array(16 + data.length);
}; buf.set(iv);
return CipherTransform; buf.set(data, 16);
})();
return bytesToString(buf);
}
let data = stringToBytes(s);
data = cipher.encrypt(data);
return bytesToString(data);
}
}
var CipherTransformFactory = (function CipherTransformFactoryClosure() { var CipherTransformFactory = (function CipherTransformFactoryClosure() {
// prettier-ignore // prettier-ignore
@ -1684,161 +1642,7 @@ var CipherTransformFactory = (function CipherTransformFactoryClosure() {
var identityName = Name.get("Identity"); var identityName = Name.get("Identity");
// eslint-disable-next-line no-shadow function buildObjectKey(num, gen, encryptionKey, isAes = false) {
function CipherTransformFactory(dict, fileId, password) {
var filter = dict.get("Filter");
if (!isName(filter, "Standard")) {
throw new FormatError("unknown encryption method");
}
this.dict = dict;
var 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;
var 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.
var cfDict = dict.get("CF");
var streamCryptoName = dict.get("StmF");
if (isDict(cfDict) && isName(streamCryptoName)) {
cfDict.suppressEncryption = true; // See comment below.
var handlerDict = cfDict.get(streamCryptoName.name);
keyLength = (handlerDict && 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");
}
// prepare keys
var ownerPassword = stringToBytes(dict.get("O")).subarray(0, 32);
var userPassword = stringToBytes(dict.get("U")).subarray(0, 32);
var flags = dict.get("P");
var revision = dict.get("R");
// meaningful when V is 4 or 5
var encryptMetadata =
(algorithm === 4 || algorithm === 5) &&
dict.get("EncryptMetadata") !== false;
this.encryptMetadata = encryptMetadata;
var fileIdBytes = stringToBytes(fileId);
var passwordBytes;
if (password) {
if (revision === 6) {
try {
password = utf8StringToString(password);
} catch (ex) {
warn(
"CipherTransformFactory: " +
"Unable to convert UTF8 encoded password."
);
}
}
passwordBytes = stringToBytes(password);
}
var encryptionKey;
if (algorithm !== 5) {
encryptionKey = prepareKeyData(
fileIdBytes,
passwordBytes,
ownerPassword,
userPassword,
flags,
revision,
keyLength,
encryptMetadata
);
} else {
var ownerValidationSalt = stringToBytes(dict.get("O")).subarray(32, 40);
var ownerKeySalt = stringToBytes(dict.get("O")).subarray(40, 48);
var uBytes = stringToBytes(dict.get("U")).subarray(0, 48);
var userValidationSalt = stringToBytes(dict.get("U")).subarray(32, 40);
var userKeySalt = stringToBytes(dict.get("U")).subarray(40, 48);
var ownerEncryption = stringToBytes(dict.get("OE"));
var userEncryption = stringToBytes(dict.get("UE"));
var perms = stringToBytes(dict.get("Perms"));
encryptionKey = 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
var 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) {
var cf = dict.get("CF");
if (isDict(cf)) {
// 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;
}
}
function buildObjectKey(num, gen, encryptionKey, isAes) {
var key = new Uint8Array(encryptionKey.length + 9), var key = new Uint8Array(encryptionKey.length + 9),
i, i,
n; n;
@ -1876,12 +1680,16 @@ var CipherTransformFactory = (function CipherTransformFactoryClosure() {
} }
if (cfm.name === "V2") { if (cfm.name === "V2") {
return function cipherTransformFactoryBuildCipherConstructorV2() { return function cipherTransformFactoryBuildCipherConstructorV2() {
return new ARCFourCipher(buildObjectKey(num, gen, key, false)); return new ARCFourCipher(
buildObjectKey(num, gen, key, /* isAes = */ false)
);
}; };
} }
if (cfm.name === "AESV2") { if (cfm.name === "AESV2") {
return function cipherTransformFactoryBuildCipherConstructorAESV2() { return function cipherTransformFactoryBuildCipherConstructorAESV2() {
return new AES128Cipher(buildObjectKey(num, gen, key, true)); return new AES128Cipher(
buildObjectKey(num, gen, key, /* isAes = */ true)
);
}; };
} }
if (cfm.name === "AESV3") { if (cfm.name === "AESV3") {
@ -1892,11 +1700,170 @@ var CipherTransformFactory = (function CipherTransformFactoryClosure() {
throw new FormatError("Unknown crypto method"); throw new FormatError("Unknown crypto method");
} }
CipherTransformFactory.prototype = { // eslint-disable-next-line no-shadow
createCipherTransform: function CipherTransformFactory_createCipherTransform( class CipherTransformFactory {
num, constructor(dict, fileId, password) {
gen var filter = dict.get("Filter");
) { if (!isName(filter, "Standard")) {
throw new FormatError("unknown encryption method");
}
this.dict = dict;
var 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;
var 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.
var cfDict = dict.get("CF");
var streamCryptoName = dict.get("StmF");
if (isDict(cfDict) && isName(streamCryptoName)) {
cfDict.suppressEncryption = true; // See comment below.
var handlerDict = cfDict.get(streamCryptoName.name);
keyLength = (handlerDict && 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");
}
// prepare keys
var ownerPassword = stringToBytes(dict.get("O")).subarray(0, 32);
var userPassword = stringToBytes(dict.get("U")).subarray(0, 32);
var flags = dict.get("P");
var revision = dict.get("R");
// meaningful when V is 4 or 5
var encryptMetadata =
(algorithm === 4 || algorithm === 5) &&
dict.get("EncryptMetadata") !== false;
this.encryptMetadata = encryptMetadata;
var fileIdBytes = stringToBytes(fileId);
var passwordBytes;
if (password) {
if (revision === 6) {
try {
password = utf8StringToString(password);
} catch (ex) {
warn(
"CipherTransformFactory: " +
"Unable to convert UTF8 encoded password."
);
}
}
passwordBytes = stringToBytes(password);
}
var encryptionKey;
if (algorithm !== 5) {
encryptionKey = prepareKeyData(
fileIdBytes,
passwordBytes,
ownerPassword,
userPassword,
flags,
revision,
keyLength,
encryptMetadata
);
} else {
var ownerValidationSalt = stringToBytes(dict.get("O")).subarray(32, 40);
var ownerKeySalt = stringToBytes(dict.get("O")).subarray(40, 48);
var uBytes = stringToBytes(dict.get("U")).subarray(0, 48);
var userValidationSalt = stringToBytes(dict.get("U")).subarray(32, 40);
var userKeySalt = stringToBytes(dict.get("U")).subarray(40, 48);
var ownerEncryption = stringToBytes(dict.get("OE"));
var userEncryption = stringToBytes(dict.get("UE"));
var perms = stringToBytes(dict.get("Perms"));
encryptionKey = 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
var 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) {
var cf = dict.get("CF");
if (isDict(cf)) {
// 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;
}
}
createCipherTransform(num, gen) {
if (this.algorithm === 4 || this.algorithm === 5) { if (this.algorithm === 4 || this.algorithm === 5) {
return new CipherTransform( return new CipherTransform(
buildCipherConstructor( buildCipherConstructor(
@ -1916,13 +1883,18 @@ var CipherTransformFactory = (function CipherTransformFactoryClosure() {
); );
} }
// algorithms 1 and 2 // algorithms 1 and 2
var key = buildObjectKey(num, gen, this.encryptionKey, false); var key = buildObjectKey(
num,
gen,
this.encryptionKey,
/* isAes = */ false
);
var cipherConstructor = function buildCipherCipherConstructor() { var cipherConstructor = function buildCipherCipherConstructor() {
return new ARCFourCipher(key); return new ARCFourCipher(key);
}; };
return new CipherTransform(cipherConstructor, cipherConstructor); return new CipherTransform(cipherConstructor, cipherConstructor);
}, }
}; }
return CipherTransformFactory; return CipherTransformFactory;
})(); })();