/** * Prepares everything to decrypt the document. * * @param encryption encryption dictionary, can be retrieved via * {@link Document#getEncryption()} * @param documentIDArray document id which is returned via * {@link org.apache.pdfbox.cos.COSDocument#getDocumentID()} (not used by * this handler) * @param decryptionMaterial Information used to decrypt the document. * * @throws IOException If there is an error accessing data. If verbose mode * is enabled, the exception message will provide more details why the * match wasn't successful. */ public override void PrepareForDecryption(PdfEncryption encryption, PdfArray documentIDArray, DecryptionMaterial decryptionMaterial) { if (!(decryptionMaterial is PublicKeyDecryptionMaterial)) { throw new IOException( "Provided decryption material is not compatible with the document"); } SetDecryptMetadata(encryption.IsEncryptMetaData); if (encryption.Length != 0) { this.keyLength = encryption.Length; } PublicKeyDecryptionMaterial material = (PublicKeyDecryptionMaterial)decryptionMaterial; try { bool foundRecipient = false; //Org.BouncyCastle.X509.Extension. X509Certificate certificate = material.Certificate; X509CertificateEntry materialCert = null; if (certificate != null) { materialCert = new X509CertificateEntry(certificate); } // the decrypted content of the enveloped data that match // the certificate in the decryption material provided byte[] envelopedData = null; // the bytes of each recipient in the recipients array PdfArray array = (PdfArray)encryption.BaseDataObject.Resolve(PdfName.Recipients); if (array == null) { PdfCryptFilterDictionary defaultCryptFilterDictionary = encryption.DefaultCryptFilterDictionary; array = (PdfArray)defaultCryptFilterDictionary.BaseDataObject.Resolve(PdfName.Recipients); } byte[][] recipientFieldsBytes = new byte[array.Count][]; //TODO encryption.getRecipientsLength() and getRecipientStringAt() should be deprecated int recipientFieldsLength = 0; StringBuilder extraInfo = new StringBuilder(); for (int i = 0; i < array.Count; i++) { PdfString recipientFieldString = (PdfString)array.Resolve(i); byte[] recipientBytes = recipientFieldString.GetBuffer(); CmsEnvelopedData data = new CmsEnvelopedData(recipientBytes); var recipCertificatesIt = data.GetRecipientInfos().GetRecipients(); int j = 0; foreach (RecipientInformation ri in recipCertificatesIt) { // Impl: if a matching certificate was previously found it is an error, // here we just don't care about it RecipientID rid = ri.RecipientID; if (!foundRecipient && rid.Match(materialCert)) { foundRecipient = true; var privateKey = material.PrivateKey; // might need to call setContentProvider() if we use PKI token, see // http://bouncy-castle.1462172.n4.nabble.com/CMSException-exception-unwrapping-key-key-invalid-unknown-key-type-passed-to-RSA-td4658109.html //DotNetUtilities.GetKeyPair(ri.AlgorithmIdentifier) envelopedData = ri.GetContent(privateKey.Key); break; } j++; if (certificate != null) { extraInfo.Append('\n'); extraInfo.Append(j); extraInfo.Append(": "); if (ri is KeyTransRecipientInformation) { appendCertInfo(extraInfo, (KeyTransRecipientInformation)ri, certificate, materialCert); } } } recipientFieldsBytes[i] = recipientBytes; recipientFieldsLength += recipientBytes.Length; } if (!foundRecipient || envelopedData == null) { throw new IOException("The certificate matches none of " + array.Count + " recipient entries" + extraInfo.ToString()); } if (envelopedData.Length != 24) { throw new IOException("The enveloped data does not contain 24 bytes"); } // now envelopedData contains: // - the 20 bytes seed // - the 4 bytes of permission for the current user byte[] accessBytes = new byte[4]; Array.Copy(envelopedData, 20, accessBytes, 0, 4); AccessPermission currentAccessPermission = new AccessPermission(accessBytes); currentAccessPermission.IsReadOnly = true; CurrentAccessPermission = currentAccessPermission; // what we will put in the SHA1 = the seed + each byte contained in the recipients array byte[] sha1Input = new byte[recipientFieldsLength + 20]; // put the seed in the sha1 input Array.Copy(envelopedData, 0, sha1Input, 0, 20); // put each bytes of the recipients array in the sha1 input int sha1InputOffset = 20; foreach (byte[] recipientFieldsByte in recipientFieldsBytes) { Array.Copy(recipientFieldsByte, 0, sha1Input, sha1InputOffset, recipientFieldsByte.Length); sha1InputOffset += recipientFieldsByte.Length; } byte[] mdResult; if (encryption.Version == 4 || encryption.Version == 5) { mdResult = SHA256.Create().Digest(sha1Input); // detect whether AES encryption is used. This assumes that the encryption algo is // stored in the PDCryptFilterDictionary // However, crypt filters are used only when V is 4 or 5. PdfCryptFilterDictionary defaultCryptFilterDictionary = encryption.DefaultCryptFilterDictionary; if (defaultCryptFilterDictionary != null) { PdfName cryptFilterMethod = defaultCryptFilterDictionary.CryptFilterMethod; IsAES = PdfName.AESV2.Equals(cryptFilterMethod) || PdfName.AESV3.Equals(cryptFilterMethod); } } else { mdResult = SHA1.Create().Digest(sha1Input); } // we have the encryption key ... encryptionKey = new byte[this.keyLength / 8]; Array.Copy(mdResult, 0, encryptionKey, 0, this.keyLength / 8); } catch (Exception e) { throw new IOException("", e); } }
/** * Prepares everything to decrypt the document. * * Only if decryption of single objects is needed this should be called. * * @param encryption encryption dictionary * @param documentIDArray document id * @param decryptionMaterial Information used to decrypt the document. * * @throws InvalidPasswordException If the password is incorrect. * @ If there is an error accessing data. */ public override void PrepareForDecryption(PdfEncryption encryption, PdfArray documentIDArray, DecryptionMaterial decryptionMaterial) { if (!(decryptionMaterial is StandardDecryptionMaterial)) { throw new IOException("Decryption material is not compatible with the document"); } // This is only used with security version 4 and 5. if (encryption.Version >= 4) { SetStreamFilterName(encryption.StreamFilterName); SetStringFilterName(encryption.StreamFilterName); } SetDecryptMetadata(encryption.IsEncryptMetaData); StandardDecryptionMaterial material = (StandardDecryptionMaterial)decryptionMaterial; string password = material.Password ?? string.Empty; int dicPermissions = encryption.Permissions; int dicRevision = encryption.Revision; int dicLength = encryption.Version == 1 ? 5 : encryption.Length / 8; byte[] documentIDBytes = GetDocumentIDBytes(documentIDArray); // we need to know whether the meta data was encrypted for password calculation bool encryptMetadata = encryption.IsEncryptMetaData; byte[] userKey = encryption.UserKey; byte[] ownerKey = encryption.OwnerKey; byte[] ue = null, oe = null; var passwordCharset = Charset.ISO88591; if (dicRevision == 6 || dicRevision == 5) { passwordCharset = Charset.UTF8; ue = encryption.UserEncryptionKey; oe = encryption.OwnerEncryptionKey; } if (dicRevision == 6) { password = SaslPrep.SaslPrepQuery(password); // PDFBOX-4155 } AccessPermission currentAccessPermission; if (IsOwnerPassword(passwordCharset.GetBytes(password), userKey, ownerKey, dicPermissions, documentIDBytes, dicRevision, dicLength, encryptMetadata)) { currentAccessPermission = AccessPermission.getOwnerAccessPermission(); CurrentAccessPermission = currentAccessPermission; byte[] computedPassword; if (dicRevision == 6 || dicRevision == 5) { computedPassword = passwordCharset.GetBytes(password); } else { computedPassword = GetUserPassword(passwordCharset.GetBytes(password), ownerKey, dicRevision, dicLength); } encryptionKey = ComputeEncryptedKey( computedPassword, ownerKey, userKey, oe, ue, dicPermissions, documentIDBytes, dicRevision, dicLength, encryptMetadata, true); } else if (IsUserPassword(passwordCharset.GetBytes(password), userKey, ownerKey, dicPermissions, documentIDBytes, dicRevision, dicLength, encryptMetadata)) { currentAccessPermission = new AccessPermission(dicPermissions); currentAccessPermission.IsReadOnly = true; CurrentAccessPermission = currentAccessPermission; encryptionKey = ComputeEncryptedKey( passwordCharset.GetBytes(password), ownerKey, userKey, oe, ue, dicPermissions, documentIDBytes, dicRevision, dicLength, encryptMetadata, false); } else { throw new InvalidPasswordException("Cannot decrypt PDF, the password is incorrect"); } if (dicRevision == 6 || dicRevision == 5) { ValidatePerms(encryption, dicPermissions, encryptMetadata); } if (encryption.Version == 4 || encryption.Version == 5) { // detect whether AES encryption is used. This assumes that the encryption algo is // stored in the PDCryptFilterDictionary // However, crypt filters are used only when V is 4 or 5. var stdCryptFilterDictionary = encryption.StdCryptFilterDictionary; if (stdCryptFilterDictionary != null) { PdfName cryptFilterMethod = stdCryptFilterDictionary.CryptFilterMethod; IsAES = PdfName.AESV2.Equals(cryptFilterMethod) || PdfName.AESV3.Equals(cryptFilterMethod); } } }