1 /* 2 * Licensed to the Apache Software Foundation (ASF) under one or more 3 * contributor license agreements. See the NOTICE file distributed with 4 * this work for additional information regarding copyright ownership. 5 * The ASF licenses this file to You under the Apache License, Version 2.0 6 * (the "License"); you may not use this file except in compliance with 7 * the License. You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 */ 17 18 package org.apache.pdfbox.pdmodel.encryption; 19 20 import java.io.ByteArrayInputStream; 21 import java.io.ByteArrayOutputStream; 22 import java.io.IOException; 23 import java.security.AlgorithmParameterGenerator; 24 import java.security.AlgorithmParameters; 25 import java.security.GeneralSecurityException; 26 import java.security.KeyStoreException; 27 import java.security.MessageDigest; 28 import java.security.NoSuchAlgorithmException; 29 import java.security.NoSuchProviderException; 30 import java.security.SecureRandom; 31 import java.security.Security; 32 import java.security.cert.X509Certificate; 33 import java.util.Iterator; 34 35 import javax.crypto.Cipher; 36 import javax.crypto.KeyGenerator; 37 import javax.crypto.SecretKey; 38 39 import org.bouncycastle.asn1.ASN1InputStream; 40 import org.bouncycastle.asn1.DERObject; 41 import org.bouncycastle.asn1.DERObjectIdentifier; 42 import org.bouncycastle.asn1.DEROctetString; 43 import org.bouncycastle.asn1.DEROutputStream; 44 import org.bouncycastle.asn1.DERSet; 45 import org.bouncycastle.asn1.cms.ContentInfo; 46 import org.bouncycastle.asn1.cms.EncryptedContentInfo; 47 import org.bouncycastle.asn1.cms.EnvelopedData; 48 import org.bouncycastle.asn1.cms.IssuerAndSerialNumber; 49 import org.bouncycastle.asn1.cms.KeyTransRecipientInfo; 50 import org.bouncycastle.asn1.cms.RecipientIdentifier; 51 import org.bouncycastle.asn1.cms.RecipientInfo; 52 import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers; 53 import org.bouncycastle.asn1.x509.AlgorithmIdentifier; 54 import org.bouncycastle.asn1.x509.TBSCertificateStructure; 55 import org.bouncycastle.cms.CMSEnvelopedData; 56 import org.bouncycastle.cms.CMSException; 57 import org.bouncycastle.cms.RecipientInformation; 58 import org.bouncycastle.jce.provider.BouncyCastleProvider; 59 import org.apache.pdfbox.cos.COSString; 60 import org.apache.pdfbox.exceptions.CryptographyException; 61 import org.apache.pdfbox.pdmodel.PDDocument; 62 63 /** 64 * This class implements the public key security handler 65 * described in the PDF specification. 66 * 67 * @see PDF Spec 1.6 p104 68 * 69 * @see PublicKeyProtectionPolicy to see how to protect document with this security handler. 70 * 71 * @author Benoit Guillon (benoit.guillon@snv.jussieu.fr) 72 * @version $Revision: 1.3 $ 73 */ 74 public class PublicKeySecurityHandler extends SecurityHandler 75 { 76 77 /** 78 * The filter name. 79 */ 80 public static final String FILTER = "Adobe.PubSec"; 81 82 private static final String SUBFILTER = "adbe.pkcs7.s4"; 83 84 private PublicKeyProtectionPolicy policy = null; 85 86 /** 87 * Constructor. 88 */ 89 public PublicKeySecurityHandler() 90 { 91 } 92 93 /** 94 * Constructor used for encryption. 95 * 96 * @param p The protection policy. 97 */ 98 public PublicKeySecurityHandler(PublicKeyProtectionPolicy p) 99 { 100 policy = p; 101 this.keyLength = policy.getEncryptionKeyLength(); 102 } 103 104 /** 105 * Decrypt the document. 106 * 107 * @param doc The document to decrypt. 108 * @param decryptionMaterial The data used to decrypt the document. 109 * 110 * @throws CryptographyException If there is an error during decryption. 111 * @throws IOException If there is an error accessing data. 112 */ 113 public void decryptDocument(PDDocument doc, DecryptionMaterial decryptionMaterial) 114 throws CryptographyException, IOException 115 { 116 this.document = doc; 117 118 PDEncryptionDictionary dictionary = doc.getEncryptionDictionary(); 119 120 if(dictionary.getLength() != 0) 121 { 122 this.keyLength = dictionary.getLength(); 123 } 124 125 if(!(decryptionMaterial instanceof PublicKeyDecryptionMaterial)) 126 { 127 throw new CryptographyException( 128 "Provided decryption material is not compatible with the document"); 129 } 130 131 PublicKeyDecryptionMaterial material = (PublicKeyDecryptionMaterial)decryptionMaterial; 132 133 try 134 { 135 boolean foundRecipient = false; 136 137 // the decrypted content of the enveloped data that match 138 // the certificate in the decryption material provided 139 byte[] envelopedData = null; 140 141 // the bytes of each recipient in the recipients array 142 byte[][] recipientFieldsBytes = new byte[dictionary.getRecipientsLength()][]; 143 144 int recipientFieldsLength = 0; 145 146 for(int i=0; i<dictionary.getRecipientsLength(); i++) 147 { 148 COSString recipientFieldString = dictionary.getRecipientStringAt(i); 149 byte[] recipientBytes = recipientFieldString.getBytes(); 150 CMSEnvelopedData data = new CMSEnvelopedData(recipientBytes); 151 Iterator recipCertificatesIt = data.getRecipientInfos().getRecipients().iterator(); 152 while(recipCertificatesIt.hasNext()) 153 { 154 RecipientInformation ri = 155 (RecipientInformation)recipCertificatesIt.next(); 156 // Impl: if a matching certificate was previously found it is an error, 157 // here we just don't care about it 158 if(ri.getRID().match(material.getCertificate()) && !foundRecipient) 159 { 160 foundRecipient = true; 161 envelopedData = ri.getContent(material.getPrivateKey(), "BC"); 162 } 163 } 164 recipientFieldsBytes[i] = recipientBytes; 165 recipientFieldsLength += recipientBytes.length; 166 } 167 if(!foundRecipient || envelopedData == null) 168 { 169 throw new CryptographyException("The certificate matches no recipient entry"); 170 } 171 if(envelopedData.length != 24) 172 { 173 throw new CryptographyException("The enveloped data does not contain 24 bytes"); 174 } 175 // now envelopedData contains: 176 // - the 20 bytes seed 177 // - the 4 bytes of permission for the current user 178 179 byte[] accessBytes = new byte[4]; 180 System.arraycopy(envelopedData, 20, accessBytes, 0, 4); 181 182 currentAccessPermission = new AccessPermission(accessBytes); 183 currentAccessPermission.setReadOnly(); 184 185 // what we will put in the SHA1 = the seed + each byte contained in the recipients array 186 byte[] sha1Input = new byte[recipientFieldsLength + 20]; 187 188 // put the seed in the sha1 input 189 System.arraycopy(envelopedData, 0, sha1Input, 0, 20); 190 191 // put each bytes of the recipients array in the sha1 input 192 int sha1InputOffset = 20; 193 for(int i=0; i<recipientFieldsBytes.length; i++) 194 { 195 System.arraycopy( 196 recipientFieldsBytes[i], 0, 197 sha1Input, sha1InputOffset, recipientFieldsBytes[i].length); 198 sha1InputOffset += recipientFieldsBytes[i].length; 199 } 200 201 MessageDigest md = MessageDigest.getInstance("SHA-1"); 202 byte[] mdResult = md.digest(sha1Input); 203 204 // we have the encryption key ... 205 encryptionKey = new byte[this.keyLength/8]; 206 System.arraycopy(mdResult, 0, encryptionKey, 0, this.keyLength/8); 207 208 proceedDecryption(); 209 210 211 } 212 catch(CMSException e) 213 { 214 throw new CryptographyException(e); 215 } 216 catch(KeyStoreException e) 217 { 218 throw new CryptographyException(e); 219 } 220 catch(NoSuchProviderException e) 221 { 222 throw new CryptographyException(e); 223 } 224 catch(NoSuchAlgorithmException e) 225 { 226 throw new CryptographyException(e); 227 } 228 229 } 230 231 /** 232 * Prepare the document for encryption. 233 * 234 * @param doc The document that will be encrypted. 235 * 236 * @throws CryptographyException If there is an error while encrypting. 237 */ 238 public void prepareDocumentForEncryption(PDDocument doc) throws CryptographyException 239 { 240 241 try 242 { 243 Security.addProvider(new BouncyCastleProvider()); 244 245 PDEncryptionDictionary dictionary = doc.getEncryptionDictionary(); 246 if (dictionary == null) 247 { 248 dictionary = new PDEncryptionDictionary(); 249 } 250 251 dictionary.setFilter(FILTER); 252 dictionary.setLength(this.keyLength); 253 dictionary.setVersion(2); 254 dictionary.setSubFilter(SUBFILTER); 255 256 byte[][] recipientsField = new byte[policy.getRecipientsNumber()][]; 257 258 // create the 20 bytes seed 259 260 byte[] seed = new byte[20]; 261 262 KeyGenerator key = KeyGenerator.getInstance("AES"); 263 key.init(192, new SecureRandom()); 264 SecretKey sk = key.generateKey(); 265 System.arraycopy(sk.getEncoded(), 0, seed, 0, 20); // create the 20 bytes seed 266 267 268 Iterator it = policy.getRecipientsIterator(); 269 int i = 0; 270 271 272 while(it.hasNext()) 273 { 274 PublicKeyRecipient recipient = (PublicKeyRecipient)it.next(); 275 X509Certificate certificate = recipient.getX509(); 276 int permission = recipient.getPermission().getPermissionBytesForPublicKey(); 277 278 byte[] pkcs7input = new byte[24]; 279 byte one = (byte)(permission); 280 byte two = (byte)(permission >>> 8); 281 byte three = (byte)(permission >>> 16); 282 byte four = (byte)(permission >>> 24); 283 284 System.arraycopy(seed, 0, pkcs7input, 0, 20); // put this seed in the pkcs7 input 285 286 pkcs7input[20] = four; 287 pkcs7input[21] = three; 288 pkcs7input[22] = two; 289 pkcs7input[23] = one; 290 291 DERObject obj = createDERForRecipient(pkcs7input, certificate); 292 293 ByteArrayOutputStream baos = new ByteArrayOutputStream(); 294 295 DEROutputStream k = new DEROutputStream(baos); 296 297 k.writeObject(obj); 298 299 recipientsField[i] = baos.toByteArray(); 300 301 i++; 302 } 303 304 dictionary.setRecipients(recipientsField); 305 306 int sha1InputLength = seed.length; 307 308 for(int j=0; j<dictionary.getRecipientsLength(); j++) 309 { 310 COSString string = dictionary.getRecipientStringAt(j); 311 sha1InputLength += string.getBytes().length; 312 } 313 314 315 byte[] sha1Input = new byte[sha1InputLength]; 316 317 System.arraycopy(seed, 0, sha1Input, 0, 20); 318 319 int sha1InputOffset = 20; 320 321 322 for(int j=0; j<dictionary.getRecipientsLength(); j++) 323 { 324 COSString string = dictionary.getRecipientStringAt(j); 325 System.arraycopy( 326 string.getBytes(), 0, 327 sha1Input, sha1InputOffset, string.getBytes().length); 328 sha1InputOffset += string.getBytes().length; 329 } 330 331 MessageDigest md = MessageDigest.getInstance("SHA-1"); 332 333 byte[] mdResult = md.digest(sha1Input); 334 335 this.encryptionKey = new byte[this.keyLength/8]; 336 System.arraycopy(mdResult, 0, this.encryptionKey, 0, this.keyLength/8); 337 338 doc.setEncryptionDictionary(dictionary); 339 doc.getDocument().setEncryptionDictionary(dictionary.encryptionDictionary); 340 341 } 342 catch(NoSuchAlgorithmException ex) 343 { 344 throw new CryptographyException(ex); 345 } 346 catch(NoSuchProviderException ex) 347 { 348 throw new CryptographyException(ex); 349 } 350 catch(Exception e) 351 { 352 e.printStackTrace(); 353 throw new CryptographyException(e); 354 } 355 356 } 357 358 private DERObject createDERForRecipient(byte[] in, X509Certificate cert) 359 throws IOException, 360 GeneralSecurityException 361 { 362 363 String s = "1.2.840.113549.3.2"; 364 365 AlgorithmParameterGenerator algorithmparametergenerator = AlgorithmParameterGenerator.getInstance(s); 366 AlgorithmParameters algorithmparameters = algorithmparametergenerator.generateParameters(); 367 ByteArrayInputStream bytearrayinputstream = new ByteArrayInputStream(algorithmparameters.getEncoded("ASN.1")); 368 ASN1InputStream asn1inputstream = new ASN1InputStream(bytearrayinputstream); 369 DERObject derobject = asn1inputstream.readObject(); 370 KeyGenerator keygenerator = KeyGenerator.getInstance(s); 371 keygenerator.init(128); 372 SecretKey secretkey = keygenerator.generateKey(); 373 Cipher cipher = Cipher.getInstance(s); 374 cipher.init(1, secretkey, algorithmparameters); 375 byte[] abyte1 = cipher.doFinal(in); 376 DEROctetString deroctetstring = new DEROctetString(abyte1); 377 KeyTransRecipientInfo keytransrecipientinfo = computeRecipientInfo(cert, secretkey.getEncoded()); 378 DERSet derset = new DERSet(new RecipientInfo(keytransrecipientinfo)); 379 AlgorithmIdentifier algorithmidentifier = new AlgorithmIdentifier(new DERObjectIdentifier(s), derobject); 380 EncryptedContentInfo encryptedcontentinfo = 381 new EncryptedContentInfo(PKCSObjectIdentifiers.data, algorithmidentifier, deroctetstring); 382 EnvelopedData env = new EnvelopedData(null, derset, encryptedcontentinfo, null); 383 ContentInfo contentinfo = 384 new ContentInfo(PKCSObjectIdentifiers.envelopedData, env); 385 return contentinfo.getDERObject(); 386 } 387 388 private KeyTransRecipientInfo computeRecipientInfo(X509Certificate x509certificate, byte[] abyte0) 389 throws GeneralSecurityException, IOException 390 { 391 ASN1InputStream asn1inputstream = 392 new ASN1InputStream(new ByteArrayInputStream(x509certificate.getTBSCertificate())); 393 TBSCertificateStructure tbscertificatestructure = 394 TBSCertificateStructure.getInstance(asn1inputstream.readObject()); 395 AlgorithmIdentifier algorithmidentifier = tbscertificatestructure.getSubjectPublicKeyInfo().getAlgorithmId(); 396 IssuerAndSerialNumber issuerandserialnumber = 397 new IssuerAndSerialNumber( 398 tbscertificatestructure.getIssuer(), 399 tbscertificatestructure.getSerialNumber().getValue()); 400 Cipher cipher = Cipher.getInstance(algorithmidentifier.getObjectId().getId()); 401 cipher.init(1, x509certificate.getPublicKey()); 402 DEROctetString deroctetstring = new DEROctetString(cipher.doFinal(abyte0)); 403 RecipientIdentifier recipId = new RecipientIdentifier(issuerandserialnumber); 404 return new KeyTransRecipientInfo( recipId, algorithmidentifier, deroctetstring); 405 } 406 407 }