Merge pull request #13026 from Snuffleupagus/crypto-classes

Convert code in `src/core/crypto.js` to use "normal" classes
This commit is contained in:
Tim van der Meij 2021-02-26 22:39:30 +01:00 committed by GitHub
commit 55786a4880
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 425 additions and 475 deletions

View File

@ -17,6 +17,7 @@
import {
bytesToString,
FormatError,
isArrayEqual,
PasswordException,
PasswordResponses,
stringToBytes,
@ -27,9 +28,8 @@ import {
import { isDict, isName, Name } from "./primitives.js";
import { DecryptStream } from "./stream.js";
var ARCFourCipher = (function ARCFourCipherClosure() {
// eslint-disable-next-line no-shadow
function ARCFourCipher(key) {
class ARCFourCipher {
constructor(key) {
this.a = 0;
this.b = 0;
var s = new Uint8Array(256);
@ -49,8 +49,7 @@ var ARCFourCipher = (function ARCFourCipherClosure() {
this.s = s;
}
ARCFourCipher.prototype = {
encryptBlock: function ARCFourCipher_encryptBlock(data) {
encryptBlock(data) {
var i,
n = data.length,
tmp,
@ -71,13 +70,16 @@ var ARCFourCipher = (function ARCFourCipherClosure() {
this.a = a;
this.b = b;
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() {
// prettier-ignore
@ -179,28 +181,29 @@ var calculateMD5 = (function calculateMD5Closure() {
return hash;
})();
var Word64 = (function Word64Closure() {
// eslint-disable-next-line no-shadow
function Word64(highInteger, lowInteger) {
class Word64 {
constructor(highInteger, lowInteger) {
this.high = highInteger | 0;
this.low = lowInteger | 0;
}
Word64.prototype = {
and: function Word64_and(word) {
and(word) {
this.high &= word.high;
this.low &= word.low;
},
xor: function Word64_xor(word) {
}
xor(word) {
this.high ^= word.high;
this.low ^= word.low;
},
}
or: function Word64_or(word) {
or(word) {
this.high |= word.high;
this.low |= word.low;
},
}
shiftRight: function Word64_shiftRight(places) {
shiftRight(places) {
if (places >= 32) {
this.low = (this.high >>> (places - 32)) | 0;
this.high = 0;
@ -208,9 +211,9 @@ var Word64 = (function Word64Closure() {
this.low = (this.low >>> places) | (this.high << (32 - places));
this.high = (this.high >>> places) | 0;
}
},
}
shiftLeft: function Word64_shiftLeft(places) {
shiftLeft(places) {
if (places >= 32) {
this.high = this.low << (places - 32);
this.low = 0;
@ -218,9 +221,9 @@ var Word64 = (function Word64Closure() {
this.high = (this.high << places) | (this.low >>> (32 - places));
this.low = this.low << places;
}
},
}
rotateRight: function Word64_rotateRight(places) {
rotateRight(places) {
var low, high;
if (places & 32) {
high = this.low;
@ -232,14 +235,14 @@ var Word64 = (function Word64Closure() {
places &= 31;
this.low = (low >>> places) | (high << (32 - places));
this.high = (high >>> places) | (low << (32 - places));
},
}
not: function Word64_not() {
not() {
this.high = ~this.high;
this.low = ~this.low;
},
}
add: function Word64_add(word) {
add(word) {
var lowAdd = (this.low >>> 0) + (word.low >>> 0);
var highAdd = (this.high >>> 0) + (word.high >>> 0);
if (lowAdd > 0xffffffff) {
@ -247,9 +250,9 @@ var Word64 = (function Word64Closure() {
}
this.low = lowAdd | 0;
this.high = highAdd | 0;
},
}
copyTo: function Word64_copyTo(bytes, offset) {
copyTo(bytes, offset) {
bytes[offset] = (this.high >>> 24) & 0xff;
bytes[offset + 1] = (this.high >> 16) & 0xff;
bytes[offset + 2] = (this.high >> 8) & 0xff;
@ -258,15 +261,13 @@ var Word64 = (function Word64Closure() {
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) {
assign(word) {
this.high = word.high;
this.low = word.low;
},
};
return Word64;
})();
}
}
var calculateSHA256 = (function calculateSHA256Closure() {
function rotr(x, n) {
@ -520,8 +521,7 @@ var calculateSHA512 = (function calculateSHA512Closure() {
new Word64(0x4cc5d4be, 0xcb3e42b6), new Word64(0x597f299c, 0xfc657e2a),
new Word64(0x5fcb6fab, 0x3ad6faec), new Word64(0x6c44198c, 0x4a475817)];
function hash(data, offset, length, mode384) {
mode384 = !!mode384;
function hash(data, offset, length, mode384 = false) {
// initial hash values
var h0, h1, h2, h3, h4, h5, h6, h7;
if (!mode384) {
@ -686,28 +686,20 @@ var calculateSHA512 = (function calculateSHA512Closure() {
return hash;
})();
var calculateSHA384 = (function calculateSHA384Closure() {
function hash(data, offset, length) {
return calculateSHA512(data, offset, length, true);
function calculateSHA384(data, offset, length) {
return calculateSHA512(data, offset, length, /* mode384 = */ true);
}
return hash;
})();
var NullCipher = (function NullCipherClosure() {
// eslint-disable-next-line no-shadow
function NullCipher() {}
NullCipher.prototype = {
decryptBlock: function NullCipher_decryptBlock(data) {
class NullCipher {
decryptBlock(data) {
return data;
},
encrypt: function NullCipher_encrypt(data) {
return data;
},
};
}
return NullCipher;
})();
encrypt(data) {
return data;
}
}
class AESBaseCipher {
constructor() {
@ -1261,53 +1253,25 @@ class AES256Cipher extends AESBaseCipher {
}
}
var PDF17 = (function PDF17Closure() {
function compareByteArrays(array1, array2) {
if (array1.length !== array2.length) {
return false;
}
for (var i = 0; i < array1.length; i++) {
if (array1[i] !== array2[i]) {
return false;
}
}
return true;
}
// eslint-disable-next-line no-shadow
function PDF17() {}
PDF17.prototype = {
checkOwnerPassword: function PDF17_checkOwnerPassword(
password,
ownerValidationSalt,
userBytes,
ownerPassword
) {
class PDF17 {
checkOwnerPassword(password, ownerValidationSalt, userBytes, ownerPassword) {
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 compareByteArrays(result, ownerPassword);
},
checkUserPassword: function PDF17_checkUserPassword(
password,
userValidationSalt,
userPassword
) {
return isArrayEqual(result, ownerPassword);
}
checkUserPassword(password, userValidationSalt, userPassword) {
var hashData = new Uint8Array(password.length + 8);
hashData.set(password, 0);
hashData.set(userValidationSalt, password.length);
var result = calculateSHA256(hashData, 0, hashData.length);
return compareByteArrays(result, userPassword);
},
getOwnerKey: function PDF17_getOwnerKey(
password,
ownerKeySalt,
userBytes,
ownerEncryption
) {
return isArrayEqual(result, userPassword);
}
getOwnerKey(password, ownerKeySalt, userBytes, ownerEncryption) {
var hashData = new Uint8Array(password.length + 56);
hashData.set(password, 0);
hashData.set(ownerKeySalt, password.length);
@ -1315,12 +1279,9 @@ var PDF17 = (function PDF17Closure() {
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
) {
}
getUserKey(password, userKeySalt, userEncryption) {
var hashData = new Uint8Array(password.length + 8);
hashData.set(password, 0);
hashData.set(userKeySalt, password.length);
@ -1328,32 +1289,28 @@ var PDF17 = (function PDF17Closure() {
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() {
function concatArrays(array1, array2) {
var t = new Uint8Array(array1.length + array2.length);
t.set(array1, 0);
t.set(array2, array1.length);
return t;
}
}
var PDF20 = (function PDF20Closure() {
function calculatePDF20Hash(password, input, userBytes) {
// This refers to Algorithm 2.B as defined in ISO 32000-2.
var k = calculateSHA256(input, 0, input.length).subarray(0, 32);
var e = [0];
var i = 0;
while (i < 64 || e[e.length - 1] > i - 32) {
var arrayLength = password.length + k.length + userBytes.length;
const combinedLength = password.length + k.length + userBytes.length,
combinedArray = new Uint8Array(combinedLength);
let writeOffset = 0;
combinedArray.set(password, writeOffset);
writeOffset += password.length;
combinedArray.set(k, writeOffset);
writeOffset += k.length;
combinedArray.set(userBytes, writeOffset);
var k1 = new Uint8Array(arrayLength * 64);
var array = concatArrays(password, k);
array = concatArrays(array, userBytes);
for (var j = 0, pos = 0; j < 64; j++, pos += arrayLength) {
k1.set(array, pos);
var k1 = new Uint8Array(combinedLength * 64);
for (var j = 0, pos = 0; j < 64; j++, pos += combinedLength) {
k1.set(combinedArray, pos);
}
// AES128 CBC NO PADDING with first 16 bytes of k as the key
// and the second 16 as the iv.
@ -1383,25 +1340,12 @@ var PDF20 = (function PDF20Closure() {
}
// eslint-disable-next-line no-shadow
function PDF20() {}
function compareByteArrays(array1, array2) {
if (array1.length !== array2.length) {
return false;
}
for (var i = 0; i < array1.length; i++) {
if (array1[i] !== array2[i]) {
return false;
}
}
return true;
}
PDF20.prototype = {
hash: function PDF20_hash(password, concatBytes, userBytes) {
class PDF20 {
hash(password, concatBytes, userBytes) {
return calculatePDF20Hash(password, concatBytes, userBytes);
},
checkOwnerPassword: function PDF20_checkOwnerPassword(
}
checkOwnerPassword(
password,
ownerValidationSalt,
userBytes,
@ -1412,25 +1356,18 @@ var PDF20 = (function PDF20Closure() {
hashData.set(ownerValidationSalt, password.length);
hashData.set(userBytes, password.length + ownerValidationSalt.length);
var result = calculatePDF20Hash(password, hashData, userBytes);
return compareByteArrays(result, ownerPassword);
},
checkUserPassword: function PDF20_checkUserPassword(
password,
userValidationSalt,
userPassword
) {
return isArrayEqual(result, ownerPassword);
}
checkUserPassword(password, userValidationSalt, userPassword) {
var hashData = new Uint8Array(password.length + 8);
hashData.set(password, 0);
hashData.set(userValidationSalt, password.length);
var result = calculatePDF20Hash(password, hashData, []);
return compareByteArrays(result, userPassword);
},
getOwnerKey: function PDF20_getOwnerKey(
password,
ownerKeySalt,
userBytes,
ownerEncryption
) {
return isArrayEqual(result, userPassword);
}
getOwnerKey(password, ownerKeySalt, userBytes, ownerEncryption) {
var hashData = new Uint8Array(password.length + 56);
hashData.set(password, 0);
hashData.set(ownerKeySalt, password.length);
@ -1438,12 +1375,9 @@ var PDF20 = (function PDF20Closure() {
var key = calculatePDF20Hash(password, hashData, userBytes);
var cipher = new AES256Cipher(key);
return cipher.decryptBlock(ownerEncryption, false, new Uint8Array(16));
},
getUserKey: function PDF20_getUserKey(
password,
userKeySalt,
userEncryption
) {
}
getUserKey(password, userKeySalt, userEncryption) {
var hashData = new Uint8Array(password.length + 8);
hashData.set(password, 0);
hashData.set(userKeySalt, password.length);
@ -1451,20 +1385,19 @@ var PDF20 = (function PDF20Closure() {
var key = calculatePDF20Hash(password, hashData, []);
var cipher = new AES256Cipher(key);
return cipher.decryptBlock(userEncryption, false, new Uint8Array(16));
},
};
}
}
return PDF20;
})();
var CipherTransform = (function CipherTransformClosure() {
// eslint-disable-next-line no-shadow
function CipherTransform(stringCipherConstructor, streamCipherConstructor) {
class CipherTransform {
constructor(stringCipherConstructor, streamCipherConstructor) {
this.StringCipherConstructor = stringCipherConstructor;
this.StreamCipherConstructor = streamCipherConstructor;
}
CipherTransform.prototype = {
createStream: function CipherTransform_createStream(stream, length) {
createStream(stream, length) {
var cipher = new this.StreamCipherConstructor();
return new DecryptStream(
stream,
@ -1473,14 +1406,16 @@ var CipherTransform = (function CipherTransformClosure() {
return cipher.decryptBlock(data, finalize);
}
);
},
decryptString: function CipherTransform_decryptString(s) {
}
decryptString(s) {
var cipher = new this.StringCipherConstructor();
var data = stringToBytes(s);
data = cipher.decryptBlock(data, true);
return bytesToString(data);
},
encryptString: function CipherTransform_encryptString(s) {
}
encryptString(s) {
const cipher = new this.StringCipherConstructor();
if (cipher instanceof AESBaseCipher) {
// Append some chars equal to "16 - (M mod 16)"
@ -1515,10 +1450,8 @@ var CipherTransform = (function CipherTransformClosure() {
let data = stringToBytes(s);
data = cipher.encrypt(data);
return bytesToString(data);
},
};
return CipherTransform;
})();
}
}
var CipherTransformFactory = (function CipherTransformFactoryClosure() {
// prettier-ignore
@ -1709,8 +1642,67 @@ var CipherTransformFactory = (function CipherTransformFactoryClosure() {
var identityName = Name.get("Identity");
function buildObjectKey(num, gen, encryptionKey, isAes = false) {
var key = new Uint8Array(encryptionKey.length + 9),
i,
n;
for (i = 0, n = encryptionKey.length; i < n; ++i) {
key[i] = encryptionKey[i];
}
key[i++] = num & 0xff;
key[i++] = (num >> 8) & 0xff;
key[i++] = (num >> 16) & 0xff;
key[i++] = gen & 0xff;
key[i++] = (gen >> 8) & 0xff;
if (isAes) {
key[i++] = 0x73;
key[i++] = 0x41;
key[i++] = 0x6c;
key[i++] = 0x54;
}
var hash = calculateMD5(key, 0, i);
return hash.subarray(0, Math.min(encryptionKey.length + 5, 16));
}
function buildCipherConstructor(cf, name, num, gen, key) {
if (!isName(name)) {
throw new FormatError("Invalid crypt filter name.");
}
var cryptFilter = cf.get(name.name);
var cfm;
if (cryptFilter !== null && cryptFilter !== undefined) {
cfm = cryptFilter.get("CFM");
}
if (!cfm || cfm.name === "None") {
return function cipherTransformFactoryBuildCipherConstructorNone() {
return new NullCipher();
};
}
if (cfm.name === "V2") {
return function cipherTransformFactoryBuildCipherConstructorV2() {
return new ARCFourCipher(
buildObjectKey(num, gen, key, /* isAes = */ false)
);
};
}
if (cfm.name === "AESV2") {
return function cipherTransformFactoryBuildCipherConstructorAESV2() {
return new AES128Cipher(
buildObjectKey(num, gen, key, /* isAes = */ true)
);
};
}
if (cfm.name === "AESV3") {
return function cipherTransformFactoryBuildCipherConstructorAESV3() {
return new AES256Cipher(key);
};
}
throw new FormatError("Unknown crypto method");
}
// eslint-disable-next-line no-shadow
function CipherTransformFactory(dict, fileId, password) {
class CipherTransformFactory {
constructor(dict, fileId, password) {
var filter = dict.get("Filter");
if (!isName(filter, "Standard")) {
throw new FormatError("unknown encryption method");
@ -1719,7 +1711,10 @@ var CipherTransformFactory = (function CipherTransformFactoryClosure() {
var algorithm = dict.get("V");
if (
!Number.isInteger(algorithm) ||
(algorithm !== 1 && algorithm !== 2 && algorithm !== 4 && algorithm !== 5)
(algorithm !== 1 &&
algorithm !== 2 &&
algorithm !== 4 &&
algorithm !== 5)
) {
throw new FormatError("unsupported encryption algorithm");
}
@ -1740,13 +1735,18 @@ var CipherTransformFactory = (function CipherTransformFactoryClosure() {
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.
// Sometimes it's incorrect value of bits, generators specify
// bytes.
keyLength <<= 3;
}
}
}
}
if (!Number.isInteger(keyLength) || keyLength < 40 || keyLength % 8 !== 0) {
if (
!Number.isInteger(keyLength) ||
keyLength < 40 ||
keyLength % 8 !== 0
) {
throw new FormatError("invalid key length");
}
@ -1852,8 +1852,8 @@ var CipherTransformFactory = (function CipherTransformFactoryClosure() {
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).
// `XRef_fetchUncompressed` if the dictionary contains indirect
// objects (fixes issue7665.pdf).
cf.suppressEncryption = true;
}
this.cf = cf;
@ -1863,65 +1863,7 @@ var CipherTransformFactory = (function CipherTransformFactoryClosure() {
}
}
function buildObjectKey(num, gen, encryptionKey, isAes) {
var key = new Uint8Array(encryptionKey.length + 9),
i,
n;
for (i = 0, n = encryptionKey.length; i < n; ++i) {
key[i] = encryptionKey[i];
}
key[i++] = num & 0xff;
key[i++] = (num >> 8) & 0xff;
key[i++] = (num >> 16) & 0xff;
key[i++] = gen & 0xff;
key[i++] = (gen >> 8) & 0xff;
if (isAes) {
key[i++] = 0x73;
key[i++] = 0x41;
key[i++] = 0x6c;
key[i++] = 0x54;
}
var hash = calculateMD5(key, 0, i);
return hash.subarray(0, Math.min(encryptionKey.length + 5, 16));
}
function buildCipherConstructor(cf, name, num, gen, key) {
if (!isName(name)) {
throw new FormatError("Invalid crypt filter name.");
}
var cryptFilter = cf.get(name.name);
var cfm;
if (cryptFilter !== null && cryptFilter !== undefined) {
cfm = cryptFilter.get("CFM");
}
if (!cfm || cfm.name === "None") {
return function cipherTransformFactoryBuildCipherConstructorNone() {
return new NullCipher();
};
}
if (cfm.name === "V2") {
return function cipherTransformFactoryBuildCipherConstructorV2() {
return new ARCFourCipher(buildObjectKey(num, gen, key, false));
};
}
if (cfm.name === "AESV2") {
return function cipherTransformFactoryBuildCipherConstructorAESV2() {
return new AES128Cipher(buildObjectKey(num, gen, key, true));
};
}
if (cfm.name === "AESV3") {
return function cipherTransformFactoryBuildCipherConstructorAESV3() {
return new AES256Cipher(key);
};
}
throw new FormatError("Unknown crypto method");
}
CipherTransformFactory.prototype = {
createCipherTransform: function CipherTransformFactory_createCipherTransform(
num,
gen
) {
createCipherTransform(num, gen) {
if (this.algorithm === 4 || this.algorithm === 5) {
return new CipherTransform(
buildCipherConstructor(
@ -1941,13 +1883,18 @@ var CipherTransformFactory = (function CipherTransformFactoryClosure() {
);
}
// 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() {
return new ARCFourCipher(key);
};
return new CipherTransform(cipherConstructor, cipherConstructor);
},
};
}
}
return CipherTransformFactory;
})();

View File

@ -884,9 +884,12 @@ function isArrayEqual(arr1, arr2) {
if (arr1.length !== arr2.length) {
return false;
}
return arr1.every(function (element, index) {
return element === arr2[index];
});
for (let i = 0, ii = arr1.length; i < ii; i++) {
if (arr1[i] !== arr2[i]) {
return false;
}
}
return true;
}
function getModificationDate(date = new Date()) {