diff options
author | Luis Bernardo <lbernardo@apache.org> | 2013-05-18 22:25:52 +0000 |
---|---|---|
committer | Luis Bernardo <lbernardo@apache.org> | 2013-05-18 22:25:52 +0000 |
commit | 016cb3199865f798f401c452554eea6e74055950 (patch) | |
tree | 10635c8e78ce1124251a9e735088f20cc81619c7 /test/java | |
parent | af665b918b7a15470f9006c7d217a9fc8ea7fcbd (diff) | |
download | xmlgraphics-fop-016cb3199865f798f401c452554eea6e74055950.tar.gz xmlgraphics-fop-016cb3199865f798f401c452554eea6e74055950.zip |
FOP-2248: add support for AES 256 PDF encryption
git-svn-id: https://svn.apache.org/repos/asf/xmlgraphics/fop/trunk@1484190 13f79535-47bb-0310-9956-ffa450edef68
Diffstat (limited to 'test/java')
-rw-r--r-- | test/java/org/apache/fop/pdf/PDFEncryptionJCETestCase.java | 178 | ||||
-rw-r--r-- | test/java/org/apache/fop/render/pdf/PDFRendererConfigParserTestCase.java | 10 |
2 files changed, 170 insertions, 18 deletions
diff --git a/test/java/org/apache/fop/pdf/PDFEncryptionJCETestCase.java b/test/java/org/apache/fop/pdf/PDFEncryptionJCETestCase.java index db10e656e..ea3b011c7 100644 --- a/test/java/org/apache/fop/pdf/PDFEncryptionJCETestCase.java +++ b/test/java/org/apache/fop/pdf/PDFEncryptionJCETestCase.java @@ -19,16 +19,28 @@ package org.apache.fop.pdf; +import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.UnsupportedEncodingException; +import java.security.InvalidAlgorithmParameterException; +import java.security.InvalidKeyException; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; import java.util.Arrays; import java.util.regex.Matcher; import java.util.regex.Pattern; +import javax.crypto.BadPaddingException; +import javax.crypto.Cipher; +import javax.crypto.IllegalBlockSizeException; +import javax.crypto.NoSuchPaddingException; +import javax.crypto.spec.IvParameterSpec; +import javax.crypto.spec.SecretKeySpec; + import org.junit.Test; /** @@ -171,19 +183,17 @@ public class PDFEncryptionJCETestCase { private static final class EncryptionDictionaryTester { - private int version = 1; + private int version = 2; - private int revision = 2; + private int revision = 3; - private int length = 40; + private int length = 128; private int permissions = -4; - private String ownerEntry - = "3EE8C4000CA44B2645EED029C9EA7D4FC63C6D9B89349E8FA5A40C7691AB96B5"; + private String ownerEntry = "D9A98017F0500EF9B69738641C9B4CBA1229EDC3F2151BC6C9C4FB07B1CB315E"; - private String userEntry - = "D1810D9E6E488BA5D2DDCBB3F974F7472D0D5389F554DB55574A787DC5C59884"; + private String userEntry = "D3EF424BFEA2E434000E1A74941CC87300000000000000000000000000000000"; EncryptionDictionaryTester setVersion(int version) { this.version = version; @@ -282,16 +292,16 @@ public class PDFEncryptionJCETestCase { @Test public void testBasic() throws IOException { test = new EncryptionTest(); - test.setData(0x00).setEncryptedData(0x56); + test.setData(0x00).setEncryptedData(0x24); runEncryptionTests(); - test.setData(0xAA).setEncryptedData(0xFC); + test.setData(0xAA).setEncryptedData(0x8E); runEncryptionTests(); - test.setData(0xFF).setEncryptedData(0xA9); + test.setData(0xFF).setEncryptedData(0xDB); runEncryptionTests(); - test = new EncryptionTest().setEncryptedData(0x56, 0x0C, 0xFC, 0xA5, 0xAB, 0x61, 0x73); + test = new EncryptionTest().setEncryptedData(0x24, 0x07, 0x85, 0xF7, 0x87, 0x31, 0x90); runEncryptionTests(); } @@ -313,14 +323,14 @@ public class PDFEncryptionJCETestCase { @Test public void testDisableRev2Permissions() throws IOException { EncryptionDictionaryTester encryptionDictionaryTester = new EncryptionDictionaryTester() + .setVersion(1) + .setRevision(2) + .setLength(40) .setPermissions(-64) + .setOwnerEntry("3EE8C4000CA44B2645EED029C9EA7D4FC63C6D9B89349E8FA5A40C7691AB96B5") .setUserEntry("3E65D0090746C4C37C5EF23C1BDB6323E00C24C4B2D744DD3BFB654CD58591A1"); - test = new EncryptionTest(encryptionDictionaryTester) - .setObjectNumber(3) - .disablePrint() - .disableEditContent() - .disableCopyContent() - .disableEditAnnotations() + test = new EncryptionTest(encryptionDictionaryTester).setObjectNumber(3).setEncryptionLength(40) + .disablePrint().disableEditContent().disableCopyContent().disableEditAnnotations() .setEncryptedData(0x66, 0xEE, 0xA7, 0x93, 0xC4, 0xB1, 0xB4); runEncryptionTests(); } @@ -330,11 +340,13 @@ public class PDFEncryptionJCETestCase { EncryptionDictionaryTester encryptionDictionaryTester = new EncryptionDictionaryTester() .setVersion(2) .setRevision(3) + .setLength(40) .setPermissions(-3844) .setOwnerEntry("8D4BCA4F4AB2BAB4E38F161D61F937EC50BE5EB30C2DC05EA409D252CD695E55") .setUserEntry("0F01171E22C7FB27B079C132BA4277DE00000000000000000000000000000000"); test = new EncryptionTest(encryptionDictionaryTester) .setObjectNumber(4) + .setEncryptionLength(40) .disableFillInForms() .disableAccessContent() .disableAssembleDocument() @@ -365,10 +377,14 @@ public class PDFEncryptionJCETestCase { @Test public void testDifferentPasswords() throws IOException { EncryptionDictionaryTester encryptionDictionaryTester = new EncryptionDictionaryTester() + .setRevision(2) + .setVersion(1) + .setLength(40) .setOwnerEntry("D11C233C65E9DC872E858ABBD8B62198771167ADCE7AB8DC7AE0A1A7E21A1E25") .setUserEntry("6F449167DB8DDF0D2DF4602DDBBA97ABF9A9101F632CC16AB0BE74EB9500B469"); test = new EncryptionTest(encryptionDictionaryTester) .setObjectNumber(6) + .setEncryptionLength(40) .setUserPassword("ADifferentUserPassword") .setOwnerPassword("ADifferentOwnerPassword") .setEncryptedData(0x27, 0xAC, 0xB1, 0x6C, 0x42, 0xE0, 0xA8); @@ -378,10 +394,14 @@ public class PDFEncryptionJCETestCase { @Test public void testNoOwnerPassword() throws IOException { EncryptionDictionaryTester encryptionDictionaryTester = new EncryptionDictionaryTester() + .setRevision(2) + .setVersion(1) + .setLength(40) .setOwnerEntry("5163AAF3EE74C76D7C223593A84C8702FEA8AA4493E4933FF5B5A5BBB20AE4BB") .setUserEntry("42DDF1C1BF3AB04786D5038E7B0A723AE614D944E1DE91A922FC54F5F2345E00"); test = new EncryptionTest(encryptionDictionaryTester) .setObjectNumber(7) + .setEncryptionLength(40) .setUserPassword("ADifferentUserPassword") .setOwnerPassword("") .setEncryptedData(0xEC, 0x2E, 0x5D, 0xC2, 0x7F, 0xAD, 0x58); @@ -434,6 +454,130 @@ public class PDFEncryptionJCETestCase { runEncryptionTests(); } + @Test + public void testAES256() throws UnsupportedEncodingException, NoSuchAlgorithmException, + NoSuchPaddingException, InvalidKeyException, InvalidAlgorithmParameterException, + IllegalBlockSizeException, BadPaddingException { + MessageDigest sha256 = MessageDigest.getInstance("SHA-256"); + String dataText = "Test data to encrypt."; + byte[] data = dataText.getBytes("UTF-8"); + PDFEncryptionParams params = new PDFEncryptionParams(); + params.setEncryptionLengthInBits(256); + params.setUserPassword("userpassword"); + params.setOwnerPassword("ownerpassword"); + PDFEncryptionJCE encryption = createEncryptionObject(params); + PDFText text = new PDFText(); + text.setObjectNumber(1); // obj number not used with AES 256, can be anything + String dictionary = new String(encryption.toPDF()); + byte[] encrypted = encryption.encrypt(data, text); + byte[] u = parseHexStringEntries(dictionary, "U"); + byte[] o = parseHexStringEntries(dictionary, "O"); + byte[] ue = parseHexStringEntries(dictionary, "UE"); + byte[] oe = parseHexStringEntries(dictionary, "OE"); + byte[] perms = parseHexStringEntries(dictionary, "Perms"); + // check byte arrays lengths + assertEquals(48, u.length); + assertEquals(48, o.length); + assertEquals(32, ue.length); + assertEquals(32, oe.length); + assertEquals(16, perms.length); + // check user password is valid + byte[] userValSalt = new byte[8]; + byte[] userKeySalt = new byte[8]; + System.arraycopy(u, 32, userValSalt, 0, 8); + System.arraycopy(u, 40, userKeySalt, 0, 8); + byte[] uPassBytes = params.getUserPassword().getBytes("UTF-8"); + byte[] testUPass = new byte[uPassBytes.length + 8]; + System.arraycopy(uPassBytes, 0, testUPass, 0, uPassBytes.length); + System.arraycopy(userValSalt, 0, testUPass, uPassBytes.length, 8); + sha256.reset(); + sha256.update(testUPass); + byte[] actualUPass = sha256.digest(); + byte[] expectedUPass = new byte[32]; + System.arraycopy(u, 0, expectedUPass, 0, 32); + assertArrayEquals(expectedUPass, actualUPass); + // check owner password is valid + byte[] ownerValSalt = new byte[8]; + byte[] ownerKeySalt = new byte[8]; + System.arraycopy(o, 32, ownerValSalt, 0, 8); + System.arraycopy(o, 40, ownerKeySalt, 0, 8); + byte[] oPassBytes = params.getOwnerPassword().getBytes("UTF-8"); + byte[] testOPass = new byte[oPassBytes.length + 8 + 48]; + System.arraycopy(oPassBytes, 0, testOPass, 0, oPassBytes.length); + System.arraycopy(ownerValSalt, 0, testOPass, oPassBytes.length, 8); + System.arraycopy(u, 0, testOPass, oPassBytes.length + 8, 48); + sha256.reset(); + sha256.update(testOPass); + byte[] actualOPass = sha256.digest(); + byte[] expectedOPass = new byte[32]; + System.arraycopy(o, 0, expectedOPass, 0, 32); + assertArrayEquals(expectedOPass, actualOPass); + // compute encryption key from ue + byte[] ivZero = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + IvParameterSpec ivspecZero = new IvParameterSpec(ivZero); + Cipher cipherNoPadding = Cipher.getInstance("AES/CBC/NoPadding"); + byte[] tmpUKey = new byte[uPassBytes.length + 8]; + System.arraycopy(uPassBytes, 0, tmpUKey, 0, uPassBytes.length); + System.arraycopy(userKeySalt, 0, tmpUKey, uPassBytes.length, 8); + sha256.reset(); + sha256.update(tmpUKey); + byte[] intUKey = sha256.digest(); + SecretKeySpec uSKeySpec = new SecretKeySpec(intUKey, "AES"); + cipherNoPadding.init(Cipher.DECRYPT_MODE, uSKeySpec, ivspecZero); + byte[] uFileEncryptionKey = cipherNoPadding.doFinal(ue); + // compute encryption key from oe + byte[] tmpOKey = new byte[oPassBytes.length + 8 + 48]; + System.arraycopy(oPassBytes, 0, tmpOKey, 0, oPassBytes.length); + System.arraycopy(ownerKeySalt, 0, tmpOKey, oPassBytes.length, 8); + System.arraycopy(u, 0, tmpOKey, oPassBytes.length + 8, 48); + sha256.reset(); + sha256.update(tmpOKey); + byte[] intOKey = sha256.digest(); + SecretKeySpec oSKeySpec = new SecretKeySpec(intOKey, "AES"); + cipherNoPadding.init(Cipher.DECRYPT_MODE, oSKeySpec, ivspecZero); + byte[] oFileEncryptionKey = cipherNoPadding.doFinal(oe); + // check both keys are the same + assertArrayEquals(uFileEncryptionKey, oFileEncryptionKey); + byte[] fileEncryptionKey = new byte[uFileEncryptionKey.length]; + System.arraycopy(uFileEncryptionKey, 0, fileEncryptionKey, 0, uFileEncryptionKey.length); + // decrypt perms + SecretKeySpec sKeySpec = new SecretKeySpec(fileEncryptionKey, "AES"); + cipherNoPadding.init(Cipher.DECRYPT_MODE, sKeySpec, ivspecZero); + byte[] decryptedPerms = cipherNoPadding.doFinal(perms); + assertEquals('T', decryptedPerms[8]); // metadata encrypted by default + assertEquals('a', decryptedPerms[9]); + assertEquals('d', decryptedPerms[10]); + assertEquals('b', decryptedPerms[11]); + int expectedPermissions = -4; // default if nothing set + int actualPermissions = decryptedPerms[3] << 24 | (decryptedPerms[2] & 0xFF) << 16 + | (decryptedPerms[1] & 0xFF) << 8 | (decryptedPerms[0] & 0xFF); + assertEquals(expectedPermissions, actualPermissions); + // decrypt data + byte[] iv = new byte[16]; + System.arraycopy(encrypted, 0, iv, 0, 16); + byte[] encryptedData = new byte[encrypted.length - 16]; + System.arraycopy(encrypted, 16, encryptedData, 0, encrypted.length - 16); + IvParameterSpec ivspec = new IvParameterSpec(iv); + Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); + cipher.init(Cipher.DECRYPT_MODE, sKeySpec, ivspec); + byte[] decryptedData = cipher.doFinal(encryptedData); + assertArrayEquals(data, decryptedData); + } + + private byte[] parseHexStringEntries(String dictionary, String entry) throws UnsupportedEncodingException { + String token = "/" + entry + " <"; + int start = dictionary.indexOf(token) + token.length(); + int end = dictionary.indexOf(">", start); + String parsedEntry = dictionary.substring(start, end); + int length = parsedEntry.length(); + byte[] data = new byte[length / 2]; + for (int i = 0; i < length; i += 2) { + data[i / 2] = (byte) ((Character.digit(parsedEntry.charAt(i), 16) << 4) + Character.digit( + parsedEntry.charAt(i + 1), 16)); + } + return data; + } + /** * Creates an encryption object using a fixed file ID generator for test reproducibility. * diff --git a/test/java/org/apache/fop/render/pdf/PDFRendererConfigParserTestCase.java b/test/java/org/apache/fop/render/pdf/PDFRendererConfigParserTestCase.java index 2d21b399c..2d3dfb760 100644 --- a/test/java/org/apache/fop/render/pdf/PDFRendererConfigParserTestCase.java +++ b/test/java/org/apache/fop/render/pdf/PDFRendererConfigParserTestCase.java @@ -165,13 +165,21 @@ public class PDFRendererConfigParserTestCase .getEncryptionLengthInBits()); } - for (int i = 128; i < 1000; i += 50) { + for (int i = 128; i < 256; i += 10) { parseConfig(createRenderer() .startEncryptionParams() .setEncryptionLength(i) .endEncryptionParams()); assertEquals(128, conf.getConfigOptions().getEncryptionParameters().getEncryptionLengthInBits()); } + + for (int i = 256; i < 1000; i += 50) { + parseConfig(createRenderer() + .startEncryptionParams() + .setEncryptionLength(i) + .endEncryptionParams()); + assertEquals(256, conf.getConfigOptions().getEncryptionParameters().getEncryptionLengthInBits()); + } } @Test |