Home » pdfbox-1.1.0-src » org.apache.pdfbox.pdmodel.encryption » [javadoc | source]

    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   }

Home » pdfbox-1.1.0-src » org.apache.pdfbox.pdmodel.encryption » [javadoc | source]