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.StandardDecryptionMaterial; 029import org.apache.pdfbox.pdmodel.encryption.StandardProtectionPolicy; 030import org.nuxeo.ecm.core.api.Blob; 031import org.nuxeo.ecm.core.api.Blobs; 032import org.nuxeo.ecm.core.api.NuxeoException; 033import org.nuxeo.ecm.core.api.impl.blob.FileBlob; 034 035/** 036 * Encrypt/Decrypt a PDF. 037 * <p> 038 * Notice that encryption is not only about pure encryption, it is also about setting the available features on the pdf: 039 * can print, copy, modify, ... (see PDFBox {@link AccessPermission}). 040 * <p> 041 * To use this class and its encrypt()/removeEncryption() methods you must always use the appropriate setters first, to 042 * set the misc info (original owner password so an encrypted PDF can be handled, encryption key length, ...). 043 * 044 * @since 8.10 045 */ 046public class PDFEncryption { 047 048 private Blob pdfBlob; 049 050 private PDDocument pdfDoc; 051 052 private static final List<Integer> ALLOWED_LENGTH = Arrays.asList(40, 128); 053 054 private static final int DEFAULT_KEYLENGTH = 128; 055 056 private int keyLength = DEFAULT_KEYLENGTH; 057 058 private String originalOwnerPwd; 059 060 private String ownerPwd; 061 062 private String userPwd; 063 064 /** 065 * Basic constructor. 066 * 067 * @param inBlob Input blob. 068 */ 069 public PDFEncryption(Blob inBlob) { 070 pdfBlob = inBlob; 071 } 072 073 /** 074 * Encrypts the PDF with readonly permission. 075 * <p> 076 * WARNING: If you are familiar with PDFBox {@link AccessPermission}, notice our encryptReadOnly() method is not the 077 * same as {@link AccessPermission#AccessPermission#setReadOnly}. The latter just makes sure the code cannot call 078 * other setter later on. 079 * <p> 080 * <code>encryptReadOnly</code> sets the following permissions on the document: 081 * <ul> 082 * <li>Can print: True</li> 083 * <li>Can Modify: False</li> 084 * <li>Can Extract Content: True</li> 085 * <li>Can Add/Modify annotations: False</li> 086 * <li>Can Fill Forms: False</li> 087 * <li>Can Extract Info for Accessibility: True</li> 088 * <li>Can Assemble: False</li> 089 * <li>Can print degraded: True</li> 090 * </ul> 091 * <p> 092 * <b>IMPORTANT 093 * </p> 094 * It is required that the following setters are called <i>before</i>: 095 * <ul> 096 * <li>{@link PDFEncryption#setOriginalOwnerPwd}: Only if the original PDF already is encrypted. This password 097 * allows to open it for modification.</li> 098 * <li>{@link PDFEncryption#setKeyLength}: To set the length of the key.</li> 099 * <li>{@link PDFEncryption#setOwnerPwd}: The password for the owner. If not called, <code>originalOwnerPwd</code> 100 * is used instead.</li> 101 * <li>{@link PDFEncryption#setUserPwd}: The password for the user.</li> 102 * </ul> 103 * 104 * @return A copy of the blob with the readonly permissions set. 105 */ 106 public Blob encryptReadOnly() { 107 AccessPermission ap = new AccessPermission(); 108 ap.setCanPrint(true); 109 ap.setCanModify(false); 110 ap.setCanExtractContent(true); 111 ap.setCanModifyAnnotations(false); 112 ap.setCanFillInForm(false); 113 ap.setCanExtractForAccessibility(true); 114 ap.setCanAssembleDocument(false); 115 ap.setCanPrintDegraded(true); 116 return encrypt(ap); 117 } 118 119 /** 120 * Encrypts the PDF with the new permissions (see {@link AccessPermission}). 121 * <p> 122 * <b>IMPORTANT 123 * </p> 124 * It is required that the following setters are called <i>before</i>: 125 * <ul> 126 * <li>{@link PDFEncryption#setOriginalOwnerPwd}: Only if the original PDF already is encrypted. This password 127 * allows to open it for modification.</li> 128 * <li>{@link PDFEncryption#setKeyLength}: To set the length of the key.</li> 129 * <li>{@link PDFEncryption#setOwnerPwd}: The password for the owner. If not called, <code>originalOwnerPwd</code> 130 * is used instead.</li> 131 * <li>{@link PDFEncryption#setUserPwd}: The password for the user.</li> 132 * </ul> 133 * 134 * @param inPerm Input permissions. 135 * @return A copy of the blob with the new permissions set. 136 */ 137 public Blob encrypt(AccessPermission inPerm) { 138 if (!ALLOWED_LENGTH.contains(keyLength)) { 139 throw new NuxeoException(keyLength + " is not an allowed length for the encrytion key"); 140 } 141 ownerPwd = (StringUtils.isBlank(ownerPwd)) ? originalOwnerPwd : ownerPwd; 142 try { 143 StandardProtectionPolicy spp = new StandardProtectionPolicy(ownerPwd, userPwd, inPerm); 144 spp.setEncryptionKeyLength(keyLength); 145 pdfDoc = PDDocument.load(pdfBlob.getFile()); 146 pdfDoc.protect(spp); 147 Blob result = Blobs.createBlobWithExtension(".pdf"); 148 pdfDoc.save(result.getFile()); 149 result.setMimeType("application/pdf"); 150 if (StringUtils.isNotBlank(pdfBlob.getFilename())) { 151 result.setFilename(pdfBlob.getFilename()); 152 } 153 pdfDoc.close(); 154 FileBlob fb = new FileBlob(result.getFile()); 155 fb.setMimeType("application/pdf"); 156 return fb; 157 } catch (Exception e) { 158 throw new NuxeoException("Failed to encrypt the PDF", e); 159 } 160 } 161 162 /** 163 * Removes all protection from the PDF, returns a copy of it. If the PDF was not encrypted, just returns a copy of 164 * it with no changes. 165 * <p> 166 * <b>IMPORTANT 167 * </p> 168 * If the PDF is encrypted, it is required for {@link PDFEncryption#setOriginalOwnerPwd} to be called before to 169 * <code>removeEncryption</code>. 170 * <ul> 171 * <li>{@link PDFEncryption#setOriginalOwnerPwd}: Only if the original PDF already is encrypted. This password 172 * allows to open it for modification.</li> 173 * <li>{@link PDFEncryption#setKeyLength}: To set the length of the key.</li> 174 * <li>{@link PDFEncryption#setOwnerPwd}: The password for the owner. If not called, <code>originalOwnerPwd</code> 175 * is used instead.</li> 176 * <li>{@link PDFEncryption#setUserPwd}: The password for the user.</li> 177 * </ul> 178 */ 179 public Blob removeEncryption() { 180 try { 181 String password = (StringUtils.isBlank(originalOwnerPwd)) ? ownerPwd : originalOwnerPwd; 182 pdfDoc = PDDocument.load(pdfBlob.getFile()); 183 if (!pdfDoc.isEncrypted()) { 184 pdfDoc.close(); 185 return pdfBlob; 186 } 187 pdfDoc.openProtection(new StandardDecryptionMaterial(password)); 188 pdfDoc.setAllSecurityToBeRemoved(true); 189 Blob result = Blobs.createBlobWithExtension(".pdf"); 190 pdfDoc.save(result.getFile()); 191 result.setMimeType("application/pdf"); 192 if (StringUtils.isNotBlank(pdfBlob.getFilename())) { 193 result.setFilename(pdfBlob.getFilename()); 194 } 195 pdfDoc.close(); 196 FileBlob fb = new FileBlob(result.getFile()); 197 fb.setMimeType("application/pdf"); 198 return fb; 199 } catch (Exception e) { 200 throw new NuxeoException("Failed to remove encryption of the PDF", e); 201 } 202 } 203 204 /** 205 * Set the lentgh of the key to be used for encryption. 206 * <p> 207 * Possible values are 40 and 128. Default value is 128 if <code>keyLength</code> is <= 0. 208 * 209 * @param keyLength Lenght of the encryption key. 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}