| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You under the Apache License, Version 2.0 |
| * (the "License"); you may not use this file except in compliance with |
| * the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| /* $Id$ */ |
| |
| package org.apache.fop.pdf; |
| |
| // Java |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.io.OutputStream; |
| import java.security.InvalidKeyException; |
| import java.security.MessageDigest; |
| import java.security.NoSuchAlgorithmException; |
| import java.util.Random; |
| |
| import javax.crypto.BadPaddingException; |
| import javax.crypto.Cipher; |
| import javax.crypto.CipherOutputStream; |
| import javax.crypto.IllegalBlockSizeException; |
| import javax.crypto.NoSuchPaddingException; |
| import javax.crypto.spec.SecretKeySpec; |
| |
| /** |
| * class representing a /Filter /Standard object. |
| * |
| */ |
| public class PDFEncryptionJCE extends PDFObject implements PDFEncryption { |
| |
| private class EncryptionFilter extends PDFFilter { |
| private PDFEncryptionJCE encryption; |
| private int number; |
| private int generation; |
| |
| /** |
| * The constructor for the internal PDFEncryptionJCE filter |
| * @param encryption The encryption object to use |
| * @param number The number of the object to be encrypted |
| * @param generation The generation of the object to be encrypted |
| */ |
| public EncryptionFilter(PDFEncryptionJCE encryption, |
| int number, int generation) { |
| super(); |
| this.encryption = encryption; |
| this.number = number; |
| this.generation = generation; |
| log.debug("new encryption filter for number " |
| + number + " and generation " + generation); |
| } |
| |
| /** |
| * Return a PDF string representation of the filter. In this |
| * case no filter name is passed. |
| * @return The filter name, blank in this case |
| */ |
| public String getName() { |
| return ""; |
| } |
| |
| /** |
| * Return a parameter dictionary for this filter, or null |
| * @return The parameter dictionary. In this case, null. |
| */ |
| public PDFObject getDecodeParms() { |
| return null; |
| } |
| |
| /** |
| * Encode the given data with the filter |
| * @param data The data to be encrypted |
| * @return The encrypted data |
| */ |
| public byte[] encode(byte[] data) { |
| return encryption.encryptData(data, number, generation); |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| public void encode(InputStream in, OutputStream out, int length) |
| throws IOException { |
| byte[] buffer = new byte[length]; |
| in.read(buffer); |
| buffer = encode(buffer); |
| out.write(buffer); |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| public OutputStream applyFilter(OutputStream out) throws IOException { |
| return new CipherOutputStream(out, |
| encryption.initCipher(number, generation)); |
| } |
| |
| } |
| |
| private static final char [] PAD |
| = {0x28, 0xBF, 0x4E, 0x5E, 0x4E, 0x75, 0x8A, 0x41, |
| 0x64, 0x00, 0x4E, 0x56, 0xFF, 0xFA, 0x01, 0x08, |
| 0x2E, 0x2E, 0x00, 0xB6, 0xD0, 0x68, 0x3E, 0x80, |
| 0x2F, 0x0C, 0xA9, 0xFE, 0x64, 0x53, 0x69, 0x7A}; |
| |
| /** Value of PRINT permission */ |
| public static final int PERMISSION_PRINT = 4; |
| /** Value of content editting permission */ |
| public static final int PERMISSION_EDIT_CONTENT = 8; |
| /** Value of content extraction permission */ |
| public static final int PERMISSION_COPY_CONTENT = 16; |
| /** Value of annotation editting permission */ |
| public static final int PERMISSION_EDIT_ANNOTATIONS = 32; |
| |
| // Encryption tools |
| private MessageDigest digest = null; |
| //private Cipher cipher = null; |
| private Random random = new Random(); |
| // Control attributes |
| private PDFEncryptionParams params; |
| // Output attributes |
| private byte[] fileID = null; |
| private byte[] encryptionKey = null; |
| private String dictionary = null; |
| |
| /** |
| * Create a /Filter /Standard object. |
| * |
| * @param objnum the object's number |
| */ |
| public PDFEncryptionJCE(int objnum) { |
| /* generic creation of object */ |
| super(); |
| setObjectNumber(objnum); |
| try { |
| digest = MessageDigest.getInstance("MD5"); |
| //cipher = Cipher.getInstance("RC4"); |
| } catch (NoSuchAlgorithmException e) { |
| throw new UnsupportedOperationException(e.getMessage()); |
| /*} catch (NoSuchPaddingException e) { |
| throw new UnsupportedOperationException(e.getMessage());*/ |
| } |
| } |
| |
| /** |
| * Local factory method. |
| * @param objnum PDF object number for the encryption object |
| * @param params PDF encryption parameters |
| * @return PDFEncryption the newly created PDFEncryption object |
| */ |
| public static PDFEncryption make(int objnum, PDFEncryptionParams params) { |
| PDFEncryptionJCE impl = new PDFEncryptionJCE(objnum); |
| impl.setParams(params); |
| impl.init(); |
| return impl; |
| } |
| |
| |
| /** |
| * Returns the encryption parameters. |
| * @return the encryption parameters |
| */ |
| public PDFEncryptionParams getParams() { |
| return this.params; |
| } |
| |
| /** |
| * Sets the encryption parameters. |
| * @param params The parameterss to set |
| */ |
| public void setParams(PDFEncryptionParams params) { |
| this.params = params; |
| } |
| |
| // Internal procedures |
| |
| private byte[] prepPassword(String password) { |
| byte[] obuffer = new byte[32]; |
| byte[] pbuffer = password.getBytes(); |
| |
| int i = 0; |
| int j = 0; |
| |
| while (i < obuffer.length && i < pbuffer.length) { |
| obuffer[i] = pbuffer[i]; |
| i++; |
| } |
| while (i < obuffer.length) { |
| obuffer[i++] = (byte) PAD[j++]; |
| } |
| |
| return obuffer; |
| } |
| |
| /** |
| * Returns the document file ID |
| * @return The file ID |
| */ |
| public byte[] getFileID() { |
| if (fileID == null) { |
| fileID = new byte[16]; |
| random.nextBytes(fileID); |
| } |
| |
| return fileID; |
| } |
| |
| /** |
| * This method returns the indexed file ID |
| * @param index The index to access the file ID |
| * @return The file ID |
| */ |
| public String getFileID(int index) { |
| if (index == 1) { |
| return PDFText.toHex(getFileID()); |
| } |
| |
| byte[] id = new byte[16]; |
| random.nextBytes(id); |
| return PDFText.toHex(id); |
| } |
| |
| private byte[] encryptWithKey(byte[] data, byte[] key) { |
| try { |
| final Cipher c = initCipher(key); |
| return c.doFinal(data); |
| } catch (IllegalBlockSizeException e) { |
| throw new IllegalStateException(e.getMessage()); |
| } catch (BadPaddingException e) { |
| throw new IllegalStateException(e.getMessage()); |
| } |
| } |
| |
| private Cipher initCipher(byte[] key) { |
| try { |
| Cipher c = Cipher.getInstance("RC4"); |
| SecretKeySpec keyspec = new SecretKeySpec(key, "RC4"); |
| c.init(Cipher.ENCRYPT_MODE, keyspec); |
| return c; |
| } catch (InvalidKeyException e) { |
| throw new IllegalStateException(e.getMessage()); |
| } catch (NoSuchAlgorithmException e) { |
| throw new UnsupportedOperationException(e.getMessage()); |
| } catch (NoSuchPaddingException e) { |
| throw new UnsupportedOperationException(e.getMessage()); |
| } |
| } |
| |
| private Cipher initCipher(int number, int generation) { |
| byte[] hash = calcHash(number, generation); |
| int size = hash.length; |
| hash = digest.digest(hash); |
| byte[] key = calcKey(hash, size); |
| return initCipher(key); |
| } |
| |
| private byte[] encryptWithHash(byte[] data, byte[] hash, int size) { |
| hash = digest.digest(hash); |
| |
| byte[] key = calcKey(hash, size); |
| |
| return encryptWithKey(data, key); |
| } |
| |
| private byte[] calcKey(byte[] hash, int size) { |
| byte[] key = new byte[size]; |
| |
| for (int i = 0; i < size; i++) { |
| key[i] = hash[i]; |
| } |
| return key; |
| } |
| |
| /** |
| * This method initializes the encryption algorithms and values |
| */ |
| public void init() { |
| // Generate the owner value |
| byte[] oValue; |
| if (params.getOwnerPassword().length() > 0) { |
| oValue = encryptWithHash( |
| prepPassword(params.getUserPassword()), |
| prepPassword(params.getOwnerPassword()), 5); |
| } else { |
| oValue = encryptWithHash( |
| prepPassword(params.getUserPassword()), |
| prepPassword(params.getUserPassword()), 5); |
| } |
| |
| // Generate permissions value |
| int permissions = -4; |
| |
| if (!params.isAllowPrint()) { |
| permissions -= PERMISSION_PRINT; |
| } |
| if (!params.isAllowCopyContent()) { |
| permissions -= PERMISSION_COPY_CONTENT; |
| } |
| if (!params.isAllowEditContent()) { |
| permissions -= PERMISSION_EDIT_CONTENT; |
| } |
| if (!params.isAllowEditAnnotations()) { |
| permissions -= PERMISSION_EDIT_ANNOTATIONS; |
| } |
| |
| // Create the encrption key |
| digest.update(prepPassword(params.getUserPassword())); |
| digest.update(oValue); |
| digest.update((byte) (permissions >>> 0)); |
| digest.update((byte) (permissions >>> 8)); |
| digest.update((byte) (permissions >>> 16)); |
| digest.update((byte) (permissions >>> 24)); |
| digest.update(getFileID()); |
| |
| byte [] hash = digest.digest(); |
| this.encryptionKey = new byte[5]; |
| |
| for (int i = 0; i < 5; i++) { |
| this.encryptionKey[i] = hash[i]; |
| } |
| |
| // Create the user value |
| byte[] uValue = encryptWithKey(prepPassword(""), this.encryptionKey); |
| |
| // Create the dictionary |
| this.dictionary = getObjectID() |
| + "<< /Filter /Standard\n" |
| + "/V 1\n" |
| + "/R 2\n" |
| + "/Length 40\n" |
| + "/P " + permissions + "\n" |
| + "/O " + PDFText.toHex(oValue) + "\n" |
| + "/U " + PDFText.toHex(uValue) + "\n" |
| + ">>\n" |
| + "endobj\n"; |
| } |
| |
| /** |
| * This method encrypts the passed data using the generated keys. |
| * @param data The data to be encrypted |
| * @param number The block number |
| * @param generation The block generation |
| * @return The encrypted data |
| */ |
| public byte[] encryptData(byte[] data, int number, int generation) { |
| if (this.encryptionKey == null) { |
| throw new IllegalStateException("PDF Encryption has not been initialized"); |
| } |
| byte[] hash = calcHash(number, generation); |
| return encryptWithHash(data, hash, hash.length); |
| } |
| |
| /** {@inheritDoc} */ |
| public byte[] encrypt(byte[] data, PDFObject refObj) { |
| PDFObject o = refObj; |
| while (o != null && !o.hasObjectNumber()) { |
| o = o.getParent(); |
| } |
| if (o == null) { |
| throw new IllegalStateException("No object number could be obtained for a PDF object"); |
| } |
| return encryptData(data, o.getObjectNumber(), o.getGeneration()); |
| } |
| |
| private byte[] calcHash(int number, int generation) { |
| byte[] hash = new byte[this.encryptionKey.length + 5]; |
| |
| int i = 0; |
| while (i < this.encryptionKey.length) { |
| hash[i] = this.encryptionKey[i]; i++; |
| } |
| |
| hash[i++] = (byte) (number >>> 0); |
| hash[i++] = (byte) (number >>> 8); |
| hash[i++] = (byte) (number >>> 16); |
| hash[i++] = (byte) (generation >>> 0); |
| hash[i++] = (byte) (generation >>> 8); |
| return hash; |
| } |
| |
| /** |
| * Creates PDFFilter for the encryption object |
| * @param number The object number |
| * @param generation The objects generation |
| * @return The resulting filter |
| */ |
| public PDFFilter makeFilter(int number, int generation) { |
| return new EncryptionFilter(this, number, generation); |
| } |
| |
| /** |
| * Adds a PDFFilter to the PDFStream object |
| * @param stream the stream to add an encryption filter to |
| */ |
| public void applyFilter(AbstractPDFStream stream) { |
| stream.getFilterList().addFilter( |
| this.makeFilter(stream.getObjectNumber(), stream.getGeneration())); |
| } |
| |
| /** |
| * Represent the object in PDF |
| * |
| * @return the PDF |
| */ |
| public byte[] toPDF() { |
| if (this.dictionary == null) { |
| throw new IllegalStateException("PDF Encryption has not been initialized"); |
| } |
| |
| return encode(this.dictionary); |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| public String getTrailerEntry() { |
| return "/Encrypt " + getObjectNumber() + " " |
| + getGeneration() + " R\n" |
| + "/ID[" + getFileID(1) + getFileID(2) + "]\n"; |
| } |
| } |