| //======================================================================== |
| // |
| // SecurityHandler.cc |
| // |
| // Copyright 2004 Glyph & Cog, LLC |
| // |
| //======================================================================== |
| |
| //======================================================================== |
| // |
| // Modified under the Poppler project - http://poppler.freedesktop.org |
| // |
| // All changes made under the Poppler project to this file are licensed |
| // under GPL version 2 or later |
| // |
| // Copyright (C) 2010, 2012, 2015, 2017, 2018 Albert Astals Cid <aacid@kde.org> |
| // Copyright (C) 2013 Adrian Johnson <ajohnson@redneon.com> |
| // Copyright (C) 2014 Fabio D'Urso <fabiodurso@hotmail.it> |
| // Copyright (C) 2016 Alok Anand <alok4nand@gmail.com> |
| // |
| // To see a description of the changes please see the Changelog file that |
| // came with your tarball or type make ChangeLog if you are building from git |
| // |
| //======================================================================== |
| |
| #include <config.h> |
| |
| #include "GooString.h" |
| #include "PDFDoc.h" |
| #include "Decrypt.h" |
| #include "Error.h" |
| #include "GlobalParams.h" |
| #include "SecurityHandler.h" |
| |
| #include <limits.h> |
| |
| //------------------------------------------------------------------------ |
| // SecurityHandler |
| //------------------------------------------------------------------------ |
| |
| SecurityHandler *SecurityHandler::make(PDFDoc *docA, Object *encryptDictA) { |
| SecurityHandler *secHdlr; |
| |
| Object filterObj = encryptDictA->dictLookup("Filter"); |
| if (filterObj.isName("Standard")) { |
| secHdlr = new StandardSecurityHandler(docA, encryptDictA); |
| } else if (filterObj.isName()) { |
| error(errSyntaxError, -1, "Couldn't find the '{0:s}' security handler", filterObj.getName()); |
| secHdlr = nullptr; |
| } else { |
| error(errSyntaxError, -1, |
| "Missing or invalid 'Filter' entry in encryption dictionary"); |
| secHdlr = nullptr; |
| } |
| return secHdlr; |
| } |
| |
| SecurityHandler::SecurityHandler(PDFDoc *docA) { |
| doc = docA; |
| } |
| |
| SecurityHandler::~SecurityHandler() { |
| } |
| |
| bool SecurityHandler::checkEncryption(const GooString *ownerPassword, |
| const GooString *userPassword) { |
| void *authData; |
| bool ok; |
| int i; |
| |
| if (ownerPassword || userPassword) { |
| authData = makeAuthData(ownerPassword, userPassword); |
| } else { |
| authData = nullptr; |
| } |
| ok = authorize(authData); |
| if (authData) { |
| freeAuthData(authData); |
| } |
| for (i = 0; !ok && i < 3; ++i) { |
| if (!(authData = getAuthData())) { |
| break; |
| } |
| ok = authorize(authData); |
| if (authData) { |
| freeAuthData(authData); |
| } |
| } |
| if (!ok) { |
| if (!ownerPassword && !userPassword) { |
| GooString dummy; |
| return checkEncryption(&dummy, &dummy); |
| } else { |
| error(errCommandLine, -1, "Incorrect password"); |
| } |
| } |
| return ok; |
| } |
| |
| //------------------------------------------------------------------------ |
| // StandardSecurityHandler |
| //------------------------------------------------------------------------ |
| |
| class StandardAuthData { |
| public: |
| |
| StandardAuthData(GooString *ownerPasswordA, GooString *userPasswordA) { |
| ownerPassword = ownerPasswordA; |
| userPassword = userPasswordA; |
| } |
| |
| ~StandardAuthData() { |
| if (ownerPassword) { |
| delete ownerPassword; |
| } |
| if (userPassword) { |
| delete userPassword; |
| } |
| } |
| |
| StandardAuthData(const StandardAuthData &) = delete; |
| StandardAuthData& operator=(const StandardAuthData &) = delete; |
| |
| GooString *ownerPassword; |
| GooString *userPassword; |
| }; |
| |
| StandardSecurityHandler::StandardSecurityHandler(PDFDoc *docA, |
| Object *encryptDictA): |
| SecurityHandler(docA) |
| { |
| ok = false; |
| fileID = nullptr; |
| ownerKey = nullptr; |
| userKey = nullptr; |
| ownerEnc = nullptr; |
| userEnc = nullptr; |
| fileKeyLength = 0; |
| |
| Object versionObj = encryptDictA->dictLookup("V"); |
| Object revisionObj = encryptDictA->dictLookup("R"); |
| Object lengthObj = encryptDictA->dictLookup("Length"); |
| Object ownerKeyObj = encryptDictA->dictLookup("O"); |
| Object userKeyObj = encryptDictA->dictLookup("U"); |
| Object ownerEncObj = encryptDictA->dictLookup("OE"); |
| Object userEncObj = encryptDictA->dictLookup("UE"); |
| Object permObj = encryptDictA->dictLookup("P"); |
| if (permObj.isInt64()) { |
| unsigned int permUint = permObj.getInt64(); |
| int perms = permUint - UINT_MAX - 1; |
| permObj = Object(perms); |
| } |
| Object fileIDObj = doc->getXRef()->getTrailerDict()->dictLookup("ID"); |
| if (versionObj.isInt() && |
| revisionObj.isInt() && |
| permObj.isInt() && |
| ownerKeyObj.isString() && |
| userKeyObj.isString()) { |
| encVersion = versionObj.getInt(); |
| encRevision = revisionObj.getInt(); |
| if ((encRevision <= 4 && |
| ownerKeyObj.getString()->getLength() == 32 && |
| userKeyObj.getString()->getLength() == 32) || |
| ((encRevision == 5 || encRevision == 6) && |
| // the spec says 48 bytes, but Acrobat pads them out longer |
| ownerKeyObj.getString()->getLength() >= 48 && |
| userKeyObj.getString()->getLength() >= 48 && |
| ownerEncObj.isString() && |
| ownerEncObj.getString()->getLength() == 32 && |
| userEncObj.isString() && |
| userEncObj.getString()->getLength() == 32)) { |
| encAlgorithm = cryptRC4; |
| // revision 2 forces a 40-bit key - some buggy PDF generators |
| // set the Length value incorrectly |
| if (encRevision == 2 || !lengthObj.isInt()) { |
| fileKeyLength = 5; |
| } else { |
| fileKeyLength = lengthObj.getInt() / 8; |
| } |
| encryptMetadata = true; |
| //~ this currently only handles a subset of crypt filter functionality |
| //~ (in particular, it ignores the EFF entry in encryptDictA, and |
| //~ doesn't handle the case where StmF, StrF, and EFF are not all the |
| //~ same) |
| if ((encVersion == 4 || encVersion == 5) && |
| (encRevision == 4 || encRevision == 5 || encRevision == 6)) { |
| Object cryptFiltersObj = encryptDictA->dictLookup("CF"); |
| Object streamFilterObj = encryptDictA->dictLookup("StmF"); |
| Object stringFilterObj = encryptDictA->dictLookup("StrF"); |
| if (cryptFiltersObj.isDict() && |
| streamFilterObj.isName() && |
| stringFilterObj.isName() && |
| !strcmp(streamFilterObj.getName(), stringFilterObj.getName())) { |
| if (!strcmp(streamFilterObj.getName(), "Identity")) { |
| // no encryption on streams or strings |
| encVersion = encRevision = -1; |
| } else { |
| Object cryptFilterObj = cryptFiltersObj.dictLookup(streamFilterObj.getName()); |
| if (cryptFilterObj.isDict()) { |
| Object cfmObj = cryptFilterObj.dictLookup("CFM"); |
| if (cfmObj.isName("V2")) { |
| encVersion = 2; |
| encRevision = 3; |
| Object cfLengthObj = cryptFilterObj.dictLookup("Length"); |
| if (cfLengthObj.isInt()) { |
| //~ according to the spec, this should be cfLengthObj / 8 |
| fileKeyLength = cfLengthObj.getInt(); |
| } |
| } else if (cfmObj.isName("AESV2")) { |
| encVersion = 2; |
| encRevision = 3; |
| encAlgorithm = cryptAES; |
| Object cfLengthObj = cryptFilterObj.dictLookup("Length"); |
| if (cfLengthObj.isInt()) { |
| //~ according to the spec, this should be cfLengthObj / 8 |
| fileKeyLength = cfLengthObj.getInt(); |
| } |
| } else if (cfmObj.isName("AESV3")) { |
| encVersion = 5; |
| // let encRevision be 5 or 6 |
| encAlgorithm = cryptAES256; |
| Object cfLengthObj = cryptFilterObj.dictLookup("Length"); |
| if (cfLengthObj.isInt()) { |
| //~ according to the spec, this should be cfLengthObj / 8 |
| fileKeyLength = cfLengthObj.getInt(); |
| } |
| } |
| } |
| } |
| } |
| Object encryptMetadataObj = encryptDictA->dictLookup("EncryptMetadata"); |
| if (encryptMetadataObj.isBool()) { |
| encryptMetadata = encryptMetadataObj.getBool(); |
| } |
| } |
| permFlags = permObj.getInt(); |
| ownerKey = ownerKeyObj.getString()->copy(); |
| userKey = userKeyObj.getString()->copy(); |
| if (encVersion >= 1 && encVersion <= 2 && |
| encRevision >= 2 && encRevision <= 3) { |
| if (fileIDObj.isArray()) { |
| Object fileIDObj1 = fileIDObj.arrayGet(0); |
| if (fileIDObj1.isString()) { |
| fileID = fileIDObj1.getString()->copy(); |
| } else { |
| fileID = new GooString(); |
| } |
| } else { |
| fileID = new GooString(); |
| } |
| if (fileKeyLength > 16 || fileKeyLength < 0) { |
| fileKeyLength = 16; |
| } |
| ok = true; |
| } else if (encVersion == 5 && (encRevision == 5 || encRevision == 6)) { |
| fileID = new GooString(); // unused for V=R=5 |
| if (ownerEncObj.isString() && userEncObj.isString()) { |
| ownerEnc = ownerEncObj.getString()->copy(); |
| userEnc = userEncObj.getString()->copy(); |
| if (fileKeyLength > 32 || fileKeyLength < 0) { |
| fileKeyLength = 32; |
| } |
| ok = true; |
| } else { |
| error(errSyntaxError, -1, "Weird encryption owner/user info"); |
| } |
| } else if (!(encVersion == -1 && encRevision == -1)) { |
| error(errUnimplemented, -1, |
| "Unsupported version/revision ({0:d}/{1:d}) of Standard security handler", |
| encVersion, encRevision); |
| } |
| } else { |
| error(errSyntaxError, -1, "Invalid encryption key length"); |
| } |
| } else { |
| error(errSyntaxError, -1, "Weird encryption info"); |
| } |
| } |
| |
| StandardSecurityHandler::~StandardSecurityHandler() { |
| if (fileID) { |
| delete fileID; |
| } |
| if (ownerKey) { |
| delete ownerKey; |
| } |
| if (userKey) { |
| delete userKey; |
| } |
| if (ownerEnc) { |
| delete ownerEnc; |
| } |
| if (userEnc) { |
| delete userEnc; |
| } |
| } |
| |
| bool StandardSecurityHandler::isUnencrypted() { |
| if (!ok) { |
| return true; |
| } |
| return encVersion == -1 && encRevision == -1; |
| } |
| |
| void *StandardSecurityHandler::makeAuthData(const GooString *ownerPassword, |
| const GooString *userPassword) { |
| return new StandardAuthData(ownerPassword ? ownerPassword->copy() |
| : nullptr, |
| userPassword ? userPassword->copy() |
| : nullptr); |
| } |
| |
| void *StandardSecurityHandler::getAuthData() { |
| return nullptr; |
| } |
| |
| void StandardSecurityHandler::freeAuthData(void *authData) { |
| delete (StandardAuthData *)authData; |
| } |
| |
| bool StandardSecurityHandler::authorize(void *authData) { |
| GooString *ownerPassword, *userPassword; |
| |
| if (!ok) { |
| return false; |
| } |
| if (authData) { |
| ownerPassword = ((StandardAuthData *)authData)->ownerPassword; |
| userPassword = ((StandardAuthData *)authData)->userPassword; |
| } else { |
| ownerPassword = nullptr; |
| userPassword = nullptr; |
| } |
| if (!Decrypt::makeFileKey(encVersion, encRevision, fileKeyLength, |
| ownerKey, userKey, ownerEnc, userEnc, |
| permFlags, fileID, |
| ownerPassword, userPassword, fileKey, |
| encryptMetadata, &ownerPasswordOk)) { |
| return false; |
| } |
| return true; |
| } |
| |