001: /**
002: * Copyright (c) 2003-2006, www.pdfbox.org
003: * All rights reserved.
004: *
005: * Redistribution and use in source and binary forms, with or without
006: * modification, are permitted provided that the following conditions are met:
007: *
008: * 1. Redistributions of source code must retain the above copyright notice,
009: * this list of conditions and the following disclaimer.
010: * 2. Redistributions in binary form must reproduce the above copyright notice,
011: * this list of conditions and the following disclaimer in the documentation
012: * and/or other materials provided with the distribution.
013: * 3. Neither the name of pdfbox; nor the names of its
014: * contributors may be used to endorse or promote products derived from this
015: * software without specific prior written permission.
016: *
017: * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
018: * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
019: * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
020: * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
021: * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
022: * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
023: * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
024: * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
025: * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
026: * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
027: *
028: * http://www.pdfbox.org
029: *
030: */package org.pdfbox.pdmodel.encryption;
031:
032: import java.io.ByteArrayInputStream;
033: import java.io.ByteArrayOutputStream;
034: import java.io.IOException;
035: import java.security.AlgorithmParameterGenerator;
036: import java.security.AlgorithmParameters;
037: import java.security.GeneralSecurityException;
038: import java.security.KeyStoreException;
039: import java.security.MessageDigest;
040: import java.security.NoSuchAlgorithmException;
041: import java.security.NoSuchProviderException;
042: import java.security.SecureRandom;
043: import java.security.Security;
044: import java.security.cert.X509Certificate;
045: import java.util.Iterator;
046:
047: import javax.crypto.Cipher;
048: import javax.crypto.KeyGenerator;
049: import javax.crypto.SecretKey;
050:
051: import org.bouncycastle.asn1.ASN1InputStream;
052: import org.bouncycastle.asn1.DERObject;
053: import org.bouncycastle.asn1.DERObjectIdentifier;
054: import org.bouncycastle.asn1.DEROctetString;
055: import org.bouncycastle.asn1.DEROutputStream;
056: import org.bouncycastle.asn1.DERSet;
057: import org.bouncycastle.asn1.cms.ContentInfo;
058: import org.bouncycastle.asn1.cms.EncryptedContentInfo;
059: import org.bouncycastle.asn1.cms.EnvelopedData;
060: import org.bouncycastle.asn1.cms.IssuerAndSerialNumber;
061: import org.bouncycastle.asn1.cms.KeyTransRecipientInfo;
062: import org.bouncycastle.asn1.cms.RecipientIdentifier;
063: import org.bouncycastle.asn1.cms.RecipientInfo;
064: import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
065: import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
066: import org.bouncycastle.asn1.x509.TBSCertificateStructure;
067: import org.bouncycastle.cms.CMSEnvelopedData;
068: import org.bouncycastle.cms.CMSException;
069: import org.bouncycastle.cms.RecipientInformation;
070: import org.bouncycastle.jce.provider.BouncyCastleProvider;
071: import org.pdfbox.cos.COSString;
072: import org.pdfbox.exceptions.CryptographyException;
073: import org.pdfbox.pdmodel.PDDocument;
074:
075: /**
076: * This class implements the public key security handler
077: * described in the PDF specification.
078: *
079: * @see PDF Spec 1.6 p104
080: *
081: * @see PublicKeyProtectionPolicy to see how to protect document with this security handler.
082: *
083: * @author Benoit Guillon (benoit.guillon@snv.jussieu.fr)
084: * @version $Revision: 1.3 $
085: */
086: public class PublicKeySecurityHandler extends SecurityHandler {
087:
088: /**
089: * The filter name.
090: */
091: public static final String FILTER = "Adobe.PubSec";
092:
093: private static final String SUBFILTER = "adbe.pkcs7.s4";
094:
095: private PublicKeyProtectionPolicy policy = null;
096:
097: /**
098: * Constructor.
099: */
100: public PublicKeySecurityHandler() {
101: }
102:
103: /**
104: * Constructor used for encryption.
105: *
106: * @param p The protection policy.
107: */
108: public PublicKeySecurityHandler(PublicKeyProtectionPolicy p) {
109: policy = p;
110: this .keyLength = policy.getEncryptionKeyLength();
111: }
112:
113: /**
114: * Decrypt the document.
115: *
116: * @param doc The document to decrypt.
117: * @param decryptionMaterial The data used to decrypt the document.
118: *
119: * @throws CryptographyException If there is an error during decryption.
120: * @throws IOException If there is an error accessing data.
121: */
122: public void decryptDocument(PDDocument doc,
123: DecryptionMaterial decryptionMaterial)
124: throws CryptographyException, IOException {
125: this .document = doc;
126:
127: PDEncryptionDictionary dictionary = doc
128: .getEncryptionDictionary();
129:
130: if (dictionary.getLength() != 0) {
131: this .keyLength = dictionary.getLength();
132: }
133:
134: if (!(decryptionMaterial instanceof PublicKeyDecryptionMaterial)) {
135: throw new CryptographyException(
136: "Provided decryption material is not compatible with the document");
137: }
138:
139: PublicKeyDecryptionMaterial material = (PublicKeyDecryptionMaterial) decryptionMaterial;
140:
141: try {
142: boolean foundRecipient = false;
143:
144: // the decrypted content of the enveloped data that match
145: // the certificate in the decryption material provided
146: byte[] envelopedData = null;
147:
148: // the bytes of each recipient in the recipients array
149: byte[][] recipientFieldsBytes = new byte[dictionary
150: .getRecipientsLength()][];
151:
152: int recipientFieldsLength = 0;
153:
154: for (int i = 0; i < dictionary.getRecipientsLength(); i++) {
155: COSString recipientFieldString = dictionary
156: .getRecipientStringAt(i);
157: byte[] recipientBytes = recipientFieldString.getBytes();
158: CMSEnvelopedData data = new CMSEnvelopedData(
159: recipientBytes);
160: Iterator recipCertificatesIt = data.getRecipientInfos()
161: .getRecipients().iterator();
162: while (recipCertificatesIt.hasNext()) {
163: RecipientInformation ri = (RecipientInformation) recipCertificatesIt
164: .next();
165: // Impl: if a matching certificate was previously found it is an error,
166: // here we just don't care about it
167: if (ri.getRID().match(material.getCertificate())
168: && !foundRecipient) {
169: foundRecipient = true;
170: envelopedData = ri.getContent(material
171: .getPrivateKey(), "BC");
172: }
173: }
174: recipientFieldsBytes[i] = recipientBytes;
175: recipientFieldsLength += recipientBytes.length;
176: }
177: if (!foundRecipient || envelopedData == null) {
178: throw new CryptographyException(
179: "The certificate matches no recipient entry");
180: }
181: if (envelopedData.length != 24) {
182: throw new CryptographyException(
183: "The enveloped data does not contain 24 bytes");
184: }
185: // now envelopedData contains:
186: // - the 20 bytes seed
187: // - the 4 bytes of permission for the current user
188:
189: byte[] accessBytes = new byte[4];
190: System.arraycopy(envelopedData, 20, accessBytes, 0, 4);
191:
192: currentAccessPermission = new AccessPermission(accessBytes);
193: currentAccessPermission.setReadOnly();
194:
195: // what we will put in the SHA1 = the seed + each byte contained in the recipients array
196: byte[] sha1Input = new byte[recipientFieldsLength + 20];
197:
198: // put the seed in the sha1 input
199: System.arraycopy(envelopedData, 0, sha1Input, 0, 20);
200:
201: // put each bytes of the recipients array in the sha1 input
202: int sha1InputOffset = 20;
203: for (int i = 0; i < recipientFieldsBytes.length; i++) {
204: System
205: .arraycopy(recipientFieldsBytes[i], 0,
206: sha1Input, sha1InputOffset,
207: recipientFieldsBytes[i].length);
208: sha1InputOffset += recipientFieldsBytes[i].length;
209: }
210:
211: MessageDigest md = MessageDigest.getInstance("SHA-1");
212: byte[] mdResult = md.digest(sha1Input);
213:
214: // we have the encryption key ...
215: encryptionKey = new byte[this .keyLength / 8];
216: System.arraycopy(mdResult, 0, encryptionKey, 0,
217: this .keyLength / 8);
218:
219: proceedDecryption();
220:
221: } catch (CMSException e) {
222: throw new CryptographyException(e);
223: } catch (KeyStoreException e) {
224: throw new CryptographyException(e);
225: } catch (NoSuchProviderException e) {
226: throw new CryptographyException(e);
227: } catch (NoSuchAlgorithmException e) {
228: throw new CryptographyException(e);
229: }
230:
231: }
232:
233: /**
234: * Prepare the document for encryption.
235: *
236: * @param doc The document that will be encrypted.
237: *
238: * @throws CryptographyException If there is an error while encrypting.
239: */
240: public void prepareDocumentForEncryption(PDDocument doc)
241: throws CryptographyException {
242:
243: try {
244: Security.addProvider(new BouncyCastleProvider());
245:
246: PDEncryptionDictionary dictionary = doc
247: .getEncryptionDictionary();
248:
249: dictionary.setFilter(FILTER);
250: dictionary.setLength(this .keyLength);
251: dictionary.setVersion(2);
252: dictionary.setSubFilter(SUBFILTER);
253:
254: byte[][] recipientsField = new byte[policy
255: .getRecipientsNumber()][];
256:
257: // create the 20 bytes seed
258:
259: byte[] seed = new byte[20];
260:
261: KeyGenerator key = KeyGenerator.getInstance("AES");
262: key.init(192, new SecureRandom());
263: SecretKey sk = key.generateKey();
264: System.arraycopy(sk.getEncoded(), 0, seed, 0, 20); // create the 20 bytes seed
265:
266: Iterator it = policy.getRecipientsIterator();
267: int i = 0;
268:
269: while (it.hasNext()) {
270: PublicKeyRecipient recipient = (PublicKeyRecipient) it
271: .next();
272: X509Certificate certificate = recipient.getX509();
273: int permission = recipient.getPermission()
274: .getPermissionBytesForPublicKey();
275:
276: byte[] pkcs7input = new byte[24];
277: byte one = (byte) (permission);
278: byte two = (byte) (permission >>> 8);
279: byte three = (byte) (permission >>> 16);
280: byte four = (byte) (permission >>> 24);
281:
282: System.arraycopy(seed, 0, pkcs7input, 0, 20); // put this seed in the pkcs7 input
283:
284: pkcs7input[20] = four;
285: pkcs7input[21] = three;
286: pkcs7input[22] = two;
287: pkcs7input[23] = one;
288:
289: DERObject obj = createDERForRecipient(pkcs7input,
290: certificate);
291:
292: ByteArrayOutputStream baos = new ByteArrayOutputStream();
293:
294: DEROutputStream k = new DEROutputStream(baos);
295:
296: k.writeObject(obj);
297:
298: recipientsField[i] = baos.toByteArray();
299:
300: i++;
301: }
302:
303: dictionary.setRecipients(recipientsField);
304:
305: int sha1InputLength = seed.length;
306:
307: for (int j = 0; j < dictionary.getRecipientsLength(); j++) {
308: COSString string = dictionary.getRecipientStringAt(j);
309: sha1InputLength += string.getBytes().length;
310: }
311:
312: byte[] sha1Input = new byte[sha1InputLength];
313:
314: System.arraycopy(seed, 0, sha1Input, 0, 20);
315:
316: int sha1InputOffset = 20;
317:
318: for (int j = 0; j < dictionary.getRecipientsLength(); j++) {
319: COSString string = dictionary.getRecipientStringAt(j);
320: System.arraycopy(string.getBytes(), 0, sha1Input,
321: sha1InputOffset, string.getBytes().length);
322: sha1InputOffset += string.getBytes().length;
323: }
324:
325: MessageDigest md = MessageDigest.getInstance("SHA-1");
326:
327: byte[] mdResult = md.digest(sha1Input);
328:
329: this .encryptionKey = new byte[this .keyLength / 8];
330: System.arraycopy(mdResult, 0, this .encryptionKey, 0,
331: this .keyLength / 8);
332:
333: doc.setEncryptionDictionary(dictionary);
334: doc.getDocument().setEncryptionDictionary(
335: dictionary.encryptionDictionary);
336:
337: } catch (NoSuchAlgorithmException ex) {
338: throw new CryptographyException(ex);
339: } catch (NoSuchProviderException ex) {
340: throw new CryptographyException(ex);
341: } catch (Exception e) {
342: e.printStackTrace();
343: throw new CryptographyException(e);
344: }
345:
346: }
347:
348: private DERObject createDERForRecipient(byte[] in,
349: X509Certificate cert) throws IOException,
350: GeneralSecurityException {
351:
352: String s = "1.2.840.113549.3.2";
353:
354: AlgorithmParameterGenerator algorithmparametergenerator = AlgorithmParameterGenerator
355: .getInstance(s);
356: AlgorithmParameters algorithmparameters = algorithmparametergenerator
357: .generateParameters();
358: ByteArrayInputStream bytearrayinputstream = new ByteArrayInputStream(
359: algorithmparameters.getEncoded("ASN.1"));
360: ASN1InputStream asn1inputstream = new ASN1InputStream(
361: bytearrayinputstream);
362: DERObject derobject = asn1inputstream.readObject();
363: KeyGenerator keygenerator = KeyGenerator.getInstance(s);
364: keygenerator.init(128);
365: SecretKey secretkey = keygenerator.generateKey();
366: Cipher cipher = Cipher.getInstance(s);
367: cipher.init(1, secretkey, algorithmparameters);
368: byte[] abyte1 = cipher.doFinal(in);
369: DEROctetString deroctetstring = new DEROctetString(abyte1);
370: KeyTransRecipientInfo keytransrecipientinfo = computeRecipientInfo(
371: cert, secretkey.getEncoded());
372: DERSet derset = new DERSet(new RecipientInfo(
373: keytransrecipientinfo));
374: AlgorithmIdentifier algorithmidentifier = new AlgorithmIdentifier(
375: new DERObjectIdentifier(s), derobject);
376: EncryptedContentInfo encryptedcontentinfo = new EncryptedContentInfo(
377: PKCSObjectIdentifiers.data, algorithmidentifier,
378: deroctetstring);
379: EnvelopedData env = new EnvelopedData(null, derset,
380: encryptedcontentinfo, null);
381: ContentInfo contentinfo = new ContentInfo(
382: PKCSObjectIdentifiers.envelopedData, env);
383: return contentinfo.getDERObject();
384: }
385:
386: private KeyTransRecipientInfo computeRecipientInfo(
387: X509Certificate x509certificate, byte[] abyte0)
388: throws GeneralSecurityException, IOException {
389: ASN1InputStream asn1inputstream = new ASN1InputStream(
390: new ByteArrayInputStream(x509certificate
391: .getTBSCertificate()));
392: TBSCertificateStructure tbscertificatestructure = TBSCertificateStructure
393: .getInstance(asn1inputstream.readObject());
394: AlgorithmIdentifier algorithmidentifier = tbscertificatestructure
395: .getSubjectPublicKeyInfo().getAlgorithmId();
396: IssuerAndSerialNumber issuerandserialnumber = new IssuerAndSerialNumber(
397: tbscertificatestructure.getIssuer(),
398: tbscertificatestructure.getSerialNumber().getValue());
399: Cipher cipher = Cipher.getInstance(algorithmidentifier
400: .getObjectId().getId());
401: cipher.init(1, x509certificate.getPublicKey());
402: DEROctetString deroctetstring = new DEROctetString(cipher
403: .doFinal(abyte0));
404: RecipientIdentifier recipId = new RecipientIdentifier(
405: issuerandserialnumber);
406: return new KeyTransRecipientInfo(recipId, algorithmidentifier,
407: deroctetstring);
408: }
409:
410: }
|