forked from soutade/libgourou
Add support of DRM removal for PDF
This commit is contained in:
parent
4fe846f78e
commit
f0ff97f7d7
|
@ -164,6 +164,9 @@ namespace gourou
|
||||||
*/
|
*/
|
||||||
std::string serializeRSAPrivateKey(void* rsa);
|
std::string serializeRSAPrivateKey(void* rsa);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Export clear private license key into path
|
||||||
|
*/
|
||||||
void exportPrivateLicenseKey(std::string path);
|
void exportPrivateLicenseKey(std::string path);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -181,7 +184,11 @@ namespace gourou
|
||||||
*/
|
*/
|
||||||
DRMProcessorClient* getClient() { return client; }
|
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:
|
private:
|
||||||
gourou::DRMProcessorClient* client;
|
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 buildSignInRequest(pugi::xml_document& signInRequest, const std::string& adobeID, const std::string& adobePassword, const std::string& authenticationCertificate);
|
||||||
void fetchLicenseServiceCertificate(const std::string& licenseURL,
|
void fetchLicenseServiceCertificate(const std::string& licenseURL,
|
||||||
const std::string& operatorURL);
|
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);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -113,8 +113,11 @@ namespace gourou
|
||||||
};
|
};
|
||||||
|
|
||||||
enum DRM_REMOVAL_ERROR {
|
enum DRM_REMOVAL_ERROR {
|
||||||
CLIENT_DRM_ERR_ENCRYPTION_KEY = 0x6000,
|
DRM_ERR_ENCRYPTION_KEY = 0x6000,
|
||||||
CLIENT_DRM_FORMAT_NOT_SUPPORTED,
|
DRM_VERSION_NOT_SUPPORTED,
|
||||||
|
DRM_FILE_ERROR,
|
||||||
|
DRM_FORMAT_NOT_SUPPORTED,
|
||||||
|
DRM_IN_OUT_EQUALS
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -35,7 +35,6 @@
|
||||||
#define ASN_TEXT 0x04
|
#define ASN_TEXT 0x04
|
||||||
#define ASN_ATTRIBUTE 0x05
|
#define ASN_ATTRIBUTE 0x05
|
||||||
|
|
||||||
|
|
||||||
namespace gourou
|
namespace gourou
|
||||||
{
|
{
|
||||||
GOUROU_LOG_LEVEL logLevel = WARN;
|
GOUROU_LOG_LEVEL logLevel = WARN;
|
||||||
|
@ -851,10 +850,10 @@ namespace gourou
|
||||||
// Generate IV in front
|
// Generate IV in front
|
||||||
client->randBytes(encrypted_data, 16);
|
client->randBytes(encrypted_data, 16);
|
||||||
|
|
||||||
client->AESEncrypt(CryptoInterface::CHAIN_CBC,
|
client->Encrypt(CryptoInterface::ALGO_AES, CryptoInterface::CHAIN_CBC,
|
||||||
deviceKey, 16, encrypted_data, 16,
|
deviceKey, 16, encrypted_data, 16,
|
||||||
data, len,
|
data, len,
|
||||||
encrypted_data+16, &outLen);
|
encrypted_data+16, &outLen);
|
||||||
|
|
||||||
ByteArray res(encrypted_data, outLen+16);
|
ByteArray res(encrypted_data, outLen+16);
|
||||||
|
|
||||||
|
@ -870,10 +869,10 @@ namespace gourou
|
||||||
const unsigned char* deviceKey = device->getDeviceKey();
|
const unsigned char* deviceKey = device->getDeviceKey();
|
||||||
unsigned char* decrypted_data = new unsigned char[len-16];
|
unsigned char* decrypted_data = new unsigned char[len-16];
|
||||||
|
|
||||||
client->AESDecrypt(CryptoInterface::CHAIN_CBC,
|
client->Decrypt(CryptoInterface::ALGO_AES, CryptoInterface::CHAIN_CBC,
|
||||||
deviceKey, 16, data, 16,
|
deviceKey, 16, data, 16,
|
||||||
data+16, len-16,
|
data+16, len-16,
|
||||||
decrypted_data, &outLen);
|
decrypted_data, &outLen);
|
||||||
|
|
||||||
ByteArray res(decrypted_data, outLen);
|
ByteArray res(decrypted_data, outLen);
|
||||||
|
|
||||||
|
@ -926,18 +925,9 @@ namespace gourou
|
||||||
int DRMProcessor::getLogLevel() {return (int)gourou::logLevel;}
|
int DRMProcessor::getLogLevel() {return (int)gourou::logLevel;}
|
||||||
void DRMProcessor::setLogLevel(int logLevel) {gourou::logLevel = (GOUROU_LOG_LEVEL)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);
|
ByteArray arrayEncryptedKey = ByteArray::fromBase64(encryptedKey);
|
||||||
unsigned char decryptedKey[RSA_KEY_SIZE];
|
|
||||||
|
|
||||||
std::string privateKeyData = user->getPrivateLicenseKey();
|
std::string privateKeyData = user->getPrivateLicenseKey();
|
||||||
ByteArray privateRSAKey = ByteArray::fromBase64(privateKeyData);
|
ByteArray privateRSAKey = ByteArray::fromBase64(privateKeyData);
|
||||||
|
@ -951,8 +941,24 @@ namespace gourou
|
||||||
|
|
||||||
if (decryptedKey[0] != 0x00 || decryptedKey[1] != 0x02 ||
|
if (decryptedKey[0] != 0x00 || decryptedKey[1] != 0x02 ||
|
||||||
decryptedKey[RSA_KEY_SIZE-16-1] != 0x00)
|
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);
|
client->zipReadFile(zipHandler, "META-INF/encryption.xml", zipData);
|
||||||
pugi::xml_document encryptionDoc;
|
pugi::xml_document encryptionDoc;
|
||||||
encryptionDoc.load_string((const char*)zipData.data());
|
encryptionDoc.load_string((const char*)zipData.data());
|
||||||
|
@ -969,18 +975,18 @@ namespace gourou
|
||||||
client->zipReadFile(zipHandler, encryptedFile, zipData, false);
|
client->zipReadFile(zipHandler, encryptedFile, zipData, false);
|
||||||
|
|
||||||
unsigned char* _data = zipData.data();
|
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();
|
unsigned char* _clearData = clearData.data();
|
||||||
gourou::ByteArray inflateData(true);
|
gourou::ByteArray inflateData(true);
|
||||||
unsigned int dataOutLength;
|
unsigned int dataOutLength;
|
||||||
|
|
||||||
client->AESDecrypt(CryptoInterface::CHAIN_CBC,
|
client->Decrypt(CryptoInterface::ALGO_AES, CryptoInterface::CHAIN_CBC,
|
||||||
decryptedKey+RSA_KEY_SIZE-16, 16, /* Key */
|
decryptedKey+RSA_KEY_SIZE-16, 16, /* Key */
|
||||||
_data, 16, /* IV */
|
_data, 16, /* IV */
|
||||||
&_data[16], zipData.length()-16,
|
&_data[16], zipData.length()-16,
|
||||||
_clearData, &dataOutLength);
|
_clearData, &dataOutLength);
|
||||||
|
|
||||||
// Add 'Z' at the end, done in ineptepub.py
|
// Add 'Z' at the end, done in ineptepub.py
|
||||||
_clearData[dataOutLength] = 'Z';
|
_clearData[dataOutLength] = 'Z';
|
||||||
|
|
||||||
client->inflate(clearData, inflateData);
|
client->inflate(clearData, inflateData);
|
||||||
|
@ -994,11 +1000,192 @@ namespace gourou
|
||||||
client->zipClose(zipHandler);
|
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)
|
switch(version)
|
||||||
removeEPubDRM(ePubFile);
|
{
|
||||||
|
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
|
else
|
||||||
EXCEPTION(CLIENT_DRM_FORMAT_NOT_SUPPORTED, "Can't remove DRM in PDF");
|
removeEPubDRM(filenameIn, filenameOut);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user