/* * Copyright 2006-2017 ICEsoft Technologies Canada Corp. * * Licensed 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. */ package org.icepdf.core.pobjects.security; import org.icepdf.core.pobjects.Name; import org.icepdf.core.pobjects.Reference; import java.io.InputStream; import java.util.HashMap; /** * <p>ICEpdf's standard security handler allows access permissions and up to two passwords * to be specified for a document: an owner password and a user password. An * application's decision to encrypt a document is based on whether the user * creating the document specifies any passwords or access restrictions (for example, in a * security settings dialog that the user can invoke before saving the PDF file); if so, * the document is encrypted, and the permissions and information required to validate * the passwords are stored in the encryption dictionary. (An application may * also create an encrypted document without any user interaction, if it has some * other source of information about what passwords and permissions to use.)</p> * * <p>If a user attempts to open an encrypted document that has a user password, the * viewer application should prompt for a password. Correctly supplying either * password allows the user to open the document, decrypt it, and display it on the * screen. If the document does not have a user password, no password is requested; * the viewer application can simply open, decrypt, and display the document. * Whether additional operations are allowed on a decrypted document depends on * which password (if any) was supplied when the document was opened and on * any access restrictions that were specified when the document was created: * <ul> * <li>Opening the document with the correct owner password (assuming it is not * the same as the user password) allows full (owner) access to the * document. This unlimited access includes the ability to change the * document's passwords and access permissions.</li> * * <li>Opening the document with the correct user password (or opening a * document that does not have a user password) allows additional operations * to be performed according to the user access permissions specified in the * document's encryption dictionary.</li> * </ul> * * <p>Access permissions are specified in the form of flags corresponding to the * various operations, and the set of operations to which they correspond, * depends in turn on the security handler's revision number (also stored in the * encryption dictionary). If the revision number is 2 or greater, the * operations to which user access can be controlled are as follows: * * <ul> * <li>Modifying the document's contents</li> * * <li>Copying or otherwise extracting text and graphics from the document, * including extraction for accessibility purposes (that is, to make the * contents of the document accessible through assistive technologies such * as screen readers or Braille output devices</li> * * <li>Adding or modifying text annotations and interactive form fields</li> * * <li>Printing the document</li> * </ul> * * <p>If the security handler's revision number is 3 or greater, user access to the * following operations can be controlled more selectively: * <ul> * <li>Filling in forms (that is, filling in existing interactive form fields) * and signing the document (which amounts to filling in existing signature * fields, a type of interactive form field)</li> * * <li>Assembling the document: inserting, rotating, or deleting pages and * creating navigation elements such as bookmarks or thumbnail images </li> * * <li>Printing to a representation from which a faithful digital copy of the * PDF content could be generated. Disallowing such printing may result in * degradation of output quality (a feature implemented as "Print As Image" * in Acrobat)</li> * </ul> * <p>In addition, revision 3 enables the extraction of text and graphics (in * support of accessibility to disabled users or for other purposes) to be * controlled separately. Beginning with revision 4, the standard security * handler supports crypt filters. The support is limited to the Identity crypt * filter and crypt filters named StdCF whose dictionaries contain a CFM value * of V2 and an AuthEvent value of DocOpen.</p> * * @since 1.1 */ public class StandardSecurityHandler extends SecurityHandler { public static final Name NAME_KEY = new Name("Name"); public static final Name IDENTITY_KEY = new Name("Identity"); // StandardEncryption holds algorithms specific to adobe standard encryption private StandardEncryption standardEncryption = null; // encryption key used for encryption, Standard encryption is symmetric, so // only one key is needed. private byte[] encryptionKey; // initiated flag private boolean initiated; // string to store password used for decoding, the user password is always // used for encryption, never the user password. private String password; public StandardSecurityHandler(EncryptionDictionary encryptionDictionary) { super(encryptionDictionary); // Full name of handler handlerName = "Adobe Standard Security"; } public boolean isAuthorized(String password) { if (encryptionDictionary.getRevisionNumber() < 5) { boolean value = standardEncryption.authenticateUserPassword(password); // check password against user password if (!value) { // check password against owner password value = standardEncryption.authenticateOwnerPassword(password); // Get user, password, as it is used for generating encryption keys if (value) { this.password = standardEncryption.getUserPassword(); } } else { // assign password for future use this.password = password; } return value; } else if (encryptionDictionary.getRevisionNumber() == 5) { // try and calculate the document key. byte[] encryptionKey = standardEncryption.encryptionKeyAlgorithm( password, encryptionDictionary.getKeyLength()); this.password = password; return encryptionKey != null; } else { return false; } } public boolean isOwnerAuthorized(String password) { // owner password is not stored as it is not used for decryption if (encryptionDictionary.getRevisionNumber() < 5) { return standardEncryption.authenticateOwnerPassword(password); } else { return encryptionDictionary.isAuthenticatedOwnerPassword(); } } public boolean isUserAuthorized(String password) { // owner password is not stored as it is not used for decryption if (encryptionDictionary.getRevisionNumber() < 5) { boolean value = standardEncryption.authenticateUserPassword(password); if (value) { this.password = password; } return value; } else { return encryptionDictionary.isAuthenticatedUserPassword(); } } public byte[] encrypt(Reference objectReference, byte[] encryptionKey, byte[] data) { // check if crypt filters are being used and find out if V2 or AESV2 String algorithmType = getAlgorithmType(); // use the general encryption algorithm for encryption return standardEncryption.generalEncryptionAlgorithm( objectReference, encryptionKey, algorithmType, data, true); } public byte[] decrypt(Reference objectReference, byte[] encryptionKey, byte[] data) { // check if crypt filters are being used and find out if V2 or AESV2 String algorithmType = getAlgorithmType(); // use the general encryption algorithm for encryption return standardEncryption.generalEncryptionAlgorithm( objectReference, encryptionKey, algorithmType, data, false); } /** * Utility to determine encryption type used. */ private String getAlgorithmType() { String algorithmType; if (encryptionDictionary.getCryptFilter() != null) { CryptFilterEntry cryptFilterEntry = encryptionDictionary.getCryptFilter().getCryptFilterByName( encryptionDictionary.getStrF()); algorithmType = cryptFilterEntry.getCryptFilterMethod().getName(); } else { algorithmType = StandardEncryption.ENCRYPTION_TYPE_V2; } return algorithmType; } public InputStream decryptInputStream( Reference objectReference, byte[] encryptionKey, HashMap decodeParams, InputStream input) { return getInputStream(objectReference, encryptionKey, decodeParams, input, false); } public InputStream encryptInputStream( Reference objectReference, byte[] encryptionKey, HashMap decodeParams, InputStream input) { return getInputStream(objectReference, encryptionKey, decodeParams, input, true); } public InputStream getInputStream( Reference objectReference, byte[] encryptionKey, HashMap decodeParams, InputStream input, boolean encrypted) { // find the name of the crypt filter used in the CF dictionary CryptFilterEntry cryptFilter = null; if (decodeParams != null) { Name filterName = (Name) decodeParams.get(NAME_KEY); if (filterName != null) { // identity means don't use the cryprt filter or encryption at all // for the stream. if (filterName.equals(IDENTITY_KEY)) { return input; } else { // find the filter name in the encryption dictionary cryptFilter = encryptionDictionary. getCryptFilter().getCryptFilterByName(filterName); } } else if (encryptionDictionary.getCryptFilter() != null) { // corner case, some images treams also use the "decodeParams" // dictionary, if it doesn't contain a filter name then we // want to make sure we assign the standard one so the steam // can be unencrypted. cryptFilter = encryptionDictionary.getCryptFilter().getCryptFilterByName( encryptionDictionary.getStmF()); } } // We default to the method specified in by StrmF in the security dictionary else if (encryptionDictionary.getCryptFilter() != null) { cryptFilter = encryptionDictionary.getCryptFilter().getCryptFilterByName( encryptionDictionary.getStmF()); } // get the method used for the general encryption algorithm String algorithmType; if (cryptFilter != null) { algorithmType = cryptFilter.getCryptFilterMethod().getName(); } else { algorithmType = StandardEncryption.ENCRYPTION_TYPE_V2; } return standardEncryption.generalEncryptionInputStream( objectReference, encryptionKey, algorithmType, input, encrypted); } public byte[] getEncryptionKey() { if (!initiated) { // make sure class instance var have been setup this.init(); } // calculate the encryptionKey based on the given user name encryptionKey = standardEncryption.encryptionKeyAlgorithm( password, encryptionDictionary.getKeyLength()); return encryptionKey; } public byte[] getDecryptionKey() { return getEncryptionKey(); } public Permissions getPermissions() { if (!initiated) { // make sure class instance var have been setup this.init(); } return permissions; } public String getHandlerName() { return this.handlerName; } public void init() { // initiate a new instance standardEncryption = new StandardEncryption(encryptionDictionary); // initiate permissions permissions = new Permissions(encryptionDictionary); permissions.init(); // update flag initiated = true; } public void dispose() { standardEncryption = null; encryptionKey = null; permissions = null; // update flag initiated = false; } }