001/*
002 * (C) Copyright 2016 Nuxeo SA (http://nuxeo.com/) and others.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *     http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 *
016 * Contributors:
017 *     Thibaud Arguillere
018 *     Miguel Nixo
019 */
020package org.nuxeo.ecm.platform.pdf;
021
022import java.util.Arrays;
023import java.util.List;
024import org.apache.commons.lang.StringUtils;
025import org.apache.pdfbox.pdmodel.PDDocument;
026import org.apache.pdfbox.pdmodel.encryption.AccessPermission;
027import org.apache.pdfbox.pdmodel.encryption.StandardDecryptionMaterial;
028import org.apache.pdfbox.pdmodel.encryption.StandardProtectionPolicy;
029import org.nuxeo.ecm.core.api.Blob;
030import org.nuxeo.ecm.core.api.Blobs;
031import org.nuxeo.ecm.core.api.NuxeoException;
032import org.nuxeo.ecm.core.api.impl.blob.FileBlob;
033
034/**
035 * Encrypt/Decrypt a PDF.
036 * <p>
037 * Notice that encryption is not only about pure encryption, it is also about setting the available features on the pdf:
038 * can print, copy, modify, ... (see PDFBox {@link AccessPermission}).
039 * <p>
040 * To use this class and its encrypt()/removeEncryption() methods you must always use the appropriate setters first, to
041 * set the misc info (original owner password so an encrypted PDF can be handled, encryption key length, ...).
042 *
043 * @since 8.10
044 */
045public class PDFEncryption {
046
047    private Blob pdfBlob;
048
049    private PDDocument pdfDoc;
050
051    private static final List<Integer> ALLOWED_LENGTH = Arrays.asList(40, 128);
052
053    private static final int DEFAULT_KEYLENGTH = 128;
054
055    private int keyLength = DEFAULT_KEYLENGTH;
056
057    private String originalOwnerPwd;
058
059    private String ownerPwd;
060
061    private String userPwd;
062
063    /**
064     * Basic constructor.
065     *
066     * @param inBlob Input blob.
067     */
068    public PDFEncryption(Blob inBlob) {
069        pdfBlob = inBlob;
070    }
071
072    /**
073     * Encrypts the PDF with readonly permission.
074     * <p>
075     * WARNING: If you are familiar with PDFBox {@link AccessPermission}, notice our encryptReadOnly() method is not the
076     * same as {@link AccessPermission#AccessPermission#setReadOnly}. The latter just makes sure the code cannot call
077     * other setter later on.
078     * <p>
079     * <code>encryptReadOnly</code> sets the following permissions on the document:
080     * <ul>
081     * <li>Can print: True</li>
082     * <li>Can Modify: False</li>
083     * <li>Can Extract Content: True</li>
084     * <li>Can Add/Modify annotations: False</li>
085     * <li>Can Fill Forms: False</li>
086     * <li>Can Extract Info for Accessibility: True</li>
087     * <li>Can Assemble: False</li>
088     * <li>Can print degraded: True</li>
089     * </ul>
090     * <p>
091     * <b>IMPORTANT
092     * </p>
093     * It is required that the following setters are called <i>before</i>:
094     * <ul>
095     * <li>{@link PDFEncryption#setOriginalOwnerPwd}: Only if the original PDF already is encrypted. This password
096     * allows to open it for modification.</li>
097     * <li>{@link PDFEncryption#setKeyLength}: To set the length of the key.</li>
098     * <li>{@link PDFEncryption#setOwnerPwd}: The password for the owner. If not called, <code>originalOwnerPwd</code>
099     * is used instead.</li>
100     * <li>{@link PDFEncryption#setUserPwd}: The password for the user.</li>
101     * </ul>
102     *
103     * @return A copy of the blob with the readonly permissions set.
104     */
105    public Blob encryptReadOnly() {
106        AccessPermission ap = new AccessPermission();
107        ap.setCanPrint(true);
108        ap.setCanModify(false);
109        ap.setCanExtractContent(true);
110        ap.setCanModifyAnnotations(false);
111        ap.setCanFillInForm(false);
112        ap.setCanExtractForAccessibility(true);
113        ap.setCanAssembleDocument(false);
114        ap.setCanPrintDegraded(true);
115        return encrypt(ap);
116    }
117
118    /**
119     * Encrypts the PDF with the new permissions (see {@link AccessPermission}).
120     * <p>
121     * <b>IMPORTANT
122     * </p>
123     * It is required that the following setters are called <i>before</i>:
124     * <ul>
125     * <li>{@link PDFEncryption#setOriginalOwnerPwd}: Only if the original PDF already is encrypted. This password
126     * allows to open it for modification.</li>
127     * <li>{@link PDFEncryption#setKeyLength}: To set the length of the key.</li>
128     * <li>{@link PDFEncryption#setOwnerPwd}: The password for the owner. If not called, <code>originalOwnerPwd</code>
129     * is used instead.</li>
130     * <li>{@link PDFEncryption#setUserPwd}: The password for the user.</li>
131     * </ul>
132     *
133     * @param inPerm Input permissions.
134     * @return A copy of the blob with the new permissions set.
135     */
136    public Blob encrypt(AccessPermission inPerm) {
137        if (!ALLOWED_LENGTH.contains(keyLength)) {
138            throw new NuxeoException(keyLength + " is not an allowed length for the encrytion key");
139        }
140        ownerPwd = (StringUtils.isBlank(ownerPwd)) ? originalOwnerPwd : ownerPwd;
141        try {
142            StandardProtectionPolicy spp = new StandardProtectionPolicy(ownerPwd, userPwd, inPerm);
143            spp.setEncryptionKeyLength(keyLength);
144            pdfDoc = PDDocument.load(pdfBlob.getFile());
145            pdfDoc.protect(spp);
146            Blob result = Blobs.createBlobWithExtension(".pdf");
147            pdfDoc.save(result.getFile());
148            result.setMimeType("application/pdf");
149            if (StringUtils.isNotBlank(pdfBlob.getFilename())) {
150                result.setFilename(pdfBlob.getFilename());
151            }
152            pdfDoc.close();
153            FileBlob fb = new FileBlob(result.getFile());
154            fb.setMimeType("application/pdf");
155            return fb;
156        } catch (Exception e) {
157            throw new NuxeoException("Failed to encrypt the PDF", e);
158        }
159    }
160
161    /**
162     * Removes all protection from the PDF, returns a copy of it. If the PDF was not encrypted, just returns a copy of
163     * it with no changes.
164     * <p>
165     * <b>IMPORTANT
166     * </p>
167     * If the PDF is encrypted, it is required for {@link PDFEncryption#setOriginalOwnerPwd} to be called before to
168     * <code>removeEncryption</code>.
169     * <ul>
170     * <li>{@link PDFEncryption#setOriginalOwnerPwd}: Only if the original PDF already is encrypted. This password
171     * allows to open it for modification.</li>
172     * <li>{@link PDFEncryption#setKeyLength}: To set the length of the key.</li>
173     * <li>{@link PDFEncryption#setOwnerPwd}: The password for the owner. If not called, <code>originalOwnerPwd</code>
174     * is used instead.</li>
175     * <li>{@link PDFEncryption#setUserPwd}: The password for the user.</li>
176     * </ul>
177     */
178    public Blob removeEncryption() {
179        try {
180            String password = (StringUtils.isBlank(originalOwnerPwd)) ? ownerPwd : originalOwnerPwd;
181            pdfDoc = PDDocument.load(pdfBlob.getFile());
182            if (!pdfDoc.isEncrypted()) {
183                pdfDoc.close();
184                return pdfBlob;
185            }
186            pdfDoc.openProtection(new StandardDecryptionMaterial(password));
187            pdfDoc.setAllSecurityToBeRemoved(true);
188            Blob result = Blobs.createBlobWithExtension(".pdf");
189            pdfDoc.save(result.getFile());
190            result.setMimeType("application/pdf");
191            if (StringUtils.isNotBlank(pdfBlob.getFilename())) {
192                result.setFilename(pdfBlob.getFilename());
193            }
194            pdfDoc.close();
195            FileBlob fb = new FileBlob(result.getFile());
196            fb.setMimeType("application/pdf");
197            return fb;
198        } catch (Exception e) {
199            throw new NuxeoException("Failed to remove encryption of the PDF", e);
200        }
201    }
202
203    /**
204     * Set the lentgh of the key to be used for encryption.
205     * <p>
206     * Possible values are 40 and 128. Default value is 128 if <code>keyLength</code> is <= 0.
207     *
208     * @param keyLength Lenght of the encryption key.
209     * @throws NuxeoException
210     */
211    public void setKeyLength(int keyLength) throws NuxeoException {
212        if (keyLength < 1) {
213            keyLength = DEFAULT_KEYLENGTH;
214        } else {
215            if (!ALLOWED_LENGTH.contains(keyLength)) {
216                throw new NuxeoException("Cannot use " + keyLength + " is not allowed as lenght for the encrytion key");
217            }
218        }
219        this.keyLength = keyLength;
220    }
221
222    /**
223     * Set the password to use when opening a protected PDF. Must be called <i>before</i> encrypting the PDF.
224     *
225     * @param originalOwnerPwd Original owner password.
226     */
227    public void setOriginalOwnerPwd(String originalOwnerPwd) {
228        this.originalOwnerPwd = originalOwnerPwd;
229    }
230
231    /**
232     * Set the owner password to use when encrypting PDF. Must be called <i>before</i> encrypting the PDF.
233     * <p>
234     * Owners can do whatever they want to the PDF (modify, change protection, ...).
235     *
236     * @param ownerPwd Owner password.
237     */
238    public void setOwnerPwd(String ownerPwd) {
239        this.ownerPwd = ownerPwd;
240    }
241
242    /**
243     * Set the user password to use when encrypting PDF. Must be called <i>before</i> encrypting the PDF.
244     * <p>
245     * Users can have less rights than owners (for example, not being able to remove protection).
246     *
247     * @param userPwd User password.
248     */
249    public void setUserPwd(String userPwd) {
250        this.userPwd = userPwd;
251    }
252
253}