001/* 002 * (C) Copyright 2016-2018 Nuxeo (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; 024 025import org.apache.commons.lang3.StringUtils; 026import org.apache.pdfbox.pdmodel.PDDocument; 027import org.apache.pdfbox.pdmodel.encryption.AccessPermission; 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#setReadOnly}. The latter just makes sure the code cannot call other setter later 077 * 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</b> 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</b> 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(), originalOwnerPwd); 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</b> 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(), password); 182 if (!pdfDoc.isEncrypted()) { 183 pdfDoc.close(); 184 return pdfBlob; 185 } 186 pdfDoc.setAllSecurityToBeRemoved(true); 187 Blob result = Blobs.createBlobWithExtension(".pdf"); 188 pdfDoc.save(result.getFile()); 189 result.setMimeType("application/pdf"); 190 if (StringUtils.isNotBlank(pdfBlob.getFilename())) { 191 result.setFilename(pdfBlob.getFilename()); 192 } 193 pdfDoc.close(); 194 FileBlob fb = new FileBlob(result.getFile()); 195 fb.setMimeType("application/pdf"); 196 return fb; 197 } catch (Exception e) { 198 throw new NuxeoException("Failed to remove encryption of the PDF", e); 199 } 200 } 201 202 /** 203 * Set the lentgh of the key to be used for encryption. 204 * <p> 205 * Possible values are 40 and 128. Default value is 128 if <code>keyLength</code> is <= 0. 206 * 207 * @param keyLength Length of the encryption key. 208 */ 209 public void setKeyLength(int keyLength) throws NuxeoException { 210 if (keyLength < 1) { 211 keyLength = DEFAULT_KEYLENGTH; 212 } else { 213 if (!ALLOWED_LENGTH.contains(keyLength)) { 214 throw new NuxeoException("Cannot use " + keyLength + " is not allowed as lenght for the encrytion key"); 215 } 216 } 217 this.keyLength = keyLength; 218 } 219 220 /** 221 * Set the password to use when opening a protected PDF. Must be called <i>before</i> encrypting the PDF. 222 * 223 * @param originalOwnerPwd Original owner password. 224 */ 225 public void setOriginalOwnerPwd(String originalOwnerPwd) { 226 this.originalOwnerPwd = originalOwnerPwd; 227 } 228 229 /** 230 * Set the owner password to use when encrypting PDF. Must be called <i>before</i> encrypting the PDF. 231 * <p> 232 * Owners can do whatever they want to the PDF (modify, change protection, ...). 233 * 234 * @param ownerPwd Owner password. 235 */ 236 public void setOwnerPwd(String ownerPwd) { 237 this.ownerPwd = ownerPwd; 238 } 239 240 /** 241 * Set the user password to use when encrypting PDF. Must be called <i>before</i> encrypting the PDF. 242 * <p> 243 * Users can have less rights than owners (for example, not being able to remove protection). 244 * 245 * @param userPwd User password. 246 */ 247 public void setUserPwd(String userPwd) { 248 this.userPwd = userPwd; 249 } 250 251}