Correctly pad strings when saving an encrypted pdf (bug 1726789)

This commit is contained in:
Calixte Denizet 2021-09-01 21:55:21 +02:00
parent 2ed133bd99
commit 9619bf92be
2 changed files with 88 additions and 3 deletions

View File

@ -1407,11 +1407,12 @@ class CipherTransform {
// Append some chars equal to "16 - (M mod 16)" // Append some chars equal to "16 - (M mod 16)"
// where M is the string length (see section 7.6.2 in PDF specification) // 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. // to have a final string where the length is a multiple of 16.
// Special note:
// "Note that the pad is present when M is evenly divisible by 16;
// it contains 16 bytes of 0x10."
const strLen = s.length; const strLen = s.length;
const pad = 16 - (strLen % 16); const pad = 16 - (strLen % 16);
if (pad !== 16) { s += String.fromCharCode(pad).repeat(pad);
s = s.padEnd(16 * Math.ceil(strLen / 16), String.fromCharCode(pad));
}
// Generate an initialization vector // Generate an initialization vector
const iv = new Uint8Array(16); const iv = new Uint8Array(16);

View File

@ -581,6 +581,30 @@ describe("CipherTransformFactory", function () {
} }
} }
function ensureAESEncryptedStringHasCorrectLength(
dict,
fileId,
password,
string
) {
const factory = new CipherTransformFactory(dict, fileId, password);
const cipher = factory.createCipherTransform(123, 0);
const encrypted = cipher.encryptString(string);
// The final length is a multiple of 16.
// If the initial string has a length which is a multiple of 16
// then 16 chars of padding are added.
// So we've the mapping:
// - length: [0-15] => new length: 16
// - length: [16-31] => new length: 32
// - length: [32-47] => new length: 48
// ...
expect(encrypted.length).toEqual(
16 /* initialization vector length */ +
16 * Math.ceil((string.length + 1) / 16)
);
}
function ensureEncryptDecryptIsIdentity(dict, fileId, password, string) { function ensureEncryptDecryptIsIdentity(dict, fileId, password, string) {
const factory = new CipherTransformFactory(dict, fileId, password); const factory = new CipherTransformFactory(dict, fileId, password);
const cipher = factory.createCipherTransform(123, 0); const cipher = factory.createCipherTransform(123, 0);
@ -807,6 +831,8 @@ describe("CipherTransformFactory", function () {
}), }),
}); });
const dict = buildDict(dict3); const dict = buildDict(dict3);
// 0 char
ensureEncryptDecryptIsIdentity(dict, fileId1, "user", "");
// 1 char // 1 char
ensureEncryptDecryptIsIdentity(dict, fileId1, "user", "a"); ensureEncryptDecryptIsIdentity(dict, fileId1, "user", "a");
// 2 chars // 2 chars
@ -828,6 +854,8 @@ describe("CipherTransformFactory", function () {
}), }),
}); });
const dict = buildDict(dict3); const dict = buildDict(dict3);
// 0 chars
ensureEncryptDecryptIsIdentity(dict, fileId1, "user", "");
// 4 chars // 4 chars
ensureEncryptDecryptIsIdentity(dict, fileId1, "user", "aaaa"); ensureEncryptDecryptIsIdentity(dict, fileId1, "user", "aaaa");
// 5 chars // 5 chars
@ -842,5 +870,61 @@ describe("CipherTransformFactory", function () {
"aaaaaaaaaaaaaaaaaaaaaa" "aaaaaaaaaaaaaaaaaaaaaa"
); );
}); });
it("should encrypt and have the correct length using AES128", function () {
dict3.CF = buildDict({
Identity: buildDict({
CFM: Name.get("AESV2"),
}),
});
const dict = buildDict(dict3);
// 0 char
ensureAESEncryptedStringHasCorrectLength(dict, fileId1, "user", "");
// 1 char
ensureAESEncryptedStringHasCorrectLength(dict, fileId1, "user", "a");
// 2 chars
ensureAESEncryptedStringHasCorrectLength(dict, fileId1, "user", "aa");
// 16 chars
ensureAESEncryptedStringHasCorrectLength(
dict,
fileId1,
"user",
"aaaaaaaaaaaaaaaa"
);
// 19 chars
ensureAESEncryptedStringHasCorrectLength(
dict,
fileId1,
"user",
"aaaaaaaaaaaaaaaaaaa"
);
});
it("should encrypt and have the correct length using AES256", function () {
dict3.CF = buildDict({
Identity: buildDict({
CFM: Name.get("AESV3"),
}),
});
const dict = buildDict(dict3);
// 0 char
ensureAESEncryptedStringHasCorrectLength(dict, fileId1, "user", "");
// 4 chars
ensureAESEncryptedStringHasCorrectLength(dict, fileId1, "user", "aaaa");
// 5 chars
ensureAESEncryptedStringHasCorrectLength(dict, fileId1, "user", "aaaaa");
// 16 chars
ensureAESEncryptedStringHasCorrectLength(
dict,
fileId1,
"user",
"aaaaaaaaaaaaaaaa"
);
// 22 chars
ensureAESEncryptedStringHasCorrectLength(
dict,
fileId1,
"user",
"aaaaaaaaaaaaaaaaaaaaaa"
);
});
}); });
}); });