From f0ff97f7d7d24e91c90b64e20a2364ffad10fcdd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9gory=20Soutad=C3=A9?= Date: Sat, 18 Dec 2021 17:40:24 +0100 Subject: [PATCH] Add support of DRM removal for PDF --- include/libgourou.h | 17 ++- include/libgourou_common.h | 7 +- src/libgourou.cpp | 251 ++++++++++++++++++++++++++++++++----- 3 files changed, 239 insertions(+), 36 deletions(-) diff --git a/include/libgourou.h b/include/libgourou.h index 09b6fa4..5368bc8 100644 --- a/include/libgourou.h +++ b/include/libgourou.h @@ -164,6 +164,9 @@ namespace gourou */ std::string serializeRSAPrivateKey(void* rsa); + /** + * @brief Export clear private license key into path + */ void exportPrivateLicenseKey(std::string path); /** @@ -181,7 +184,11 @@ namespace gourou */ DRMProcessorClient* getClient() { return client; } - void removeDRM(const std::string& ePubFile, ITEM_TYPE type); + /** + * @brief Remove ADEPT DRM. + * Warning: for PDF format, filenameIn must be different than filenameOut + */ + void removeDRM(const std::string& filenameIn, const std::string& filenameOut, ITEM_TYPE type); private: gourou::DRMProcessorClient* client; @@ -206,7 +213,13 @@ namespace gourou void buildSignInRequest(pugi::xml_document& signInRequest, const std::string& adobeID, const std::string& adobePassword, const std::string& authenticationCertificate); void fetchLicenseServiceCertificate(const std::string& licenseURL, const std::string& operatorURL); - void removeEPubDRM(const std::string& ePubFile); + void decryptADEPTKey(const std::string& encryptedKey, unsigned char* decryptedKey); + void removeEPubDRM(const std::string& filenameIn, const std::string& filenameOut); + void generatePDFObjectKey(int version, + const unsigned char* masterKey, unsigned int masterKeyLength, + int objectId, int objectGenerationNumber, + unsigned char* keyOut); + void removePDFDRM(const std::string& filenameIn, const std::string& filenameOut); }; } diff --git a/include/libgourou_common.h b/include/libgourou_common.h index a5f01a3..41d60f4 100644 --- a/include/libgourou_common.h +++ b/include/libgourou_common.h @@ -113,8 +113,11 @@ namespace gourou }; enum DRM_REMOVAL_ERROR { - CLIENT_DRM_ERR_ENCRYPTION_KEY = 0x6000, - CLIENT_DRM_FORMAT_NOT_SUPPORTED, + DRM_ERR_ENCRYPTION_KEY = 0x6000, + DRM_VERSION_NOT_SUPPORTED, + DRM_FILE_ERROR, + DRM_FORMAT_NOT_SUPPORTED, + DRM_IN_OUT_EQUALS }; /** diff --git a/src/libgourou.cpp b/src/libgourou.cpp index 0cc1f13..2f5df7b 100644 --- a/src/libgourou.cpp +++ b/src/libgourou.cpp @@ -35,7 +35,6 @@ #define ASN_TEXT 0x04 #define ASN_ATTRIBUTE 0x05 - namespace gourou { GOUROU_LOG_LEVEL logLevel = WARN; @@ -851,10 +850,10 @@ namespace gourou // Generate IV in front client->randBytes(encrypted_data, 16); - client->AESEncrypt(CryptoInterface::CHAIN_CBC, - deviceKey, 16, encrypted_data, 16, - data, len, - encrypted_data+16, &outLen); + client->Encrypt(CryptoInterface::ALGO_AES, CryptoInterface::CHAIN_CBC, + deviceKey, 16, encrypted_data, 16, + data, len, + encrypted_data+16, &outLen); ByteArray res(encrypted_data, outLen+16); @@ -870,10 +869,10 @@ namespace gourou const unsigned char* deviceKey = device->getDeviceKey(); unsigned char* decrypted_data = new unsigned char[len-16]; - client->AESDecrypt(CryptoInterface::CHAIN_CBC, - deviceKey, 16, data, 16, - data+16, len-16, - decrypted_data, &outLen); + client->Decrypt(CryptoInterface::ALGO_AES, CryptoInterface::CHAIN_CBC, + deviceKey, 16, data, 16, + data+16, len-16, + decrypted_data, &outLen); ByteArray res(decrypted_data, outLen); @@ -926,18 +925,9 @@ namespace gourou int DRMProcessor::getLogLevel() {return (int)gourou::logLevel;} void DRMProcessor::setLogLevel(int logLevel) {gourou::logLevel = (GOUROU_LOG_LEVEL)logLevel;} - void DRMProcessor::removeEPubDRM(const std::string& ePubFile) + void DRMProcessor::decryptADEPTKey(const std::string& encryptedKey, unsigned char* decryptedKey) { - ByteArray zipData; - void* zipHandler = client->zipOpen(ePubFile); - - client->zipReadFile(zipHandler, "META-INF/rights.xml", zipData); - pugi::xml_document rightsDoc; - rightsDoc.load_string((const char*)zipData.data()); - - std::string encryptedKey = extractTextElem(rightsDoc, "/adept:rights/licenseToken/encryptedKey"); ByteArray arrayEncryptedKey = ByteArray::fromBase64(encryptedKey); - unsigned char decryptedKey[RSA_KEY_SIZE]; std::string privateKeyData = user->getPrivateLicenseKey(); ByteArray privateRSAKey = ByteArray::fromBase64(privateKeyData); @@ -951,8 +941,24 @@ namespace gourou if (decryptedKey[0] != 0x00 || decryptedKey[1] != 0x02 || decryptedKey[RSA_KEY_SIZE-16-1] != 0x00) - EXCEPTION(CLIENT_DRM_ERR_ENCRYPTION_KEY, "Unable to retrieve encryption key"); - + EXCEPTION(DRM_ERR_ENCRYPTION_KEY, "Unable to retrieve encryption key"); + } + + + void DRMProcessor::removeEPubDRM(const std::string& filenameIn, const std::string& filenameOut) + { + ByteArray zipData; + void* zipHandler = client->zipOpen(filenameOut); + + client->zipReadFile(zipHandler, "META-INF/rights.xml", zipData); + pugi::xml_document rightsDoc; + rightsDoc.load_string((const char*)zipData.data()); + + std::string encryptedKey = extractTextElem(rightsDoc, "/adept:rights/licenseToken/encryptedKey"); + unsigned char decryptedKey[RSA_KEY_SIZE]; + + decryptADEPTKey(encryptedKey, decryptedKey); + client->zipReadFile(zipHandler, "META-INF/encryption.xml", zipData); pugi::xml_document encryptionDoc; encryptionDoc.load_string((const char*)zipData.data()); @@ -969,18 +975,18 @@ namespace gourou client->zipReadFile(zipHandler, encryptedFile, zipData, false); unsigned char* _data = zipData.data(); - ByteArray clearData(zipData.length()-16+1); /* Reserve 1 byte for 'Z' */ + ByteArray clearData(zipData.length()-16+1, true); /* Reserve 1 byte for 'Z' */ unsigned char* _clearData = clearData.data(); gourou::ByteArray inflateData(true); unsigned int dataOutLength; - client->AESDecrypt(CryptoInterface::CHAIN_CBC, - decryptedKey+RSA_KEY_SIZE-16, 16, /* Key */ - _data, 16, /* IV */ - &_data[16], zipData.length()-16, - _clearData, &dataOutLength); + client->Decrypt(CryptoInterface::ALGO_AES, CryptoInterface::CHAIN_CBC, + decryptedKey+RSA_KEY_SIZE-16, 16, /* Key */ + _data, 16, /* IV */ + &_data[16], zipData.length()-16, + _clearData, &dataOutLength); - // Add 'Z' at the end, done in ineptepub.py + // Add 'Z' at the end, done in ineptepub.py _clearData[dataOutLength] = 'Z'; client->inflate(clearData, inflateData); @@ -994,11 +1000,192 @@ namespace gourou client->zipClose(zipHandler); } - void DRMProcessor::removeDRM(const std::string& ePubFile, ITEM_TYPE type) + void DRMProcessor::generatePDFObjectKey(int version, + const unsigned char* masterKey, unsigned int masterKeyLength, + int objectId, int objectGenerationNumber, + unsigned char* keyOut) { - if (type == EPUB) - removeEPubDRM(ePubFile); + switch(version) + { + case 4: + ByteArray toHash(masterKey, masterKeyLength); + uint32_t _objectId = objectId; + uint32_t _objectGenerationNumber = objectGenerationNumber; + toHash.append((const unsigned char*)&_objectId, 3); // Fill 3 bytes + toHash.append((const unsigned char*)&_objectGenerationNumber, 2); // Fill 2 bytes + + client->digest("md5", toHash.data(), toHash.length(), keyOut); + break; + } + } + + void DRMProcessor::removePDFDRM(const std::string& filenameIn, const std::string& filenameOut) + { + uPDFParser::Parser parser; + bool EBXHandlerFound = false; + + if (filenameIn == filenameOut) + { + EXCEPTION(DRM_IN_OUT_EQUALS, "PDF IN must be different of PDF OUT"); + } + + try + { + GOUROU_LOG(DEBUG, "Parse PDF"); + parser.parse(filenameIn); + } + catch(std::invalid_argument& e) + { + GOUROU_LOG(ERROR, "Invalid PDF"); + return; + } + + uPDFParser::Integer* ebxVersion; + std::vector objects = parser.objects(); + std::vector::reverse_iterator it; + unsigned char decryptedKey[RSA_KEY_SIZE]; + + for(it = objects.rbegin(); it != objects.rend(); it++) + { + // Update EBX_HANDLER with rights + if ((*it)->hasKey("Filter") && (**it)["Filter"]->str() == "/EBX_HANDLER") + { + EBXHandlerFound = true; + uPDFParser::Object* ebx = *it; + + ebxVersion = (uPDFParser::Integer*)(*ebx)["V"]; + if (ebxVersion->value() != 4) + { + EXCEPTION(DRM_VERSION_NOT_SUPPORTED, "EBX encryption version not supported " << ebxVersion->value()); + } + + if (!(ebx->hasKey("ADEPT_LICENSE"))) + { + EXCEPTION(DRM_ERR_ENCRYPTION_KEY, "No ADEPT_LICENSE found"); + } + + uPDFParser::String* licenseObject = (uPDFParser::String*)(*ebx)["ADEPT_LICENSE"]; + + ByteArray zippedData = ByteArray::fromBase64(licenseObject->value()); + ByteArray rightsStr; + client->inflate(zippedData, rightsStr); + + pugi::xml_document rightsDoc; + rightsDoc.load_string((const char*)rightsStr.data()); + + std::string encryptedKey = extractTextElem(rightsDoc, "/adept:rights/licenseToken/encryptedKey"); + + decryptADEPTKey(encryptedKey, decryptedKey); + break; + } + } + + if (!EBXHandlerFound) + { + EXCEPTION(DRM_ERR_ENCRYPTION_KEY, "EBX_HANDLER not found"); + } + + std::vector xrefTable = parser.xrefTable(); + std::vector::iterator xrefIt; + + for(xrefIt = xrefTable.begin(); xrefIt != xrefTable.end(); xrefIt++) + { + GOUROU_LOG(DEBUG, "XREF obj " << (*xrefIt).objectId() << " used " << (*xrefIt).used()); + + if (!(*xrefIt).used()) + continue; + + uPDFParser::Object* object = (*xrefIt).object(); + + if (!object) + { + GOUROU_LOG(DEBUG, "No object"); + continue; + } + + unsigned char tmpKey[16]; + + generatePDFObjectKey(ebxVersion->value(), + decryptedKey+RSA_KEY_SIZE-16, 16, + object->objectId(), object->generationNumber(), + tmpKey); + + uPDFParser::Dictionary& dictionary = object->dictionary(); + std::map& dictValues = dictionary.value(); + std::map::iterator dictIt; + std::map decodedStrings; + std::string string; + + /* Parse dictionary */ + for (dictIt = dictValues.begin(); dictIt != dictValues.end(); dictIt++) + { + uPDFParser::DataType* dictData = dictIt->second; + if (dictData->type() == uPDFParser::DataType::STRING) + { + string = ((uPDFParser::String*) dictData)->unescapedValue(); + + unsigned char* encryptedData = (unsigned char*)string.c_str(); + unsigned int dataLength = string.size(); + unsigned char* clearData = new unsigned char[dataLength]; + unsigned int dataOutLength; + + GOUROU_LOG(DEBUG, "Decrypt string " << dictIt->first << " " << dataLength); + + client->Decrypt(CryptoInterface::ALGO_RC4, CryptoInterface::CHAIN_ECB, + tmpKey, 16, /* Key */ + NULL, 0, /* IV */ + encryptedData, dataLength, + clearData, &dataOutLength); + + decodedStrings[dictIt->first] = new uPDFParser::String( + std::string((const char*)clearData, dataOutLength)); + + delete[] clearData; + } + } + + for (dictIt = decodedStrings.begin(); dictIt != decodedStrings.end(); dictIt++) + dictionary.replace(dictIt->first, dictIt->second); + + std::vector::iterator datasIt; + std::vector& datas = (*xrefIt).object()->data(); + uPDFParser::Stream* stream; + + for (datasIt = datas.begin(); datasIt != datas.end(); datasIt++) + { + if ((*datasIt)->type() != uPDFParser::DataType::STREAM) + continue; + + GOUROU_LOG(DEBUG, "Decrypt stream id " << object->objectId()); + + stream = (uPDFParser::Stream*) (*datasIt); + unsigned char* encryptedData = stream->data(); + unsigned int dataLength = stream->dataLength(); + unsigned char* clearData = new unsigned char[dataLength]; + unsigned int dataOutLength; + + client->Decrypt(CryptoInterface::ALGO_RC4, CryptoInterface::CHAIN_ECB, + tmpKey, 16, /* Key */ + NULL, 0, /* IV */ + encryptedData, dataLength, + clearData, &dataOutLength); + + stream->setData(clearData, dataOutLength, true); + } + } + + uPDFParser::Object& trailer = parser.getTrailer(); + trailer.deleteKey("Encrypt"); + + parser.write(filenameOut); + } + + void DRMProcessor::removeDRM(const std::string& filenameIn, const std::string& filenameOut, + ITEM_TYPE type) + { + if (type == PDF) + removePDFDRM(filenameIn, filenameOut); else - EXCEPTION(CLIENT_DRM_FORMAT_NOT_SUPPORTED, "Can't remove DRM in PDF"); + removeEPubDRM(filenameIn, filenameOut); } }