| //======================================================================== |
| // |
| // 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 Albert Astals Cid <aacid@kde.org> |
| // Copyright (C) 2013 Adrian Johnson <ajohnson@redneon.com> |
| // Copyright (C) 2014 Fabio D'Urso <fabiodurso@hotmail.it> |
| // |
| // 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> |
| |
| #ifdef USE_GCC_PRAGMAS |
| #pragma implementation |
| #endif |
| |
| #include "GooString.h" |
| #include "PDFDoc.h" |
| #include "Decrypt.h" |
| #include "Error.h" |
| #include "GlobalParams.h" |
| #ifdef ENABLE_PLUGINS |
| # include "XpdfPluginAPI.h" |
| #endif |
| #include "SecurityHandler.h" |
| |
| #include <limits.h> |
| |
| //------------------------------------------------------------------------ |
| // SecurityHandler |
| //------------------------------------------------------------------------ |
| |
| SecurityHandler *SecurityHandler::make(PDFDoc *docA, Object *encryptDictA) { |
| Object filterObj; |
| SecurityHandler *secHdlr; |
| #ifdef ENABLE_PLUGINS |
| XpdfSecurityHandler *xsh; |
| #endif |
| |
| encryptDictA->dictLookup("Filter", &filterObj); |
| if (filterObj.isName("Standard")) { |
| secHdlr = new StandardSecurityHandler(docA, encryptDictA); |
| } else if (filterObj.isName()) { |
| #ifdef ENABLE_PLUGINS |
| if ((xsh = globalParams->getSecurityHandler(filterObj.getName()))) { |
| secHdlr = new ExternalSecurityHandler(docA, encryptDictA, xsh); |
| } else { |
| #endif |
| error(errSyntaxError, -1, "Couldn't find the '{0:s}' security handler", |
| filterObj.getName()); |
| secHdlr = NULL; |
| #ifdef ENABLE_PLUGINS |
| } |
| #endif |
| } else { |
| error(errSyntaxError, -1, |
| "Missing or invalid 'Filter' entry in encryption dictionary"); |
| secHdlr = NULL; |
| } |
| filterObj.free(); |
| return secHdlr; |
| } |
| |
| SecurityHandler::SecurityHandler(PDFDoc *docA) { |
| doc = docA; |
| } |
| |
| SecurityHandler::~SecurityHandler() { |
| } |
| |
| GBool SecurityHandler::checkEncryption(GooString *ownerPassword, |
| GooString *userPassword) { |
| void *authData; |
| GBool ok; |
| int i; |
| |
| if (ownerPassword || userPassword) { |
| authData = makeAuthData(ownerPassword, userPassword); |
| } else { |
| authData = NULL; |
| } |
| 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; |
| } |
| } |
| |
| GooString *ownerPassword; |
| GooString *userPassword; |
| }; |
| |
| StandardSecurityHandler::StandardSecurityHandler(PDFDoc *docA, |
| Object *encryptDictA): |
| SecurityHandler(docA) |
| { |
| Object versionObj, revisionObj, lengthObj; |
| Object ownerKeyObj, userKeyObj, ownerEncObj, userEncObj; |
| Object permObj, fileIDObj, fileIDObj1; |
| Object cryptFiltersObj, streamFilterObj, stringFilterObj; |
| Object cryptFilterObj, cfmObj, cfLengthObj; |
| Object encryptMetadataObj; |
| |
| ok = gFalse; |
| fileID = NULL; |
| ownerKey = NULL; |
| userKey = NULL; |
| ownerEnc = NULL; |
| userEnc = NULL; |
| fileKeyLength = 0; |
| |
| encryptDictA->dictLookup("V", &versionObj); |
| encryptDictA->dictLookup("R", &revisionObj); |
| encryptDictA->dictLookup("Length", &lengthObj); |
| encryptDictA->dictLookup("O", &ownerKeyObj); |
| encryptDictA->dictLookup("U", &userKeyObj); |
| encryptDictA->dictLookup("OE", &ownerEncObj); |
| encryptDictA->dictLookup("UE", &userEncObj); |
| encryptDictA->dictLookup("P", &permObj); |
| if (permObj.isInt64()) { |
| unsigned int permUint = permObj.getInt64(); |
| int perms = permUint - UINT_MAX - 1; |
| permObj.free(); |
| permObj.initInt(perms); |
| } |
| doc->getXRef()->getTrailerDict()->dictLookup("ID", &fileIDObj); |
| 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 && |
| // 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 = gTrue; |
| //~ 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)) { |
| encryptDictA->dictLookup("CF", &cryptFiltersObj); |
| encryptDictA->dictLookup("StmF", &streamFilterObj); |
| encryptDictA->dictLookup("StrF", &stringFilterObj); |
| 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 { |
| if (cryptFiltersObj.dictLookup(streamFilterObj.getName(), |
| &cryptFilterObj)->isDict()) { |
| cryptFilterObj.dictLookup("CFM", &cfmObj); |
| if (cfmObj.isName("V2")) { |
| encVersion = 2; |
| encRevision = 3; |
| if (cryptFilterObj.dictLookup("Length", |
| &cfLengthObj)->isInt()) { |
| //~ according to the spec, this should be cfLengthObj / 8 |
| fileKeyLength = cfLengthObj.getInt(); |
| } |
| cfLengthObj.free(); |
| } else if (cfmObj.isName("AESV2")) { |
| encVersion = 2; |
| encRevision = 3; |
| encAlgorithm = cryptAES; |
| if (cryptFilterObj.dictLookup("Length", |
| &cfLengthObj)->isInt()) { |
| //~ according to the spec, this should be cfLengthObj / 8 |
| fileKeyLength = cfLengthObj.getInt(); |
| } |
| cfLengthObj.free(); |
| } else if (cfmObj.isName("AESV3")) { |
| encVersion = 5; |
| encRevision = 5; |
| encAlgorithm = cryptAES256; |
| if (cryptFilterObj.dictLookup("Length", |
| &cfLengthObj)->isInt()) { |
| //~ according to the spec, this should be cfLengthObj / 8 |
| fileKeyLength = cfLengthObj.getInt(); |
| } |
| cfLengthObj.free(); |
| } |
| cfmObj.free(); |
| } |
| cryptFilterObj.free(); |
| } |
| } |
| stringFilterObj.free(); |
| streamFilterObj.free(); |
| cryptFiltersObj.free(); |
| if (encryptDictA->dictLookup("EncryptMetadata", |
| &encryptMetadataObj)->isBool()) { |
| encryptMetadata = encryptMetadataObj.getBool(); |
| } |
| encryptMetadataObj.free(); |
| } |
| permFlags = permObj.getInt(); |
| ownerKey = ownerKeyObj.getString()->copy(); |
| userKey = userKeyObj.getString()->copy(); |
| if (encVersion >= 1 && encVersion <= 2 && |
| encRevision >= 2 && encRevision <= 3) { |
| if (fileIDObj.isArray()) { |
| if (fileIDObj.arrayGet(0, &fileIDObj1)->isString()) { |
| fileID = fileIDObj1.getString()->copy(); |
| } else { |
| fileID = new GooString(); |
| } |
| fileIDObj1.free(); |
| } else { |
| fileID = new GooString(); |
| } |
| if (fileKeyLength > 16 || fileKeyLength < 0) { |
| fileKeyLength = 16; |
| } |
| ok = gTrue; |
| } else if (encVersion == 5 && encRevision == 5) { |
| fileID = new GooString(); // unused for V=R=5 |
| ownerEnc = ownerEncObj.getString()->copy(); |
| userEnc = userEncObj.getString()->copy(); |
| if (fileKeyLength > 32 || fileKeyLength < 0) { |
| fileKeyLength = 32; |
| } |
| ok = gTrue; |
| } 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"); |
| } |
| fileIDObj.free(); |
| permObj.free(); |
| userEncObj.free(); |
| ownerEncObj.free(); |
| userKeyObj.free(); |
| ownerKeyObj.free(); |
| lengthObj.free(); |
| revisionObj.free(); |
| versionObj.free(); |
| } |
| |
| StandardSecurityHandler::~StandardSecurityHandler() { |
| if (fileID) { |
| delete fileID; |
| } |
| if (ownerKey) { |
| delete ownerKey; |
| } |
| if (userKey) { |
| delete userKey; |
| } |
| if (ownerEnc) { |
| delete ownerEnc; |
| } |
| if (userEnc) { |
| delete userEnc; |
| } |
| } |
| |
| GBool StandardSecurityHandler::isUnencrypted() { |
| return encVersion == -1 && encRevision == -1; |
| } |
| |
| void *StandardSecurityHandler::makeAuthData(GooString *ownerPassword, |
| GooString *userPassword) { |
| return new StandardAuthData(ownerPassword ? ownerPassword->copy() |
| : (GooString *)NULL, |
| userPassword ? userPassword->copy() |
| : (GooString *)NULL); |
| } |
| |
| void *StandardSecurityHandler::getAuthData() { |
| return NULL; |
| } |
| |
| void StandardSecurityHandler::freeAuthData(void *authData) { |
| delete (StandardAuthData *)authData; |
| } |
| |
| GBool StandardSecurityHandler::authorize(void *authData) { |
| GooString *ownerPassword, *userPassword; |
| |
| if (!ok) { |
| return gFalse; |
| } |
| if (authData) { |
| ownerPassword = ((StandardAuthData *)authData)->ownerPassword; |
| userPassword = ((StandardAuthData *)authData)->userPassword; |
| } else { |
| ownerPassword = NULL; |
| userPassword = NULL; |
| } |
| if (!Decrypt::makeFileKey(encVersion, encRevision, fileKeyLength, |
| ownerKey, userKey, ownerEnc, userEnc, |
| permFlags, fileID, |
| ownerPassword, userPassword, fileKey, |
| encryptMetadata, &ownerPasswordOk)) { |
| return gFalse; |
| } |
| return gTrue; |
| } |
| |
| #ifdef ENABLE_PLUGINS |
| |
| //------------------------------------------------------------------------ |
| // ExternalSecurityHandler |
| //------------------------------------------------------------------------ |
| |
| ExternalSecurityHandler::ExternalSecurityHandler(PDFDoc *docA, |
| Object *encryptDictA, |
| XpdfSecurityHandler *xshA): |
| SecurityHandler(docA) |
| { |
| encryptDictA->copy(&encryptDict); |
| xsh = xshA; |
| encAlgorithm = cryptRC4; //~ this should be obtained via getKey |
| ok = gFalse; |
| |
| if (!(*xsh->newDoc)(xsh->handlerData, (XpdfDoc)docA, |
| (XpdfObject)encryptDictA, &docData)) { |
| return; |
| } |
| |
| ok = gTrue; |
| } |
| |
| ExternalSecurityHandler::~ExternalSecurityHandler() { |
| (*xsh->freeDoc)(xsh->handlerData, docData); |
| encryptDict.free(); |
| } |
| |
| void *ExternalSecurityHandler::makeAuthData(GooString *ownerPassword, |
| GooString *userPassword) { |
| char *opw, *upw; |
| void *authData; |
| |
| opw = ownerPassword ? ownerPassword->getCString() : (char *)NULL; |
| upw = userPassword ? userPassword->getCString() : (char *)NULL; |
| if (!(*xsh->makeAuthData)(xsh->handlerData, docData, opw, upw, &authData)) { |
| return NULL; |
| } |
| return authData; |
| } |
| |
| void *ExternalSecurityHandler::getAuthData() { |
| void *authData; |
| |
| if (!(*xsh->getAuthData)(xsh->handlerData, docData, &authData)) { |
| return NULL; |
| } |
| return authData; |
| } |
| |
| void ExternalSecurityHandler::freeAuthData(void *authData) { |
| (*xsh->freeAuthData)(xsh->handlerData, docData, authData); |
| } |
| |
| GBool ExternalSecurityHandler::authorize(void *authData) { |
| char *key; |
| int length; |
| |
| if (!ok) { |
| return gFalse; |
| } |
| permFlags = (*xsh->authorize)(xsh->handlerData, docData, authData); |
| if (!(permFlags & xpdfPermissionOpen)) { |
| return gFalse; |
| } |
| if (!(*xsh->getKey)(xsh->handlerData, docData, &key, &length, &encVersion, &encRevision)) { |
| return gFalse; |
| } |
| if ((fileKeyLength = length) > 16) { |
| fileKeyLength = 16; |
| } |
| memcpy(fileKey, key, fileKeyLength); |
| (*xsh->freeKey)(xsh->handlerData, docData, key, length); |
| return gTrue; |
| } |
| |
| #endif // ENABLE_PLUGINS |