9 Commits
v0.5.2 ... v0.6

14 changed files with 589 additions and 294 deletions

View File

@@ -53,8 +53,8 @@ $(BUILDDIR)/%.$(OBJEXT): $(SRCDIR)/%.$(SRCEXT)
libgourou: libgourou.a libgourou.so
libgourou.a: $(OBJECTS)
$(AR) crs $@ obj/*.o ./lib/updfparser/obj/*.o
libgourou.a: $(OBJECTS) $(UPDFPARSERLIB)
$(AR) crs $@ obj/*.o $(UPDFPARSERLIB)
libgourou.so: $(OBJECTS) $(UPDFPARSERLIB)
$(CXX) obj/*.o $(LDFLAGS) -o $@ -shared

View File

@@ -39,8 +39,7 @@ For libgourou :
For utils :
* QT5Core
* QT5Network
* libcurl
* OpenSSL
* libzip

View File

@@ -98,10 +98,11 @@ namespace gourou
* @param POSTData POST data if needed, if not set, a GET request is done
* @param contentType Optional content type of POST Data
* @param responseHeaders Optional Response headers of HTTP request
* @param fd Optional file descriptor to write request result
*
* @return data of HTTP response
*/
virtual std::string sendHTTPRequest(const std::string& URL, const std::string& POSTData=std::string(""), const std::string& contentType=std::string(""), std::map<std::string, std::string>* responseHeaders=0) = 0;
virtual std::string sendHTTPRequest(const std::string& URL, const std::string& POSTData=std::string(""), const std::string& contentType=std::string(""), std::map<std::string, std::string>* responseHeaders=0, int fd=0) = 0;
};
class RSAInterface

View File

@@ -40,7 +40,7 @@
#define ACS_SERVER "http://adeactivate.adobe.com/adept"
#endif
#define LIBGOUROU_VERSION "0.5.2"
#define LIBGOUROU_VERSION "0.6"
namespace gourou
{
@@ -134,10 +134,11 @@ namespace gourou
* @param POSTData POST data if needed, if not set, a GET request is done
* @param contentType Optional content type of POST Data
* @param responseHeaders Optional Response headers of HTTP request
* @param fd Optional File descriptor to write received data
*
* @return data of HTTP response
*/
ByteArray sendRequest(const std::string& URL, const std::string& POSTData=std::string(), const char* contentType=0, std::map<std::string, std::string>* responseHeaders=0);
ByteArray sendRequest(const std::string& URL, const std::string& POSTData=std::string(), const char* contentType=0, std::map<std::string, std::string>* responseHeaders=0, int fd=0);
/**
* @brief Send HTTP POST request to URL with document as POSTData
@@ -185,10 +186,16 @@ namespace gourou
DRMProcessorClient* getClient() { return client; }
/**
* @brief Remove ADEPT DRM.
* @brief Remove ADEPT DRM
* Warning: for PDF format, filenameIn must be different than filenameOut
*
* @param filenameIn Input file (with ADEPT DRM)
* @param filenameOut Output file (without ADEPT DRM)
* @param type Type of file (ePub or PDF)
* @param encryptionKey Optional encryption key, do not try to decrypt the one inside input file
* @param encryptionKeySize Size of encryption key (if provided)
*/
void removeDRM(const std::string& filenameIn, const std::string& filenameOut, ITEM_TYPE type);
void removeDRM(const std::string& filenameIn, const std::string& filenameOut, ITEM_TYPE type, const unsigned char* encryptionKey=0, unsigned encryptionKeySize=0);
private:
gourou::DRMProcessorClient* client;
@@ -214,12 +221,12 @@ namespace gourou
void fetchLicenseServiceCertificate(const std::string& licenseURL,
const std::string& operatorURL);
void decryptADEPTKey(const std::string& encryptedKey, unsigned char* decryptedKey);
void removeEPubDRM(const std::string& filenameIn, const std::string& filenameOut);
void removeEPubDRM(const std::string& filenameIn, const std::string& filenameOut, const unsigned char* encryptionKey, unsigned encryptionKeySize);
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);
void removePDFDRM(const std::string& filenameIn, const std::string& filenameOut, const unsigned char* encryptionKey, unsigned encryptionKeySize);
};
}

View File

@@ -92,6 +92,7 @@ namespace gourou
USER_INVALID_ACTIVATION_FILE,
USER_NO_AUTHENTICATION_URL,
USER_NO_PROPERTY,
USER_INVALID_INPUT,
};
enum FULFILL_ITEM_ERROR {
@@ -110,7 +111,8 @@ namespace gourou
CLIENT_ZIP_ERROR,
CLIENT_GENERIC_EXCEPTION,
CLIENT_NETWORK_ERROR,
CLIENT_INVALID_PKCS8
CLIENT_INVALID_PKCS8,
CLIENT_FILE_ERROR
};
enum DRM_REMOVAL_ERROR {
@@ -285,15 +287,27 @@ namespace gourou
}
/**
* @brief Write data in a file. If it already exists, it's truncated
* @brief Open a file descriptor on path. If it already exists, it's truncated
*
* @return Created fd, must be closed
*/
static inline void writeFile(std::string path, const unsigned char* data, unsigned int length)
static inline int createNewFile(std::string path)
{
int fd = open(path.c_str(), O_CREAT|O_WRONLY|O_TRUNC, S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH);
if (fd <= 0)
EXCEPTION(GOUROU_FILE_ERROR, "Unable to create " << path);
return fd;
}
/**
* @brief Write data in a file. If it already exists, it's truncated
*/
static inline void writeFile(std::string path, const unsigned char* data, unsigned int length)
{
int fd = createNewFile(path);
if (write(fd, data, length) != length)
EXCEPTION(GOUROU_FILE_ERROR, "Write error for file " << path);

View File

@@ -300,11 +300,13 @@ namespace gourou
appendTextElem(root, "adept:expiration", buffer);
}
ByteArray DRMProcessor::sendRequest(const std::string& URL, const std::string& POSTdata, const char* contentType, std::map<std::string, std::string>* responseHeaders)
ByteArray DRMProcessor::sendRequest(const std::string& URL, const std::string& POSTdata, const char* contentType, std::map<std::string, std::string>* responseHeaders, int fd)
{
if (contentType == 0)
contentType = "";
std::string reply = client->sendHTTPRequest(URL, POSTdata, contentType, responseHeaders);
std::string reply = client->sendHTTPRequest(URL, POSTdata, contentType, responseHeaders, fd);
if (fd) return ByteArray();
pugi::xml_document replyDoc;
replyDoc.load_buffer(reply.c_str(), reply.length());
@@ -590,9 +592,11 @@ namespace gourou
std::map<std::string, std::string> headers;
ByteArray replyData = sendRequest(item->getDownloadURL(), "", 0, &headers);
int fd = createNewFile(path);
writeFile(path, replyData);
sendRequest(item->getDownloadURL(), "", 0, &headers, fd);
close(fd);
GOUROU_LOG(INFO, "Download into " << path);
@@ -667,7 +671,10 @@ namespace gourou
pugi::xml_node signIn = signInRequest.append_child("adept:signIn");
signIn.append_attribute("xmlns:adept") = ADOBE_ADEPT_NS;
std::string loginMethod = user->getLoginMethod();
if (loginMethod.size())
if (adobeID == "anonymous")
signIn.append_attribute("method") = "anonymous";
else if (loginMethod.size())
signIn.append_attribute("method") = loginMethod.c_str();
else
signIn.append_attribute("method") = "AdobeID";
@@ -949,7 +956,8 @@ namespace gourou
}
void DRMProcessor::removeEPubDRM(const std::string& filenameIn, const std::string& filenameOut)
void DRMProcessor::removeEPubDRM(const std::string& filenameIn, const std::string& filenameOut,
const unsigned char* encryptionKey, unsigned encryptionKeySize)
{
ByteArray zipData;
bool removeEncryptionXML = true;
@@ -962,7 +970,16 @@ namespace gourou
std::string encryptedKey = extractTextElem(rightsDoc, "/adept:rights/licenseToken/encryptedKey");
unsigned char decryptedKey[RSA_KEY_SIZE];
if (!encryptionKey)
decryptADEPTKey(encryptedKey, decryptedKey);
else
{
GOUROU_LOG(DEBUG, "Use provided encryption key");
if (encryptionKeySize != 16)
EXCEPTION(DRM_ERR_ENCRYPTION_KEY, "Provided encryption key must be 16 bytes");
memcpy(&decryptedKey[sizeof(decryptedKey)-16], encryptionKey, encryptionKeySize);
}
client->zipReadFile(zipHandler, "META-INF/encryption.xml", zipData);
pugi::xml_document encryptionDoc;
@@ -1001,7 +1018,7 @@ namespace gourou
unsigned int dataOutLength;
client->Decrypt(CryptoInterface::ALGO_AES, CryptoInterface::CHAIN_CBC,
decryptedKey+RSA_KEY_SIZE-16, 16, /* Key */
decryptedKey+sizeof(decryptedKey)-16, 16, /* Key */
_data, 16, /* IV */
&_data[16], zipData.length()-16,
_clearData, &dataOutLength);
@@ -1068,7 +1085,8 @@ namespace gourou
}
}
void DRMProcessor::removePDFDRM(const std::string& filenameIn, const std::string& filenameOut)
void DRMProcessor::removePDFDRM(const std::string& filenameIn, const std::string& filenameOut,
const unsigned char* encryptionKey, unsigned encryptionKeySize)
{
uPDFParser::Parser parser;
bool EBXHandlerFound = false;
@@ -1091,16 +1109,18 @@ namespace gourou
uPDFParser::Integer* ebxVersion;
std::vector<uPDFParser::Object*> objects = parser.objects();
std::vector<uPDFParser::Object*>::reverse_iterator it;
std::vector<uPDFParser::Object*>::iterator it;
std::vector<uPDFParser::Object*>::reverse_iterator rIt;
unsigned char decryptedKey[RSA_KEY_SIZE];
int ebxId;
for(it = objects.rbegin(); it != objects.rend(); it++)
for(rIt = objects.rbegin(); rIt != objects.rend(); rIt++)
{
// Update EBX_HANDLER with rights
if ((*it)->hasKey("Filter") && (**it)["Filter"]->str() == "/EBX_HANDLER")
if ((*rIt)->hasKey("Filter") && (**rIt)["Filter"]->str() == "/EBX_HANDLER")
{
EBXHandlerFound = true;
uPDFParser::Object* ebx = *it;
uPDFParser::Object* ebx = *rIt;
ebxVersion = (uPDFParser::Integer*)(*ebx)["V"];
if (ebxVersion->value() != 4)
@@ -1115,7 +1135,15 @@ namespace gourou
uPDFParser::String* licenseObject = (uPDFParser::String*)(*ebx)["ADEPT_LICENSE"];
ByteArray zippedData = ByteArray::fromBase64(licenseObject->value());
std::string value = licenseObject->value();
// Pad with '='
while ((value.size() % 4))
value += "=";
ByteArray zippedData = ByteArray::fromBase64(value);
if (zippedData.size() == 0)
EXCEPTION(DRM_ERR_ENCRYPTION_KEY, "Invalid ADEPT_LICENSE");
ByteArray rightsStr;
client->inflate(zippedData, rightsStr);
@@ -1124,7 +1152,19 @@ namespace gourou
std::string encryptedKey = extractTextElem(rightsDoc, "/adept:rights/licenseToken/encryptedKey");
if (!encryptionKey)
decryptADEPTKey(encryptedKey, decryptedKey);
else
{
GOUROU_LOG(DEBUG, "Use provided encryption key");
if (encryptionKeySize != 16)
EXCEPTION(DRM_ERR_ENCRYPTION_KEY, "Provided encryption key must be 16 bytes");
memcpy(&decryptedKey[sizeof(decryptedKey)-16], encryptionKey, encryptionKeySize);
}
ebxId = ebx->objectId();
break;
}
}
@@ -1134,28 +1174,29 @@ namespace gourou
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++)
for(it = objects.begin(); it != objects.end(); it++)
{
GOUROU_LOG(DEBUG, "XREF obj " << (*xrefIt).objectId() << " used " << (*xrefIt).used());
uPDFParser::Object* object = *it;
if (!(*xrefIt).used())
continue;
uPDFParser::Object* object = (*xrefIt).object();
if (!object)
if (object->objectId() == ebxId)
{
GOUROU_LOG(DEBUG, "No object");
// object->deleteKey("Filter");
continue;
}
// Should not decrypt XRef stream
if (object->hasKey("Type") && (*object)["Type"]->str() == "/XRef")
{
GOUROU_LOG(DEBUG, "XRef stream at " << object->offset());
continue;
}
GOUROU_LOG(DEBUG, "Obj " << object->objectId());
unsigned char tmpKey[16];
generatePDFObjectKey(ebxVersion->value(),
decryptedKey+RSA_KEY_SIZE-16, 16,
decryptedKey+sizeof(decryptedKey)-16, 16,
object->objectId(), object->generationNumber(),
tmpKey);
@@ -1197,7 +1238,7 @@ namespace gourou
dictionary.replace(dictIt->first, dictIt->second);
std::vector<uPDFParser::DataType*>::iterator datasIt;
std::vector<uPDFParser::DataType*>& datas = (*xrefIt).object()->data();
std::vector<uPDFParser::DataType*>& datas = object->data();
uPDFParser::Stream* stream;
for (datasIt = datas.begin(); datasIt != datas.end(); datasIt++)
@@ -1220,6 +1261,8 @@ namespace gourou
clearData, &dataOutLength);
stream->setData(clearData, dataOutLength, true);
if (dataOutLength != dataLength)
GOUROU_LOG(DEBUG, "New size " << dataOutLength);
}
}
@@ -1230,11 +1273,11 @@ namespace gourou
}
void DRMProcessor::removeDRM(const std::string& filenameIn, const std::string& filenameOut,
ITEM_TYPE type)
ITEM_TYPE type, const unsigned char* encryptionKey, unsigned encryptionKeySize)
{
if (type == PDF)
removePDFDRM(filenameIn, filenameOut);
removePDFDRM(filenameIn, filenameOut, encryptionKey, encryptionKeySize);
else
removeEPubDRM(filenameIn, filenameOut);
removeEPubDRM(filenameIn, filenameOut, encryptionKey, encryptionKeySize);
}
}

View File

@@ -1,10 +1,10 @@
TARGETS=acsmdownloader adept_activate adept_remove
CXXFLAGS=-Wall `pkg-config --cflags Qt5Core Qt5Network` -fPIC -I$(ROOT)/include -I$(ROOT)/lib/pugixml/src/
CXXFLAGS=-Wall -fPIC -I$(ROOT)/include -I$(ROOT)/lib/pugixml/src/
STATIC_DEP=
LDFLAGS=`pkg-config --libs Qt5Core Qt5Network` -L$(ROOT) -lcrypto -lzip -lz
LDFLAGS=-L$(ROOT) -lcrypto -lzip -lz -lcurl
ifneq ($(STATIC_UTILS),)
STATIC_DEP = $(ROOT)/libgourou.a
@@ -18,7 +18,7 @@ else
CXXFLAGS += -O2
endif
COMMON_DEPS = drmprocessorclientimpl.cpp $(STATIC_DEP)
COMMON_DEPS = drmprocessorclientimpl.cpp utils_common.cpp $(STATIC_DEP)
all: $(TARGETS)

View File

@@ -26,21 +26,14 @@
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include <unistd.h>
#include <getopt.h>
#include <iostream>
#include <QFile>
#include <QDir>
#include <QCoreApplication>
#include <QRunnable>
#include <QThreadPool>
#include <algorithm>
#include <libgourou.h>
#include "drmprocessorclientimpl.h"
#define ARRAY_SIZE(arr) (sizeof(arr)/sizeof(arr[0]))
#include "utils_common.h"
static const char* deviceFile = "device.xml";
static const char* activationFile = "activation.xml";
@@ -49,23 +42,13 @@ static const char* acsmFile = 0;
static bool exportPrivateKey = false;
static const char* outputFile = 0;
static const char* outputDir = 0;
static const char* defaultDirs[] = {
".adept/",
"./adobe-digital-editions/",
"./.adobe-digital-editions/"
};
class ACSMDownloader: public QRunnable
class ACSMDownloader
{
public:
ACSMDownloader(QCoreApplication* app):
app(app)
{
setAutoDelete(false);
}
void run()
int run()
{
int ret = 0;
try
@@ -84,9 +67,8 @@ public:
if (outputDir)
{
QDir dir(outputDir);
if (!dir.exists(outputDir))
dir.mkpath(outputDir);
if (!fileExists(outputDir))
mkpath(outputDir);
filename = std::string(outputDir) + "/" + filename;
}
@@ -116,9 +98,8 @@ public:
if (outputDir)
{
QDir dir(outputDir);
if (!dir.exists(outputDir))
dir.mkpath(outputDir);
if (!fileExists(outputDir))
mkpath(outputDir);
filename = std::string(outputDir) + "/" + filename;
}
@@ -132,8 +113,7 @@ public:
finalName += ".pdf";
else
finalName += ".epub";
QDir dir;
dir.rename(filename.c_str(), finalName.c_str());
rename(filename.c_str(), finalName.c_str());
filename = finalName;
}
std::cout << "Created " << filename << std::endl;
@@ -144,37 +124,10 @@ public:
ret = 1;
}
this->app->exit(ret);
return ret;
}
private:
QCoreApplication* app;
};
static const char* findFile(const char* filename, bool inDefaultDirs=true)
{
QFile file(filename);
if (file.exists())
return strdup(filename);
if (!inDefaultDirs) return 0;
for (int i=0; i<(int)ARRAY_SIZE(defaultDirs); i++)
{
QString path = QString(defaultDirs[i]) + QString(filename);
file.setFileName(path);
if (file.exists())
return strdup(path.toStdString().c_str());
}
return 0;
}
static void version(void)
{
std::cout << "Current libgourou version : " << gourou::DRMProcessor::VERSION << std::endl ;
}
static void usage(const char* cmd)
{
@@ -275,8 +228,7 @@ int main(int argc, char** argv)
return -1;
}
QCoreApplication app(argc, argv);
ACSMDownloader downloader(&app);
ACSMDownloader downloader;
int i;
bool hasErrors = false;
@@ -306,8 +258,7 @@ int main(int argc, char** argv)
}
else
{
QFile file(acsmFile);
if (!file.exists())
if (!fileExists(acsmFile))
{
std::cout << "Error : " << acsmFile << " doesn't exists" << std::endl;
ret = -1;
@@ -315,9 +266,7 @@ int main(int argc, char** argv)
}
}
QThreadPool::globalInstance()->start(&downloader);
ret = app.exec();
ret = downloader.run();
end:
for (i=0; i<(int)ARRAY_SIZE(files); i++)

View File

@@ -28,22 +28,16 @@
#include <unistd.h>
#include <getopt.h>
#include <stdlib.h>
#include <termios.h>
#include <string.h>
#include <limits.h>
#include <iostream>
#include <ostream>
#include <QFile>
#include <QDir>
#include <QCoreApplication>
#include <QRunnable>
#include <QThreadPool>
#include <libgourou.h>
#include "drmprocessorclientimpl.h"
#define ARRAY_SIZE(arr) (sizeof(arr)/sizeof(arr[0]))
#include "utils_common.h"
static const char* username = 0;
static const char* password = 0;
@@ -100,16 +94,11 @@ static std::string getpass(const char *prompt, bool show_asterisk=false)
}
class ADEPTActivate: public QRunnable
class ADEPTActivate
{
public:
ADEPTActivate(QCoreApplication* app):
app(app)
{
setAutoDelete(false);
}
void run()
int run()
{
int ret = 0;
try
@@ -128,17 +117,10 @@ public:
ret = 1;
}
this->app->exit(ret);
return ret;
}
private:
QCoreApplication* app;
};
static void version(void)
{
std::cout << "Current libgourou version : " << gourou::DRMProcessor::VERSION << std::endl ;
}
static void usage(const char* cmd)
{
@@ -162,8 +144,8 @@ static void usage(const char* cmd)
static const char* abspath(const char* filename)
{
const char* root = getcwd(0, PATH_MAX);
QString fullPath = QString(root) + QString("/") + QString(filename);
const char* res = strdup(fullPath.toStdString().c_str());
std::string fullPath = std::string(root) + std::string("/") + filename;
const char* res = strdup(fullPath.c_str());
free((void*)root);
@@ -255,9 +237,8 @@ int main(int argc, char** argv)
// Relative path
if (_outputDir[0] == '.' || _outputDir[0] != '/')
{
QFile file(_outputDir);
// realpath doesn't works if file/dir doesn't exists
if (file.exists())
if (fileExists(_outputDir))
outputDir = strdup(realpath(_outputDir, 0));
else
outputDir = strdup(abspath(_outputDir));
@@ -266,10 +247,8 @@ int main(int argc, char** argv)
outputDir = strdup(_outputDir);
}
QCoreApplication app(argc, argv);
QFile file(outputDir);
if (file.exists())
std::string pass;
if (fileExists(outputDir))
{
int key;
@@ -289,7 +268,6 @@ int main(int argc, char** argv)
;
}
std::string pass;
if (!password)
{
char prompt[128];
@@ -298,13 +276,11 @@ int main(int argc, char** argv)
password = pass.c_str();
}
ADEPTActivate activate(&app);
QThreadPool::globalInstance()->start(&activate);
ADEPTActivate activate;
ret = app.exec();
ret = activate.run();
end:
free((void*)outputDir);
return ret;
}

View File

@@ -26,23 +26,15 @@
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include <unistd.h>
#include <getopt.h>
#include <iostream>
#include <QFile>
#include <QDir>
#include <QCoreApplication>
#include <QRunnable>
#include <QThreadPool>
#include <QTemporaryFile>
#include <libgourou.h>
#include <libgourou_common.h>
#include "drmprocessorclientimpl.h"
#define ARRAY_SIZE(arr) (sizeof(arr)/sizeof(arr[0]))
#include "drmprocessorclientimpl.h"
#include "utils_common.h"
static const char* deviceFile = "device.xml";
static const char* activationFile = "activation.xml";
@@ -50,27 +42,35 @@ static const char* devicekeyFile = "devicesalt";
static const char* inputFile = 0;
static const char* outputFile = 0;
static const char* outputDir = 0;
static const char* defaultDirs[] = {
".adept/",
"./adobe-digital-editions/",
"./.adobe-digital-editions/"
};
static char* encryptionKeyUser = 0;
static unsigned char* encryptionKey = 0;
static unsigned encryptionKeySize = 0;
static inline unsigned char htoi(unsigned char c)
{
if (c >= '0' && c <= '9')
c -= '0';
else if (c >= 'a' && c <= 'f')
c -= 'a' - 10;
else if (c >= 'A' && c <= 'F')
c -= 'A' - 10;
else
EXCEPTION(gourou::USER_INVALID_INPUT, "Invalid character " << c << " in encryption key");
return c;
}
static inline bool endsWith(const std::string& s, const std::string& suffix)
{
return s.rfind(suffix) == std::abs((int)(s.size()-suffix.size()));
}
class ADEPTRemove: public QRunnable
class ADEPTRemove
{
public:
ADEPTRemove(QCoreApplication* app):
app(app)
{
setAutoDelete(false);
}
void run()
int run()
{
int ret = 0;
try
@@ -87,9 +87,8 @@ public:
if (outputDir)
{
QDir dir(outputDir);
if (!dir.exists(outputDir))
dir.mkpath(outputDir);
if (!fileExists(outputDir))
mkpath(outputDir);
filename = std::string(outputDir) + "/" + filename;
}
@@ -105,12 +104,9 @@ public:
if (inputFile != filename)
{
QFile::remove(filename.c_str());
if (!QFile::copy(inputFile, filename.c_str()))
{
EXCEPTION(gourou::DRM_FILE_ERROR, "Unable to copy " << inputFile << " into " << filename);
}
processor.removeDRM(inputFile, filename, type);
unlink(filename.c_str());
fileCopy(inputFile, filename.c_str());
processor.removeDRM(inputFile, filename, type, encryptionKey, encryptionKeySize);
std::cout << "DRM removed into new file " << filename << std::endl;
}
else
@@ -118,21 +114,19 @@ public:
// Use temp file for PDF
if (type == gourou::DRMProcessor::ITEM_TYPE::PDF)
{
QTemporaryFile tempFile;
tempFile.open();
tempFile.setAutoRemove(false); // In case of failure
processor.removeDRM(inputFile, tempFile.fileName().toStdString(), type);
char* tempFile = tempnam("/tmp", NULL);
processor.removeDRM(inputFile, tempFile, type, encryptionKey, encryptionKeySize);
/* Original file must be removed before doing a copy... */
QFile origFile(inputFile);
origFile.remove();
if (!QFile::copy(tempFile.fileName(), filename.c_str()))
unlink(inputFile);
if (!rename(tempFile, filename.c_str()))
{
EXCEPTION(gourou::DRM_FILE_ERROR, "Unable to copy " << tempFile.fileName().toStdString() << " into " << filename);
free(tempFile);
EXCEPTION(gourou::DRM_FILE_ERROR, "Unable to copy " << tempFile << " into " << filename);
}
tempFile.setAutoRemove(true);
free(tempFile);
}
else
processor.removeDRM(inputFile, filename, type);
processor.removeDRM(inputFile, filename, type, encryptionKey, encryptionKeySize);
std::cout << "DRM removed from " << filename << std::endl;
}
} catch(std::exception& e)
@@ -141,38 +135,10 @@ public:
ret = 1;
}
this->app->exit(ret);
return ret;
}
private:
QCoreApplication* app;
};
static const char* findFile(const char* filename, bool inDefaultDirs=true)
{
QFile file(filename);
if (file.exists())
return strdup(filename);
if (!inDefaultDirs) return 0;
for (int i=0; i<(int)ARRAY_SIZE(defaultDirs); i++)
{
QString path = QString(defaultDirs[i]) + QString(filename);
file.setFileName(path);
if (file.exists())
return strdup(path.toStdString().c_str());
}
return 0;
}
static void version(void)
{
std::cout << "Current libgourou version : " << gourou::DRMProcessor::VERSION << std::endl ;
}
static void usage(const char* cmd)
{
std::cout << "Remove ADEPT DRM (from Adobe) of EPUB/PDF file" << std::endl;
@@ -213,14 +179,14 @@ int main(int argc, char** argv)
{"output-dir", required_argument, 0, 'O' },
{"output-file", required_argument, 0, 'o' },
{"input-file", required_argument, 0, 'f' },
{"export-private-key",no_argument, 0, 'e' },
{"encryption-key", required_argument, 0, 'K' }, // Private option
{"verbose", no_argument, 0, 'v' },
{"version", no_argument, 0, 'V' },
{"help", no_argument, 0, 'h' },
{0, 0, 0, 0 }
};
c = getopt_long(argc, argv, "d:a:k:O:o:f:evVh",
c = getopt_long(argc, argv, "d:a:k:O:o:f:K:vVh",
long_options, &option_index);
if (c == -1)
break;
@@ -244,6 +210,9 @@ int main(int argc, char** argv)
case 'o':
outputFile = optarg;
break;
case 'K':
encryptionKeyUser = optarg;
break;
case 'v':
verbose++;
break;
@@ -268,8 +237,7 @@ int main(int argc, char** argv)
return -1;
}
QCoreApplication app(argc, argv);
ADEPTRemove remover(&app);
ADEPTRemove remover;
int i;
bool hasErrors = false;
@@ -286,12 +254,36 @@ int main(int argc, char** argv)
}
}
if (encryptionKeyUser)
{
int size = std::string(encryptionKeyUser).size();
if ((size % 2))
{
std::cout << "Error : Encryption key must be odd length" << std::endl;
goto end;
}
if (encryptionKeyUser[0] == '0' && encryptionKeyUser[1] == 'x')
{
encryptionKeyUser += 2;
size -= 2;
}
encryptionKey = new unsigned char[size/2];
for(i=0; i<size; i+=2)
{
encryptionKey[i/2] = htoi(encryptionKeyUser[i]) << 4;
encryptionKey[i/2] |= htoi(encryptionKeyUser[i+1]);
}
encryptionKeySize = size/2;
}
if (hasErrors)
goto end;
QThreadPool::globalInstance()->start(&remover);
ret = app.exec();
ret = remover.run();
end:
for (i=0; i<(int)ARRAY_SIZE(files); i++)
@@ -300,5 +292,8 @@ end:
free((void*)*files[i]);
}
if (encryptionKey)
free(encryptionKey);
return ret;
}

View File

@@ -25,17 +25,18 @@
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include <bytearray.h>
#include <algorithm>
#include <cctype>
#include <locale>
#include <openssl/rand.h>
#include <openssl/pkcs12.h>
#include <openssl/evp.h>
#include <openssl/err.h>
#include <QCoreApplication>
#include <QNetworkReply>
#include <QNetworkRequest>
#include <QNetworkAccessManager>
#include <QFile>
#include <curl/curl.h>
#include <zlib.h>
#include <zip.h>
@@ -44,6 +45,27 @@
#include <libgourou_log.h>
#include "drmprocessorclientimpl.h"
// https://stackoverflow.com/questions/216823/how-to-trim-a-stdstring
// trim from start (in place)
static inline void ltrim(std::string &s) {
s.erase(s.begin(), std::find_if(s.begin(), s.end(), [](unsigned char ch) {
return !std::isspace(ch);
}));
}
// trim from end (in place)
static inline void rtrim(std::string &s) {
s.erase(std::find_if(s.rbegin(), s.rend(), [](unsigned char ch) {
return !std::isspace(ch);
}).base(), s.end());
}
// trim from both ends (in place)
static inline void trim(std::string &s) {
ltrim(s);
rtrim(s);
}
/* Digest interface */
void* DRMProcessorClientImpl::createDigest(const std::string& digestName)
{
@@ -88,25 +110,78 @@ void DRMProcessorClientImpl::randBytes(unsigned char* bytesOut, unsigned int len
}
/* HTTP interface */
#define HTTP_REQ_MAX_RETRY 5
#define DISPLAY_THRESHOLD 10*1024 // Threshold to display download progression
static unsigned downloadedBytes;
static void downloadProgress(qint64 bytesReceived, qint64 bytesTotal) {
static int downloadProgress(void *clientp, curl_off_t dltotal, curl_off_t dlnow,
curl_off_t ultotal, curl_off_t ulnow)
{
// For "big" files only
if (bytesTotal >= DISPLAY_THRESHOLD && gourou::logLevel >= gourou::WARN)
if (dltotal >= DISPLAY_THRESHOLD && gourou::logLevel >= gourou::WARN)
{
int percent = 0;
if (bytesTotal)
percent = (bytesReceived * 100) / bytesTotal;
if (dltotal)
percent = (dlnow * 100) / dltotal;
std::cout << "\rDownload " << percent << "%" << std::flush;
}
return 0;
}
std::string DRMProcessorClientImpl::sendHTTPRequest(const std::string& URL, const std::string& POSTData, const std::string& contentType, std::map<std::string, std::string>* responseHeaders)
static size_t curlRead(void *data, size_t size, size_t nmemb, void *userp)
{
QNetworkRequest request(QUrl(URL.c_str()));
QNetworkAccessManager networkManager;
QByteArray replyData;
gourou::ByteArray* replyData = (gourou::ByteArray*) userp;
replyData->append((unsigned char*)data, size*nmemb);
return size*nmemb;
}
static size_t curlReadFd(void *data, size_t size, size_t nmemb, void *userp)
{
int fd = *(int*) userp;
size_t res = write(fd, data, size*nmemb);
downloadedBytes += res;
return res;
}
static size_t curlHeaders(char *buffer, size_t size, size_t nitems, void *userdata)
{
std::map<std::string, std::string>* responseHeaders = (std::map<std::string, std::string>*)userdata;
std::string::size_type pos = 0;
std::string buf(buffer, size*nitems);
pos = buf.find(":", pos);
if (pos != std::string::npos)
{
std::string key = std::string(buffer, pos);
std::string value = std::string(&buffer[pos+1], (size*nitems)-(pos+1));
trim(key);
trim(value);
(*responseHeaders)[key] = value;
if (gourou::logLevel >= gourou::DEBUG)
std::cout << key << " : " << value << std::endl;
}
return size*nitems;
}
std::string DRMProcessorClientImpl::sendHTTPRequest(const std::string& URL, const std::string& POSTData, const std::string& contentType, std::map<std::string, std::string>* responseHeaders, int fd)
{
gourou::ByteArray replyData;
std::map<std::string, std::string> localHeaders;
if (!responseHeaders)
responseHeaders = &localHeaders;
GOUROU_LOG(gourou::INFO, "Send request to " << URL);
if (POSTData.size())
@@ -114,54 +189,99 @@ std::string DRMProcessorClientImpl::sendHTTPRequest(const std::string& URL, cons
GOUROU_LOG(gourou::DEBUG, "<<< " << std::endl << POSTData);
}
request.setRawHeader("Accept", "*/*");
request.setRawHeader("User-Agent", "book2png");
if (contentType.size())
request.setRawHeader("Content-Type", contentType.c_str());
unsigned prevDownloadedBytes;
downloadedBytes = 0;
CURL *curl = curl_easy_init();
CURLcode res;
curl_easy_setopt(curl, CURLOPT_URL, URL.c_str());
curl_easy_setopt(curl, CURLOPT_USERAGENT, "book2png");
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1);
QNetworkReply* reply;
struct curl_slist *list = NULL;
list = curl_slist_append(list, "Accept: */*");
std::string _contentType;
if (contentType.size())
{
_contentType = "Content-Type: " + contentType;
list = curl_slist_append(list, _contentType.c_str());
}
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, list);
if (POSTData.size())
reply = networkManager.post(request, POSTData.c_str());
else
reply = networkManager.get(request);
QEventLoop loop;
QObject::connect(reply, &QNetworkReply::finished, &loop, &QEventLoop::quit);
// Handled just below
QObject::connect(reply, &QNetworkReply::errorOccurred, &loop, &QEventLoop::quit);
QObject::connect(reply, &QNetworkReply::downloadProgress, &loop, downloadProgress);
loop.exec();
QByteArray location = reply->rawHeader("Location");
if (location.size() != 0)
{
GOUROU_LOG(gourou::DEBUG, "New location");
return sendHTTPRequest(location.constData(), POSTData, contentType, responseHeaders);
curl_easy_setopt(curl, CURLOPT_POST, 1L);
curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, POSTData.size());
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, POSTData.data());
}
if (reply->error() != QNetworkReply::NoError)
EXCEPTION(gourou::CLIENT_NETWORK_ERROR, "Error " << reply->errorString().toStdString());
QList<QByteArray> headers = reply->rawHeaderList();
for (int i = 0; i < headers.size(); ++i) {
if (gourou::logLevel >= gourou::DEBUG)
std::cout << headers[i].constData() << " : " << reply->rawHeader(headers[i]).constData() << std::endl;
if (responseHeaders)
(*responseHeaders)[headers[i].constData()] = reply->rawHeader(headers[i]).constData();
if (fd)
{
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, curlReadFd);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void*)&fd);
}
else
{
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, curlRead);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void*)&replyData);
}
replyData = reply->readAll();
if (replyData.size() >= DISPLAY_THRESHOLD && gourou::logLevel >= gourou::WARN)
curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, curlHeaders);
curl_easy_setopt(curl, CURLOPT_HEADERDATA, (void*)responseHeaders);
curl_easy_setopt(curl, CURLOPT_XFERINFOFUNCTION, downloadProgress);
curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 0);
for (int i=0; i<HTTP_REQ_MAX_RETRY; i++)
{
prevDownloadedBytes = downloadedBytes;
if (downloadedBytes)
curl_easy_setopt(curl, CURLOPT_RESUME_FROM, downloadedBytes);
res = curl_easy_perform(curl);
// Connexion failed, wait & retry
if (res == CURLE_COULDNT_CONNECT)
{
GOUROU_LOG(gourou::WARN, "Connection failed, attempt " << (i+1) << "/" << HTTP_REQ_MAX_RETRY);
}
// Transfer failed but some data has been received
// --> try again without incrementing tries
else if (res == CURLE_RECV_ERROR)
{
if (prevDownloadedBytes != downloadedBytes)
{
GOUROU_LOG(gourou::WARN, "Connection broken, but data received, try again");
i--;
}
else
GOUROU_LOG(gourou::WARN, "Connection broken and no data received, attempt " << (i+1) << "/" << HTTP_REQ_MAX_RETRY);
}
// Other error --> fail
else
break;
// Wait a little bit (250ms * i)
usleep((250 * 1000) * (i+1));
}
curl_slist_free_all(list);
curl_easy_cleanup(curl);
if (res != CURLE_OK)
EXCEPTION(gourou::CLIENT_NETWORK_ERROR, "Error " << curl_easy_strerror(res));
if ((downloadedBytes >= DISPLAY_THRESHOLD || replyData.size() >= DISPLAY_THRESHOLD) &&
gourou::logLevel >= gourou::WARN)
std::cout << std::endl;
if (reply->rawHeader("Content-Type") == "application/vnd.adobe.adept+xml")
if ((*responseHeaders)["Content-Type"] == "application/vnd.adobe.adept+xml")
{
GOUROU_LOG(gourou::DEBUG, ">>> " << std::endl << replyData.data());
}
return std::string(replyData.data(), replyData.length());
return std::string((char*)replyData.data(), replyData.length());
}
void DRMProcessorClientImpl::RSAPrivateEncrypt(const unsigned char* RSAKey, unsigned int RSAKeyLength,
@@ -532,7 +652,7 @@ void DRMProcessorClientImpl::inflate(gourou::ByteArray& data, gourou::ByteArray&
{
// Real error
if (ret == Z_BUF_ERROR && infstream.avail_out == (uInt)dataSize)
EXCEPTION(gourou::CLIENT_ZIP_ERROR, "Inflate error, code " << zError(ret) << ", msg " << infstream.msg);
break;
result.append(buffer, dataSize-infstream.avail_out);

View File

@@ -46,7 +46,7 @@ public:
virtual void randBytes(unsigned char* bytesOut, unsigned int length);
/* HTTP interface */
virtual std::string sendHTTPRequest(const std::string& URL, const std::string& POSTData=std::string(""), const std::string& contentType=std::string(""), std::map<std::string, std::string>* responseHeaders=0);
virtual std::string sendHTTPRequest(const std::string& URL, const std::string& POSTData=std::string(""), const std::string& contentType=std::string(""), std::map<std::string, std::string>* responseHeaders=0, int fd=0);
virtual void RSAPrivateEncrypt(const unsigned char* RSAKey, unsigned int RSAKeyLength,
const RSA_KEY_TYPE keyType, const std::string& password,

127
utils/utils_common.cpp Normal file
View File

@@ -0,0 +1,127 @@
/*
Copyright (c) 2022, Grégory Soutadé
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* Neither the name of the copyright holder nor the
names of its contributors may be used to endorse or promote products
derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY
EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <string.h>
#include <stdio.h>
#include <fcntl.h>
#include <limits.h>
#include <iostream>
#include <libgourou.h>
#include <libgourou_common.h>
#include "utils_common.h"
static const char* defaultDirs[] = {
".adept/",
"./adobe-digital-editions/",
"./.adobe-digital-editions/"
};
void version(void)
{
std::cout << "Current libgourou version : " << gourou::DRMProcessor::VERSION << std::endl ;
}
bool fileExists(const char* filename)
{
struct stat _stat;
int ret = stat(filename, &_stat);
return (ret == 0);
}
const char* findFile(const char* filename, bool inDefaultDirs)
{
if (fileExists(filename))
return strdup(filename);
if (!inDefaultDirs) return 0;
for (int i=0; i<(int)ARRAY_SIZE(defaultDirs); i++)
{
std::string path = std::string(defaultDirs[i]) + filename;
if (fileExists(path.c_str()))
return strdup(path.c_str());
}
return 0;
}
// https://stackoverflow.com/questions/2336242/recursive-mkdir-system-call-on-unix
void mkpath(const char *dir)
{
char tmp[PATH_MAX];
char *p = NULL;
size_t len;
snprintf(tmp, sizeof(tmp),"%s",dir);
len = strlen(tmp);
if (tmp[len - 1] == '/')
tmp[len - 1] = 0;
for (p = tmp + 1; *p; p++)
if (*p == '/') {
*p = 0;
mkdir(tmp, S_IRWXU);
*p = '/';
}
mkdir(tmp, S_IRWXU);
}
void fileCopy(const char* in, const char* out)
{
char buffer[4096];
int ret, fdIn, fdOut;
fdIn = open(in, O_RDONLY);
if (!fdIn)
EXCEPTION(gourou::CLIENT_FILE_ERROR, "Unable to open " << in);
fdOut = gourou::createNewFile(out);
if (!fdOut)
{
close (fdIn);
EXCEPTION(gourou::CLIENT_FILE_ERROR, "Unable to open " << out);
}
while (true)
{
ret = ::read(fdIn, buffer, sizeof(buffer));
if (ret <= 0)
break;
::write(fdOut, buffer, ret);
}
close (fdIn);
close (fdOut);
}

64
utils/utils_common.h Normal file
View File

@@ -0,0 +1,64 @@
/*
Copyright (c) 2022, Grégory Soutadé
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* Neither the name of the copyright holder nor the
names of its contributors may be used to endorse or promote products
derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY
EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#ifndef _UTILS_COMMON_H_
#define _UTILS_COMMON_H_
#define ARRAY_SIZE(arr) (sizeof(arr)/sizeof(arr[0]))
/**
* @brief Display libgourou version
*/
void version(void);
/**
* @brief Find a given filename in current directory and/or in default directories
*
* @param filename Filename to search
* @param inDefaultDirs Search is default directories or not
*
* @return A copy of full path
*/
const char* findFile(const char* filename, bool inDefaultDirs=true);
/**
* @brief Does the file (or directory exists)
*/
bool fileExists(const char* filename);
/**
* @brief Recursively created dir
*/
void mkpath(const char *dir);
/**
* @brief Copy file in into file out
*/
void fileCopy(const char* in, const char* out);
#endif