001: /**
002: * Copyright (c) 2003-2006, www.pdfbox.org
003: * All rights reserved.
004: *
005: * Redistribution and use in source and binary forms, with or without
006: * modification, are permitted provided that the following conditions are met:
007: *
008: * 1. Redistributions of source code must retain the above copyright notice,
009: * this list of conditions and the following disclaimer.
010: * 2. Redistributions in binary form must reproduce the above copyright notice,
011: * this list of conditions and the following disclaimer in the documentation
012: * and/or other materials provided with the distribution.
013: * 3. Neither the name of pdfbox; nor the names of its
014: * contributors may be used to endorse or promote products derived from this
015: * software without specific prior written permission.
016: *
017: * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
018: * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
019: * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
020: * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
021: * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
022: * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
023: * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
024: * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
025: * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
026: * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
027: *
028: * http://www.pdfbox.org
029: *
030: */package org.pdfbox.pdmodel.encryption;
031:
032: import java.io.ByteArrayInputStream;
033: import java.io.ByteArrayOutputStream;
034: import java.io.IOException;
035: import java.io.InputStream;
036: import java.io.OutputStream;
037: import java.security.MessageDigest;
038: import java.security.NoSuchAlgorithmException;
039: import java.util.HashSet;
040: import java.util.Iterator;
041: import java.util.List;
042: import java.util.Set;
043:
044: import org.pdfbox.cos.COSArray;
045: import org.pdfbox.cos.COSBase;
046: import org.pdfbox.cos.COSDictionary;
047: import org.pdfbox.cos.COSName;
048: import org.pdfbox.cos.COSObject;
049: import org.pdfbox.cos.COSStream;
050: import org.pdfbox.cos.COSString;
051: import org.pdfbox.encryption.ARCFour;
052: import org.pdfbox.exceptions.CryptographyException;
053: import org.pdfbox.pdmodel.PDDocument;
054:
055: /**
056: * This class represents a security handler as described in the PDF specifications.
057: * A security handler is responsible of documents protection.
058: *
059: * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
060: * @author Benoit Guillon (benoit.guillon@snv.jussieu.fr)
061: *
062: * @version $Revision: 1.4 $
063: */
064:
065: public abstract class SecurityHandler {
066:
067: /* ------------------------------------------------
068: * CONSTANTS
069: -------------------------------------------------- */
070:
071: private static final int DEFAULT_KEY_LENGTH = 40;
072:
073: /**
074: * The value of V field of the Encryption dictionary.
075: */
076: protected int version;
077:
078: /**
079: * The length of the secret key used to encrypt the document.
080: */
081: protected int keyLength = DEFAULT_KEY_LENGTH;
082:
083: /**
084: * The encryption key that will used to encrypt / decrypt.
085: */
086: protected byte[] encryptionKey;
087:
088: /**
089: * The document whose security is handled by this security handler.
090: */
091:
092: protected PDDocument document;
093:
094: /**
095: * The RC4 implementation used for cryptographic functions.
096: */
097: protected ARCFour rc4 = new ARCFour();
098:
099: private Set objects = new HashSet();
100:
101: private Set potentialSignatures = new HashSet();
102:
103: /**
104: * The access permission granted to the current user for the document. These
105: * permissions are computed during decryption and are in read only mode.
106: */
107:
108: protected AccessPermission currentAccessPermission = null;
109:
110: /**
111: * Prepare the document for encryption.
112: *
113: * @param doc The document that will be encrypted.
114: *
115: * @throws CryptographyException If there is an error while preparing.
116: * @throws IOException If there is an error with the document.
117: */
118: public abstract void prepareDocumentForEncryption(PDDocument doc)
119: throws CryptographyException, IOException;
120:
121: /**
122: * Prepare the document for decryption.
123: *
124: * @param doc The document to decrypt.
125: * @param mat Information required to decrypt the document.
126: * @throws CryptographyException If there is an error while preparing.
127: * @throws IOException If there is an error with the document.
128: */
129: public abstract void decryptDocument(PDDocument doc,
130: DecryptionMaterial mat) throws CryptographyException,
131: IOException;
132:
133: /**
134: * This method must be called by an implementation of this class to really proceed
135: * to decryption.
136: *
137: * @throws IOException If there is an error in the decryption.
138: * @throws CryptographyException If there is an error in the decryption.
139: */
140: protected void proceedDecryption() throws IOException,
141: CryptographyException {
142:
143: COSDictionary trailer = document.getDocument().getTrailer();
144: COSArray fields = (COSArray) trailer
145: .getObjectFromPath("Root/AcroForm/Fields");
146:
147: //We need to collect all the signature dictionaries, for some
148: //reason the 'Contents' entry of signatures is not really encrypted
149: if (fields != null) {
150: for (int i = 0; i < fields.size(); i++) {
151: COSDictionary field = (COSDictionary) fields
152: .getObject(i);
153: addDictionaryAndSubDictionary(potentialSignatures,
154: field);
155: }
156: }
157:
158: List allObjects = document.getDocument().getObjects();
159: Iterator objectIter = allObjects.iterator();
160: while (objectIter.hasNext()) {
161: decryptObject((COSObject) objectIter.next());
162: }
163: document.setEncryptionDictionary(null);
164: }
165:
166: private void addDictionaryAndSubDictionary(Set set,
167: COSDictionary dic) {
168: set.add(dic);
169: COSArray kids = (COSArray) dic.getDictionaryObject("Kids");
170: for (int i = 0; kids != null && i < kids.size(); i++) {
171: addDictionaryAndSubDictionary(set, (COSDictionary) kids
172: .getObject(i));
173: }
174: COSBase value = dic.getDictionaryObject("V");
175: if (value instanceof COSDictionary) {
176: addDictionaryAndSubDictionary(set, (COSDictionary) value);
177: }
178: }
179:
180: /**
181: * Encrypt a set of data.
182: *
183: * @param objectNumber The data object number.
184: * @param genNumber The data generation number.
185: * @param data The data to encrypt.
186: * @param output The output to write the encrypted data to.
187: *
188: * @throws CryptographyException If there is an error during the encryption.
189: * @throws IOException If there is an error reading the data.
190: */
191: public void encryptData(long objectNumber, long genNumber,
192: InputStream data, OutputStream output)
193: throws CryptographyException, IOException {
194: byte[] newKey = new byte[encryptionKey.length + 5];
195: System.arraycopy(encryptionKey, 0, newKey, 0,
196: encryptionKey.length);
197: //PDF 1.4 reference pg 73
198: //step 1
199: //we have the reference
200:
201: //step 2
202: newKey[newKey.length - 5] = (byte) (objectNumber & 0xff);
203: newKey[newKey.length - 4] = (byte) ((objectNumber >> 8) & 0xff);
204: newKey[newKey.length - 3] = (byte) ((objectNumber >> 16) & 0xff);
205: newKey[newKey.length - 2] = (byte) (genNumber & 0xff);
206: newKey[newKey.length - 1] = (byte) ((genNumber >> 8) & 0xff);
207:
208: //step 3
209: byte[] digestedKey = null;
210: try {
211: MessageDigest md = MessageDigest.getInstance("MD5");
212: digestedKey = md.digest(newKey);
213: } catch (NoSuchAlgorithmException e) {
214: throw new CryptographyException(e);
215: }
216:
217: //step 4
218: int length = Math.min(newKey.length, 16);
219: byte[] finalKey = new byte[length];
220: System.arraycopy(digestedKey, 0, finalKey, 0, length);
221:
222: rc4.setKey(finalKey);
223: rc4.write(data, output);
224: output.flush();
225:
226: }
227:
228: /**
229: * This will decrypt an object in the document.
230: *
231: * @param object The object to decrypt.
232: *
233: * @throws CryptographyException If there is an error decrypting the stream.
234: * @throws IOException If there is an error getting the stream data.
235: */
236: private void decryptObject(COSObject object)
237: throws CryptographyException, IOException {
238: long objNum = object.getObjectNumber().intValue();
239: long genNum = object.getGenerationNumber().intValue();
240: COSBase base = object.getObject();
241: decrypt(base, objNum, genNum);
242: }
243:
244: /**
245: * This will dispatch to the correct method.
246: *
247: * @param obj The object to decrypt.
248: * @param objNum The object number.
249: * @param genNum The object generation Number.
250: *
251: * @throws CryptographyException If there is an error decrypting the stream.
252: * @throws IOException If there is an error getting the stream data.
253: */
254: private void decrypt(Object obj, long objNum, long genNum)
255: throws CryptographyException, IOException {
256: if (!objects.contains(obj)) {
257: objects.add(obj);
258:
259: if (obj instanceof COSString) {
260: decryptString((COSString) obj, objNum, genNum);
261: } else if (obj instanceof COSStream) {
262: decryptStream((COSStream) obj, objNum, genNum);
263: } else if (obj instanceof COSDictionary) {
264: decryptDictionary((COSDictionary) obj, objNum, genNum);
265: } else if (obj instanceof COSArray) {
266: decryptArray((COSArray) obj, objNum, genNum);
267: }
268: }
269: }
270:
271: /**
272: * This will decrypt a stream.
273: *
274: * @param stream The stream to decrypt.
275: * @param objNum The object number.
276: * @param genNum The object generation number.
277: *
278: * @throws CryptographyException If there is an error getting the stream.
279: * @throws IOException If there is an error getting the stream data.
280: */
281: public void decryptStream(COSStream stream, long objNum, long genNum)
282: throws CryptographyException, IOException {
283: decryptDictionary(stream, objNum, genNum);
284: InputStream encryptedStream = stream.getFilteredStream();
285: encryptData(objNum, genNum, encryptedStream, stream
286: .createFilteredStream());
287: }
288:
289: /**
290: * This will decrypt a dictionary.
291: *
292: * @param dictionary The dictionary to decrypt.
293: * @param objNum The object number.
294: * @param genNum The object generation number.
295: *
296: * @throws CryptographyException If there is an error decrypting the document.
297: * @throws IOException If there is an error creating a new string.
298: */
299: private void decryptDictionary(COSDictionary dictionary,
300: long objNum, long genNum) throws CryptographyException,
301: IOException {
302: Iterator keys = dictionary.keyList().iterator();
303: while (keys.hasNext()) {
304: COSName key = (COSName) keys.next();
305: Object value = dictionary.getItem(key);
306: //if we are a signature dictionary and contain a Contents entry then
307: //we don't decrypt it.
308: if (!(key.getName().equals("Contents")
309: && value instanceof COSString && potentialSignatures
310: .contains(dictionary))) {
311: decrypt(value, objNum, genNum);
312: }
313: }
314: }
315:
316: /**
317: * This will decrypt a string.
318: *
319: * @param string the string to decrypt.
320: * @param objNum The object number.
321: * @param genNum The object generation number.
322: *
323: * @throws CryptographyException If an error occurs during decryption.
324: * @throws IOException If an error occurs writing the new string.
325: */
326: public void decryptString(COSString string, long objNum, long genNum)
327: throws CryptographyException, IOException {
328: ByteArrayInputStream data = new ByteArrayInputStream(string
329: .getBytes());
330: ByteArrayOutputStream buffer = new ByteArrayOutputStream();
331: encryptData(objNum, genNum, data, buffer);
332: string.reset();
333: string.append(buffer.toByteArray());
334: }
335:
336: /**
337: * This will decrypt an array.
338: *
339: * @param array The array to decrypt.
340: * @param objNum The object number.
341: * @param genNum The object generation number.
342: *
343: * @throws CryptographyException If an error occurs during decryption.
344: * @throws IOException If there is an error accessing the data.
345: */
346: private void decryptArray(COSArray array, long objNum, long genNum)
347: throws CryptographyException, IOException {
348: for (int i = 0; i < array.size(); i++) {
349: decrypt(array.get(i), objNum, genNum);
350: }
351: }
352:
353: /**
354: * Getter of the property <tt>keyLength</tt>.
355: * @return Returns the keyLength.
356: * @uml.property name="keyLength"
357: */
358: public int getKeyLength() {
359: return keyLength;
360: }
361:
362: /**
363: * Setter of the property <tt>keyLength</tt>.
364: *
365: * @param keyLen The keyLength to set.
366: */
367: public void setKeyLength(int keyLen) {
368: this .keyLength = keyLen;
369: }
370:
371: /**
372: * Returns the access permissions that were computed during document decryption.
373: * The returned object is in read only mode.
374: *
375: * @return the access permissions or null if the document was not decrypted.
376: */
377: public AccessPermission getCurrentAccessPermission() {
378: return currentAccessPermission;
379: }
380: }
|