Correctly pad strings when saving an encrypted pdf (bug 1726789)
This commit is contained in:
parent
2ed133bd99
commit
9619bf92be
@ -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);
|
||||||
|
@ -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"
|
||||||
|
);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
Loading…
Reference in New Issue
Block a user