aboutsummaryrefslogtreecommitdiffstats
path: root/test/java
diff options
context:
space:
mode:
authorLuis Bernardo <lbernardo@apache.org>2013-05-18 22:25:52 +0000
committerLuis Bernardo <lbernardo@apache.org>2013-05-18 22:25:52 +0000
commit016cb3199865f798f401c452554eea6e74055950 (patch)
tree10635c8e78ce1124251a9e735088f20cc81619c7 /test/java
parentaf665b918b7a15470f9006c7d217a9fc8ea7fcbd (diff)
downloadxmlgraphics-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.java178
-rw-r--r--test/java/org/apache/fop/render/pdf/PDFRendererConfigParserTestCase.java10
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