001: /**
002: * Copyright (c) 2003-2004, 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.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.math.BigInteger;
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.COSDocument;
048: import org.pdfbox.cos.COSName;
049: import org.pdfbox.cos.COSObject;
050: import org.pdfbox.cos.COSStream;
051: import org.pdfbox.cos.COSString;
052: import org.pdfbox.exceptions.CryptographyException;
053: import org.pdfbox.exceptions.InvalidPasswordException;
054: import org.pdfbox.pdmodel.PDDocument;
055: import org.pdfbox.pdmodel.encryption.PDStandardEncryption;
056:
057: /**
058: * This class will deal with encrypting/decrypting a document.
059: *
060: * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
061: * @version $Revision: 1.13 $
062: *
063: * @deprecated use the new security API instead.
064: *
065: * @see org.pdfbox.pdmodel.encryption.StandardSecurityHandler
066: */
067: public class DocumentEncryption {
068: private PDDocument pdDocument = null;
069: private COSDocument document = null;
070:
071: private byte[] encryptionKey = null;
072: private PDFEncryption encryption = new PDFEncryption();
073:
074: private Set objects = new HashSet();
075:
076: /**
077: * A set that contains potential signature dictionaries. This is used
078: * because the Contents entry of the signature is not encrypted.
079: */
080: private Set potentialSignatures = new HashSet();
081:
082: /**
083: * Constructor.
084: *
085: * @param doc The document to decrypt.
086: */
087: public DocumentEncryption(PDDocument doc) {
088: pdDocument = doc;
089: document = doc.getDocument();
090: }
091:
092: /**
093: * Constructor.
094: *
095: * @param doc The document to decrypt.
096: */
097: public DocumentEncryption(COSDocument doc) {
098: pdDocument = new PDDocument(doc);
099: document = doc;
100: }
101:
102: /**
103: * This will encrypt the given document, given the owner password and user password.
104: * The encryption method used is the standard filter.
105: *
106: * @throws CryptographyException If an error occurs during encryption.
107: * @throws IOException If there is an error accessing the data.
108: */
109: public void initForEncryption() throws CryptographyException,
110: IOException {
111: String ownerPassword = pdDocument
112: .getOwnerPasswordForEncryption();
113: String userPassword = pdDocument.getUserPasswordForEncryption();
114: if (ownerPassword == null) {
115: ownerPassword = "";
116: }
117: if (userPassword == null) {
118: userPassword = "";
119: }
120: PDStandardEncryption encParameters = (PDStandardEncryption) pdDocument
121: .getEncryptionDictionary();
122: int permissionInt = encParameters.getPermissions();
123: int revision = encParameters.getRevision();
124: int length = encParameters.getLength() / 8;
125: COSArray idArray = document.getDocumentID();
126:
127: //check if the document has an id yet. If it does not then
128: //generate one
129: if (idArray == null || idArray.size() < 2) {
130: idArray = new COSArray();
131: try {
132: MessageDigest md = MessageDigest.getInstance("MD5");
133: BigInteger time = BigInteger.valueOf(System
134: .currentTimeMillis());
135: md.update(time.toByteArray());
136: md.update(ownerPassword.getBytes());
137: md.update(userPassword.getBytes());
138: md.update(document.toString().getBytes());
139: byte[] id = md.digest(this .toString().getBytes());
140: COSString idString = new COSString();
141: idString.append(id);
142: idArray.add(idString);
143: idArray.add(idString);
144: document.setDocumentID(idArray);
145: } catch (NoSuchAlgorithmException e) {
146: throw new CryptographyException(e);
147: }
148:
149: }
150: COSString id = (COSString) idArray.getObject(0);
151: encryption = new PDFEncryption();
152:
153: byte[] o = encryption.computeOwnerPassword(ownerPassword
154: .getBytes("ISO-8859-1"), userPassword
155: .getBytes("ISO-8859-1"), revision, length);
156:
157: byte[] u = encryption.computeUserPassword(userPassword
158: .getBytes("ISO-8859-1"), o, permissionInt, id
159: .getBytes(), revision, length);
160:
161: encryptionKey = encryption.computeEncryptedKey(userPassword
162: .getBytes("ISO-8859-1"), o, permissionInt, id
163: .getBytes(), revision, length);
164:
165: encParameters.setOwnerKey(o);
166: encParameters.setUserKey(u);
167:
168: document.setEncryptionDictionary(encParameters
169: .getCOSDictionary());
170: }
171:
172: /**
173: * This will decrypt the document.
174: *
175: * @param password The password for the document.
176: *
177: * @throws CryptographyException If there is an error decrypting the document.
178: * @throws IOException If there is an error getting the stream data.
179: * @throws InvalidPasswordException If the password is not a user or owner password.
180: */
181: public void decryptDocument(String password)
182: throws CryptographyException, IOException,
183: InvalidPasswordException {
184: if (password == null) {
185: password = "";
186: }
187:
188: PDStandardEncryption encParameters = (PDStandardEncryption) pdDocument
189: .getEncryptionDictionary();
190:
191: int permissions = encParameters.getPermissions();
192: int revision = encParameters.getRevision();
193: int length = encParameters.getLength() / 8;
194:
195: COSString id = (COSString) document.getDocumentID()
196: .getObject(0);
197: byte[] u = encParameters.getUserKey();
198: byte[] o = encParameters.getOwnerKey();
199:
200: boolean isUserPassword = encryption.isUserPassword(password
201: .getBytes(), u, o, permissions, id.getBytes(),
202: revision, length);
203: boolean isOwnerPassword = encryption.isOwnerPassword(password
204: .getBytes(), u, o, permissions, id.getBytes(),
205: revision, length);
206:
207: if (isUserPassword) {
208: encryptionKey = encryption.computeEncryptedKey(password
209: .getBytes(), o, permissions, id.getBytes(),
210: revision, length);
211: } else if (isOwnerPassword) {
212: byte[] computedUserPassword = encryption.getUserPassword(
213: password.getBytes(), o, revision, length);
214: encryptionKey = encryption.computeEncryptedKey(
215: computedUserPassword, o, permissions,
216: id.getBytes(), revision, length);
217: } else {
218: throw new InvalidPasswordException(
219: "Error: The supplied password does not match "
220: + "either the owner or user password in the document.");
221: }
222:
223: COSDictionary trailer = document.getTrailer();
224: COSArray fields = (COSArray) trailer
225: .getObjectFromPath("Root/AcroForm/Fields");
226:
227: //We need to collect all the signature dictionaries, for some
228: //reason the 'Contents' entry of signatures is not really encrypted
229: if (fields != null) {
230: for (int i = 0; i < fields.size(); i++) {
231: COSDictionary field = (COSDictionary) fields
232: .getObject(i);
233: addDictionaryAndSubDictionary(potentialSignatures,
234: field);
235: }
236: }
237:
238: List allObjects = document.getObjects();
239: Iterator objectIter = allObjects.iterator();
240: while (objectIter.hasNext()) {
241: decryptObject((COSObject) objectIter.next());
242: }
243: document.setEncryptionDictionary(null);
244: }
245:
246: private void addDictionaryAndSubDictionary(Set set,
247: COSDictionary dic) {
248: set.add(dic);
249: COSArray kids = (COSArray) dic.getDictionaryObject("Kids");
250: for (int i = 0; kids != null && i < kids.size(); i++) {
251: addDictionaryAndSubDictionary(set, (COSDictionary) kids
252: .getObject(i));
253: }
254: COSBase value = dic.getDictionaryObject("V");
255: if (value instanceof COSDictionary) {
256: addDictionaryAndSubDictionary(set, (COSDictionary) value);
257: }
258: }
259:
260: /**
261: * This will decrypt an object in the document.
262: *
263: * @param object The object to decrypt.
264: *
265: * @throws CryptographyException If there is an error decrypting the stream.
266: * @throws IOException If there is an error getting the stream data.
267: */
268: private void decryptObject(COSObject object)
269: throws CryptographyException, IOException {
270: long objNum = object.getObjectNumber().intValue();
271: long genNum = object.getGenerationNumber().intValue();
272: COSBase base = object.getObject();
273: decrypt(base, objNum, genNum);
274: }
275:
276: /**
277: * This will dispatch to the correct method.
278: *
279: * @param obj The object to decrypt.
280: * @param objNum The object number.
281: * @param genNum The object generation Number.
282: *
283: * @throws CryptographyException If there is an error decrypting the stream.
284: * @throws IOException If there is an error getting the stream data.
285: */
286: public void decrypt(Object obj, long objNum, long genNum)
287: throws CryptographyException, IOException {
288: if (!objects.contains(obj)) {
289: objects.add(obj);
290:
291: if (obj instanceof COSString) {
292: decryptString((COSString) obj, objNum, genNum);
293: } else if (obj instanceof COSStream) {
294: decryptStream((COSStream) obj, objNum, genNum);
295: } else if (obj instanceof COSDictionary) {
296: decryptDictionary((COSDictionary) obj, objNum, genNum);
297: } else if (obj instanceof COSArray) {
298: decryptArray((COSArray) obj, objNum, genNum);
299: }
300: }
301: }
302:
303: /**
304: * This will decrypt a stream.
305: *
306: * @param stream The stream to decrypt.
307: * @param objNum The object number.
308: * @param genNum The object generation number.
309: *
310: * @throws CryptographyException If there is an error getting the stream.
311: * @throws IOException If there is an error getting the stream data.
312: */
313: private void decryptStream(COSStream stream, long objNum,
314: long genNum) throws CryptographyException, IOException {
315: decryptDictionary(stream, objNum, genNum);
316: InputStream encryptedStream = stream.getFilteredStream();
317: encryption.encryptData(objNum, genNum, encryptionKey,
318: encryptedStream, stream.createFilteredStream());
319: }
320:
321: /**
322: * This will decrypt a dictionary.
323: *
324: * @param dictionary The dictionary to decrypt.
325: * @param objNum The object number.
326: * @param genNum The object generation number.
327: *
328: * @throws CryptographyException If there is an error decrypting the document.
329: * @throws IOException If there is an error creating a new string.
330: */
331: private void decryptDictionary(COSDictionary dictionary,
332: long objNum, long genNum) throws CryptographyException,
333: IOException {
334: Iterator keys = dictionary.keyList().iterator();
335: while (keys.hasNext()) {
336: COSName key = (COSName) keys.next();
337: Object value = dictionary.getItem(key);
338: //if we are a signature dictionary and contain a Contents entry then
339: //we don't decrypt it.
340: if (!(key.getName().equals("Contents")
341: && value instanceof COSString && potentialSignatures
342: .contains(dictionary))) {
343: decrypt(value, objNum, genNum);
344: }
345: }
346: }
347:
348: /**
349: * This will decrypt a string.
350: *
351: * @param string the string to decrypt.
352: * @param objNum The object number.
353: * @param genNum The object generation number.
354: *
355: * @throws CryptographyException If an error occurs during decryption.
356: * @throws IOException If an error occurs writing the new string.
357: */
358: private void decryptString(COSString string, long objNum,
359: long genNum) throws CryptographyException, IOException {
360: ByteArrayInputStream data = new ByteArrayInputStream(string
361: .getBytes());
362: ByteArrayOutputStream buffer = new ByteArrayOutputStream();
363: encryption.encryptData(objNum, genNum, encryptionKey, data,
364: buffer);
365: string.reset();
366: string.append(buffer.toByteArray());
367: }
368:
369: /**
370: * This will decrypt an array.
371: *
372: * @param array The array to decrypt.
373: * @param objNum The object number.
374: * @param genNum The object generation number.
375: *
376: * @throws CryptographyException If an error occurs during decryption.
377: * @throws IOException If there is an error accessing the data.
378: */
379: private void decryptArray(COSArray array, long objNum, long genNum)
380: throws CryptographyException, IOException {
381: for (int i = 0; i < array.size(); i++) {
382: decrypt(array.get(i), objNum, genNum);
383: }
384: }
385: }
|