Add support of DRM removal for PDF

This commit is contained in:
Grégory Soutadé 2021-12-18 17:40:24 +01:00
parent 4fe846f78e
commit f0ff97f7d7
3 changed files with 239 additions and 36 deletions

View File

@ -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);
};
}

View File

@ -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
};
/**

View File

@ -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<uPDFParser::Object*> objects = parser.objects();
std::vector<uPDFParser::Object*>::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<uPDFParser::XRefValue> xrefTable = parser.xrefTable();
std::vector<uPDFParser::XRefValue>::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<std::string, uPDFParser::DataType*>& dictValues = dictionary.value();
std::map<std::string, uPDFParser::DataType*>::iterator dictIt;
std::map<std::string, uPDFParser::DataType*> 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<uPDFParser::DataType*>::iterator datasIt;
std::vector<uPDFParser::DataType*>& 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);
}
}