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}