Compare commits

..

No commits in common. "master" and "v0.8.2" have entirely different histories.

30 changed files with 3935 additions and 4240 deletions

View file

@ -12,8 +12,6 @@ LDFLAGS = -lpugixml
VERSION := $(shell cat include/libgourou.h |grep LIBGOUROU_VERSION|cut -d '"' -f2) VERSION := $(shell cat include/libgourou.h |grep LIBGOUROU_VERSION|cut -d '"' -f2)
UNAME := $(shell uname -s)
BUILD_STATIC ?= 0 BUILD_STATIC ?= 0
BUILD_SHARED ?= 1 BUILD_SHARED ?= 1
BUILD_UTILS ?= 1 BUILD_UTILS ?= 1
@ -29,13 +27,8 @@ ifneq ($(BUILD_STATIC), 0)
STATIC_UTILS=1 STATIC_UTILS=1
endif endif
ifneq ($(BUILD_SHARED), 0) ifneq ($(BUILD_SHARED), 0)
ifeq ($(UNAME), Darwin)
TARGETS += libgourou.dylib
TARGET_LIBRARIES += libgourou.dylib libgourou.dylib.$(VERSION)
else
TARGETS += libgourou.so TARGETS += libgourou.so
TARGET_LIBRARIES += libgourou.so libgourou.so.$(VERSION) TARGET_LIBRARIES += libgourou.so libgourou.so.$(VERSION)
endif
endif endif
ifneq ($(BUILD_UTILS), 0) ifneq ($(BUILD_UTILS), 0)
TARGETS += build_utils TARGETS += build_utils
@ -89,12 +82,6 @@ libgourou.so.$(VERSION): $(OBJECTS) $(UPDFPARSERLIB)
libgourou.so: libgourou.so.$(VERSION) libgourou.so: libgourou.so.$(VERSION)
ln -f -s $^ $@ ln -f -s $^ $@
libgourou.dylib.$(VERSION): $(OBJECTS) $(UPDFPARSERLIB)
$(CXX) $^ $(LDFLAGS) -o $@ -shared
libgourou.dylib: libgourou.dylib.$(VERSION)
ln -f -s $^ $@
build_utils: $(TARGET_LIBRARIES) build_utils: $(TARGET_LIBRARIES)
$(MAKE) -C utils ROOT=$(PWD) CXX=$(CXX) AR=$(AR) DEBUG=$(DEBUG) STATIC_UTILS=$(STATIC_UTILS) DESTDIR=$(DESTDIR) PREFIX=$(PREFIX) $(MAKE) -C utils ROOT=$(PWD) CXX=$(CXX) AR=$(AR) DEBUG=$(DEBUG) STATIC_UTILS=$(STATIC_UTILS) DESTDIR=$(DESTDIR) PREFIX=$(PREFIX)

View file

@ -1,16 +1,16 @@
Introduction Introduction
------------ ------------
libgourou is a free implementation of Adobe's ADEPT protocol used to add DRM on ePub/PDF files. It overcomes the lack of Adobe support for Linux platforms. libgourou is a free implementation of Adobe's ADEPT protocol used to add DRM on ePub/PDF files. It overcome the lacks of Adobe support for Linux platforms.
Architecture Architecture
------------ ------------
Like RMSDK, libgourou has a client/server scheme. All platform specific functions (crypto, network...) have to be implemented in a client class (that derives from DRMProcessorClient) while server implements ADEPT protocol. Like RMSDK, libgourou has a client/server scheme. All platform specific functions (crypto, network...) has to be implemented in a client class (that derives from DRMProcessorClient) while server implements ADEPT protocol.
A reference implementation using cURL, OpenSSL and libzip is provided (in _utils_ directory). A reference implementation using Qt, OpenSSL and libzip is provided (in _utils_ directory).
Main functions to use from gourou::DRMProcessor are: Main fucntions to use from gourou::DRMProcessor are :
* Get an ePub from an ACSM file : _fulfill()_ and _download()_ * Get an ePub from an ACSM file : _fulfill()_ and _download()_
* Create a new device : _createDRMProcessor()_ * Create a new device : _createDRMProcessor()_
@ -18,43 +18,41 @@ Main functions to use from gourou::DRMProcessor are:
* Remove DRM : _removeDRM()_ * Remove DRM : _removeDRM()_
* Return loaned book : _returnLoan()_ * Return loaned book : _returnLoan()_
You can import configuration from (at least): You can import configuration from (at least) :
* Kobo device : .adept/device.xml, .adept/devicesalt and .adept/activation.xml * Kobo device : .adept/device.xml, .adept/devicesalt and .adept/activation.xml
* Bookeen device : .adobe-digital-editions/device.xml, root/devkey.bin and .adobe-digital-editions/activation.xml * Bookeen device : .adobe-digital-editions/device.xml, root/devkey.bin and .adobe-digital-editions/activation.xml
Or create a new one. Be careful: there is a limited number of devices that can be created by one account. Or create a new one. Be careful : there is a limited number of devices that can be created bye one account.
ePub are encrypted using a shared key: one account / multiple devices, so you can create and register a device into your computer and read downloaded (and encrypted) ePub file with your eReader configured using the same AdobeID account. ePub are encrypted using a shared key : one account / multiple devices, so you can create and register a device into your computer and read downloaded (and encrypted) ePub file with your eReader configured using the same AdobeID account.
For those who want to remove DRM without adept_remove, you can export your private key and import it within [Calibre](https://calibre-ebook.com/) an its DeDRM plugin. For those who wants to remove DRM without adept_remove, you can export your private key and import it within [Calibre](https://calibre-ebook.com/) an its DeDRM plugin.
Dependencies Dependencies
------------ ------------
For libgourou: For libgourou :
_externals_ : _externals_ :
* libpugixml * libpugixml
_internals_: _internals_ :
* uPDFParser * uPDFParser
For utils: For utils :
* libcurl * libcurl
* openssl * OpenSSL
* libzip * libzip
* libpugixml * libpugixml
External & utils dependencies has to be installed by your package manager (_apt_ for example). Internal libraries are automatically fetched and statically compiled during the first run.
Use _-dev_ flavours to get needed headers. When you update libgourou's repository, **don't forget to update internal libraries** with :
Internal libraries are automatically fetched and statically compiled during the first compilation.
When you update libgourou's repository, **don't forget to update internal libraries** with:
make update_lib make update_lib
@ -94,31 +92,31 @@ You can optionaly specify your .adept directory
export ADEPT_DIR=/home/XXX export ADEPT_DIR=/home/XXX
Then, use utils as following: Then, use utils as following :
You can import configuration from your eReader or create a new one with _utils/adept\_activate_: You can import configuration from your eReader or create a new one with _utils/adept\_activate_ :
./utils/adept_activate -u <AdobeID USERNAME> ./utils/adept_activate -u <AdobeID USERNAME>
Then a _/home/<user>/.config/adept_ directory is created with all configuration file Then a _/home/<user>/.config/adept_ directory is created with all configuration file
To download an ePub/PDF: To download an ePub/PDF :
./utils/acsmdownloader <ACSM_FILE> ./utils/acsmdownloader <ACSM_FILE>
To export your private key (for DeDRM software): To export your private key (for DeDRM software) :
./utils/acsmdownloader --export-private-key [-o adobekey_1.der] ./utils/acsmdownloader --export-private-key [-o adobekey_1.der]
To remove ADEPT DRM: To remove ADEPT DRM :
./utils/adept_remove <encryptedFile> ./utils/adept_remove <encryptedFile>
To list loaned books: To list loaned books :
./utils/adept_loan_mgt [-l] ./utils/adept_loan_mgt [-l]
To return a loaned book: To return a loaned book :
./utils/adept_loan_mgt -r <id> ./utils/adept_loan_mgt -r <id>
@ -126,12 +124,6 @@ To return a loaned book:
You can get utils full options description with -h or --help switch You can get utils full options description with -h or --help switch
Binary packages
---------------
Compiled version (and AppImage) of libgourou and utils are available in [Release page](https://forge.soutade.fr/soutade/libgourou/releases)
Docker Docker
------ ------
@ -158,18 +150,3 @@ Special thanks
* _Jens_ for all test samples and utils testing * _Jens_ for all test samples and utils testing
* _Milian_ for debug & code * _Milian_ for debug & code
* _Berwyn H_ for all test samples, feedbacks, patches and kind donation * _Berwyn H_ for all test samples, feedbacks, patches and kind donation
Donation
--------
https://www.paypal.com/donate/?hosted_button_id=JD3U6XMZCPHKN
Donators
--------
* _Berwyn H_
* _bwitt_
* _Ismail_
* _Radon_

View file

@ -30,7 +30,7 @@
namespace macaron { namespace macaron {
class Base64 { class Base64 {
public: public:
static std::string Encode(const std::string data) { static std::string Encode(const std::string data) {
@ -126,7 +126,7 @@ namespace macaron {
return ""; return "";
} }
}; };
} }

View file

@ -37,7 +37,7 @@
#define ACS_SERVER "http://adeactivate.adobe.com/adept" #define ACS_SERVER "http://adeactivate.adobe.com/adept"
#endif #endif
#define LIBGOUROU_VERSION "0.8.8" #define LIBGOUROU_VERSION "0.8.2"
namespace gourou namespace gourou
{ {
@ -67,11 +67,10 @@ namespace gourou
* @brief Fulfill ACSM file to server in order to retrieve ePub fulfillment item * @brief Fulfill ACSM file to server in order to retrieve ePub fulfillment item
* *
* @param ACSMFile Path of ACSMFile * @param ACSMFile Path of ACSMFile
* @param notify Notify server if requested by response
* *
* @return a FulfillmentItem if all is OK * @return a FulfillmentItem if all is OK
*/ */
FulfillmentItem* fulfill(const std::string& ACSMFile, bool notify=true); FulfillmentItem* fulfill(const std::string& ACSMFile);
/** /**
* @brief Once fulfilled, ePub file needs to be downloaded. * @brief Once fulfilled, ePub file needs to be downloaded.
@ -103,9 +102,8 @@ namespace gourou
* *
* @param loanID Loan ID received during fulfill * @param loanID Loan ID received during fulfill
* @param operatorURL URL of operator that loans this book * @param operatorURL URL of operator that loans this book
* @param notify Notify server if requested by response
*/ */
void returnLoan(const std::string& loanID, const std::string& operatorURL, bool notify=true); void returnLoan(const std::string& loanID, const std::string& operatorURL);
/** /**
* @brief Return default ADEPT directory (ie /home/<user>/.config/adept) * @brief Return default ADEPT directory (ie /home/<user>/.config/adept)
@ -235,9 +233,6 @@ 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 buildNotifyReq(pugi::xml_document& returnReq, pugi::xml_node& body);
void notifyServer(pugi::xml_node& notifyRoot);
void notifyServer(pugi::xml_document& fulfillReply);
std::string encryptedKeyFirstPass(pugi::xml_document& rightsDoc, const std::string& encryptedKey, const std::string& keyType); std::string encryptedKeyFirstPass(pugi::xml_document& rightsDoc, const std::string& encryptedKey, const std::string& keyType);
void decryptADEPTKey(pugi::xml_document& rightsDoc, unsigned char* decryptedKey, const unsigned char* encryptionKey=0, unsigned encryptionKeySize=0); void decryptADEPTKey(pugi::xml_document& rightsDoc, unsigned char* decryptedKey, const unsigned char* encryptionKey=0, unsigned encryptionKeySize=0);
void removeEPubDRM(const std::string& filenameIn, const std::string& filenameOut, const unsigned char* encryptionKey, unsigned encryptionKeySize); void removeEPubDRM(const std::string& filenameIn, const std::string& filenameOut, const unsigned char* encryptionKey, unsigned encryptionKeySize);

View file

@ -44,7 +44,7 @@ namespace gourou
* Some common utilities * Some common utilities
*/ */
#define ADOBE_ADEPT_NS "http://ns.adobe.com/adept" #define ADOBE_ADEPT_NS "http://ns.adobe.com/adept"
static const int SHA1_LEN = 20; static const int SHA1_LEN = 20;
static const int RSA_KEY_SIZE = 128; static const int RSA_KEY_SIZE = 128;
@ -120,7 +120,6 @@ namespace gourou
CLIENT_OSSL_ERROR, CLIENT_OSSL_ERROR,
CLIENT_CRYPT_ERROR, CLIENT_CRYPT_ERROR,
CLIENT_DIGEST_ERROR, CLIENT_DIGEST_ERROR,
CLIENT_HTTP_ERROR
}; };
enum DRM_REMOVAL_ERROR { enum DRM_REMOVAL_ERROR {
@ -135,15 +134,15 @@ namespace gourou
DRM_INVALID_USER DRM_INVALID_USER
}; };
#ifndef _NOEXCEPT #ifndef _NOEXCEPT
#if __STDC_VERSION__ >= 201112L #if __STDC_VERSION__ >= 201112L
# define _NOEXCEPT noexcept # define _NOEXCEPT noexcept
# define _NOEXCEPT_(x) noexcept(x) # define _NOEXCEPT_(x) noexcept(x)
#else #else
# define _NOEXCEPT throw() # define _NOEXCEPT throw()
# define _NOEXCEPT_(x) # define _NOEXCEPT_(x)
#endif #endif
#endif /* !_NOEXCEPT */ #endif /* !_NOEXCEPT */
/** /**
* Generic exception class * Generic exception class
@ -165,8 +164,8 @@ namespace gourou
Exception(const Exception& other) Exception(const Exception& other)
{ {
this->code = other.code; this->code = other.code;
this->line = other.line; this->line = line;
this->file = other.file; this->file = file;
this->fullmessage = strdup(other.fullmessage); this->fullmessage = strdup(other.fullmessage);
} }
@ -181,7 +180,7 @@ namespace gourou
private: private:
int code, line; int code, line;
const char* file; const char* message, *file;
char* fullmessage; char* fullmessage;
}; };
@ -236,7 +235,12 @@ namespace gourou
return ltrim(rtrim(s, t), t); return ltrim(rtrim(s, t), t);
} }
static inline pugi::xml_node getNode(const pugi::xml_node& root, const char* tagName, bool throwOnNull=true) /**
* @brief Extract text node from tag in document
* It can throw an exception if tag does not exists
* or just return an empty value
*/
static inline std::string extractTextElem(const pugi::xml_node& root, const char* tagName, bool throwOnNull=true)
{ {
pugi::xpath_node xpath_node = root.select_node(tagName); pugi::xpath_node xpath_node = root.select_node(tagName);
@ -245,23 +249,10 @@ namespace gourou
if (throwOnNull) if (throwOnNull)
EXCEPTION(GOUROU_TAG_NOT_FOUND, "Tag " << tagName << " not found"); EXCEPTION(GOUROU_TAG_NOT_FOUND, "Tag " << tagName << " not found");
return pugi::xml_node(); return "";
} }
return xpath_node.node(); pugi::xml_node node = xpath_node.node().first_child();
}
/**
* @brief Extract text node from tag in document
* It can throw an exception if tag does not exists
* or just return an empty value
*/
static inline std::string extractTextElem(const pugi::xml_node& root, const char* tagName, bool throwOnNull=true)
{
pugi::xml_node node = getNode(root, tagName, throwOnNull);
node = node.first_child();
if (!node) if (!node)
{ {
@ -275,30 +266,6 @@ namespace gourou
return trim(res); return trim(res);
} }
/**
* @brief Set text node of a tag in document
* It can throw an exception if tag does not exists
*/
static inline void setTextElem(const pugi::xml_node& root, const char* tagName,
const std::string& value, bool throwOnNull=true)
{
pugi::xml_node node = getNode(root, tagName, throwOnNull);
if (!node)
{
if (throwOnNull)
EXCEPTION(GOUROU_TAG_NOT_FOUND, "Text element for tag " << tagName << " not found");
return;
}
node = node.first_child();
if (!node)
node.append_child(pugi::node_pcdata).set_value(value.c_str());
else
node.set_value(value.c_str());
}
/** /**
* @brief Extract text attribute from tag in document * @brief Extract text attribute from tag in document
* It can throw an exception if attribute does not exists * It can throw an exception if attribute does not exists
@ -306,9 +273,17 @@ namespace gourou
*/ */
static inline std::string extractTextAttribute(const pugi::xml_node& root, const char* tagName, const char* attributeName, bool throwOnNull=true) static inline std::string extractTextAttribute(const pugi::xml_node& root, const char* tagName, const char* attributeName, bool throwOnNull=true)
{ {
pugi::xml_node node = getNode(root, tagName, throwOnNull); pugi::xpath_node xpath_node = root.select_node(tagName);
pugi::xml_attribute attr = node.attribute(attributeName); if (!xpath_node)
{
if (throwOnNull)
EXCEPTION(GOUROU_TAG_NOT_FOUND, "Tag " << tagName << " not found");
return "";
}
pugi::xml_attribute attr = xpath_node.node().attribute(attributeName);
if (!attr) if (!attr)
{ {

View file

@ -2,7 +2,7 @@
# uPDFParser # uPDFParser
if [ ! -d lib/updfparser ] ; then if [ ! -d lib/updfparser ] ; then
git clone https://forge.soutade.fr/soutade/uPDFParser.git lib/updfparser git clone git://soutade.fr/updfparser.git lib/updfparser
pushd lib/updfparser pushd lib/updfparser
make BUILD_STATIC=1 BUILD_SHARED=0 make BUILD_STATIC=1 BUILD_SHARED=0
popd popd

View file

@ -3,7 +3,7 @@
if [ ! -d lib/updfparser ] ; then if [ ! -d lib/updfparser ] ; then
echo "Some libraries are missing" echo "Some libraries are missing"
echo "You must run this script at the top of libgourou working direcotry." echo "You must run this script at the top of libgourou working direcotry."
echo "./scripts/setup.sh must be called first (make all)" echo "./lib/setup.sh must be called first (make all)"
exit 1 exit 1
fi fi

View file

@ -202,7 +202,7 @@ namespace gourou
char* tmp = new char[_length*2+1]; char* tmp = new char[_length*2+1];
for(int i=0; i<(int)_length; i++) for(int i=0; i<(int)_length; i++)
snprintf(&tmp[i*2], (_length-i)*2+1, "%02x", _data[i]); sprintf(&tmp[i*2], "%02x", _data[i]);
tmp[_length*2] = 0; tmp[_length*2] = 0;

View file

@ -94,12 +94,12 @@ namespace gourou
std::string FulfillmentItem::getMetadata(std::string name) std::string FulfillmentItem::getMetadata(std::string name)
{ {
// https://stackoverflow.com/questions/313970/how-to-convert-an-instance-of-stdstring-to-lower-case // https://stackoverflow.com/questions/313970/how-to-convert-an-instance-of-stdstring-to-lower-case
#if __STDC_VERSION__ >= 201112L #if __STDC_VERSION__ >= 201112L
std::transform(name.begin(), name.end(), name.begin(), std::transform(name.begin(), name.end(), name.begin(),
[](unsigned char c){ return std::tolower(c); }); [](unsigned char c){ return std::tolower(c); });
#else #else
std::transform(name.begin(), name.end(), name.begin(), tolower); std::transform(name.begin(), name.end(), name.begin(), tolower);
#endif #endif
name = std::string("dc:") + name; name = std::string("dc:") + name;
pugi::xpath_node path = metadatas.select_node(name.c_str()); pugi::xpath_node path = metadatas.select_node(name.c_str());

View file

@ -21,7 +21,6 @@
#include <sys/time.h> #include <sys/time.h>
#include <time.h> #include <time.h>
#include <vector> #include <vector>
#include <ctime>
#include <uPDFParser.h> #include <uPDFParser.h>
@ -71,15 +70,6 @@ namespace gourou
if (user) delete user; if (user) delete user;
} }
// function to parse a date or time string.
// https://www.geeksforgeeks.org/cpp/date-and-time-parsing-in-cpp/
static time_t parseDateTime(const char* datetimeString, const char* format)
{
struct tm tmStruct;
strptime(datetimeString, format, &tmStruct);
return mktime(&tmStruct);
}
DRMProcessor* DRMProcessor::createDRMProcessor(DRMProcessorClient* client, bool randomSerial, std::string dirName, DRMProcessor* DRMProcessor::createDRMProcessor(DRMProcessorClient* client, bool randomSerial, std::string dirName,
const std::string& hobbes, const std::string& ACSServer) const std::string& hobbes, const std::string& ACSServer)
{ {
@ -409,6 +399,30 @@ namespace gourou
} }
doOperatorAuth(operatorURL); doOperatorAuth(operatorURL);
// Add new operatorURL to list
pugi::xml_document activationDoc;
user->readActivation(activationDoc);
pugi::xml_node root;
pugi::xpath_node xpathRes = activationDoc.select_node("//adept:operatorURLList");
// Create adept:operatorURLList if it doesn't exists
if (!xpathRes)
{
xpathRes = activationDoc.select_node("/activationInfo");
root = xpathRes.node();
root = root.append_child("adept:operatorURLList");
root.append_attribute("xmlns:adept") = ADOBE_ADEPT_NS;
appendTextElem(root, "adept:user", user->getUUID());
}
else
root = xpathRes.node();
appendTextElem(root, "adept:operatorURL", operatorURL);
user->updateActivationFile(activationDoc);
} }
void DRMProcessor::buildFulfillRequest(pugi::xml_document& acsmDoc, pugi::xml_document& fulfillReq) void DRMProcessor::buildFulfillRequest(pugi::xml_document& acsmDoc, pugi::xml_document& fulfillReq)
@ -478,28 +492,10 @@ namespace gourou
appendTextElem(root, "adept:licenseURL", licenseURL); appendTextElem(root, "adept:licenseURL", licenseURL);
appendTextElem(root, "adept:certificate", certificate); appendTextElem(root, "adept:certificate", certificate);
// Add new operatorURL to list
xpathRes = activationDoc.select_node("//adept:operatorURLList");
// Create adept:operatorURLList if it doesn't exists
if (!xpathRes)
{
xpathRes = activationDoc.select_node("/activationInfo");
root = xpathRes.node();
root = root.append_child("adept:operatorURLList");
root.append_attribute("xmlns:adept") = ADOBE_ADEPT_NS;
appendTextElem(root, "adept:user", user->getUUID());
}
else
root = xpathRes.node();
appendTextElem(root, "adept:operatorURL", operatorURL);
user->updateActivationFile(activationDoc); user->updateActivationFile(activationDoc);
} }
FulfillmentItem* DRMProcessor::fulfill(const std::string& ACSMFile, bool notify) FulfillmentItem* DRMProcessor::fulfill(const std::string& ACSMFile)
{ {
if (!user->getPKCS12().length()) if (!user->getPKCS12().length())
EXCEPTION(FF_NOT_ACTIVATED, "Device not activated"); EXCEPTION(FF_NOT_ACTIVATED, "Device not activated");
@ -518,16 +514,6 @@ namespace gourou
GOUROU_LOG(INFO, "Fulfill " << ACSMFile); GOUROU_LOG(INFO, "Fulfill " << ACSMFile);
std::string expiration = extractTextElem(rootNode, "expiration", false);
if (expiration != "")
{
time_t expirationTime = parseDateTime(expiration.c_str(), "%Y-%m-%dT%H:%M:%S");
if (time(NULL) > expirationTime)
GOUROU_LOG(WARN, "WARNING: ACSM file expired (" << expiration << "), It may not work.");
}
// Build req file // Build req file
pugi::xml_document fulfillReq; pugi::xml_document fulfillReq;
@ -594,12 +580,7 @@ namespace gourou
fetchLicenseServiceCertificate(licenseURL, operatorURL); fetchLicenseServiceCertificate(licenseURL, operatorURL);
FulfillmentItem* item = new FulfillmentItem(fulfillReply, user); return new FulfillmentItem(fulfillReply, user);
if (notify)
notifyServer(fulfillReply);
return item;
} }
DRMProcessor::ITEM_TYPE DRMProcessor::download(FulfillmentItem* item, std::string path, bool resume) DRMProcessor::ITEM_TYPE DRMProcessor::download(FulfillmentItem* item, std::string path, bool resume)
@ -879,12 +860,6 @@ namespace gourou
std::string DRMProcessor::getDefaultAdeptDir(void) std::string DRMProcessor::getDefaultAdeptDir(void)
{ {
#ifndef DEFAULT_ADEPT_DIR #ifndef DEFAULT_ADEPT_DIR
const char* home = getenv("HOME");
if (home)
return home + std::string("/.config/adept/");
else
{
const char* user = getenv("USER"); const char* user = getenv("USER");
if (user && user[0]) if (user && user[0])
@ -893,14 +868,12 @@ namespace gourou
} }
else else
return LOCAL_ADEPT_DIR; return LOCAL_ADEPT_DIR;
}
#else #else
return DEFAULT_ADEPT_DIR "/"; return DEFAULT_ADEPT_DIR "/";
#endif #endif
} }
void DRMProcessor::returnLoan(const std::string& loanID, const std::string& operatorURL, void DRMProcessor::returnLoan(const std::string& loanID, const std::string& operatorURL)
bool notify)
{ {
pugi::xml_document returnReq; pugi::xml_document returnReq;
@ -908,71 +881,7 @@ namespace gourou
buildReturnReq(returnReq, loanID, operatorURL); buildReturnReq(returnReq, loanID, operatorURL);
ByteArray replyData = sendRequest(returnReq, operatorURL + "/LoanReturn"); sendRequest(returnReq, operatorURL + "/LoanReturn");
pugi::xml_document fulfillReply;
fulfillReply.load_string((const char*)replyData.data());
if (notify)
notifyServer(fulfillReply);
}
void DRMProcessor::buildNotifyReq(pugi::xml_document& returnReq, pugi::xml_node& body)
{
pugi::xml_node decl = returnReq.append_child(pugi::node_declaration);
decl.append_attribute("version") = "1.0";
pugi::xml_node root = returnReq.append_child("adept:notification");
root.append_attribute("xmlns:adept") = ADOBE_ADEPT_NS;
appendTextElem(root, "adept:user", user->getUUID());
appendTextElem(root, "adept:device", user->getDeviceUUID());
body = root.append_copy(body);
body.append_attribute("xmlns") = ADOBE_ADEPT_NS;
addNonce(root);
signNode(root);
}
void DRMProcessor::notifyServer(pugi::xml_node& notifyRoot)
{
std::string notifyUrl = extractTextElem(notifyRoot, "//notifyURL", false);
pugi::xml_node notifyBody = getNode(notifyRoot, "//body", false);
if (notifyUrl == "")
{
GOUROU_LOG(INFO, "No notify URL");
return;
}
if (!notifyBody)
{
GOUROU_LOG(INFO, "No notify body");
return;
}
pugi::xml_document notifyReq;
buildNotifyReq(notifyReq, notifyBody);
sendRequest(notifyReq, notifyUrl);
}
void DRMProcessor::notifyServer(pugi::xml_document& fulfillReply)
{
pugi::xpath_node_set notifySet = fulfillReply.select_nodes("//notify");
if (notifySet.empty())
{
GOUROU_LOG(DEBUG, "No notify request");
return;
}
for (pugi::xpath_node_set::const_iterator it = notifySet.begin(); it != notifySet.end(); ++it)
{
pugi::xml_node notifyRoot = it->node();
notifyServer(notifyRoot);
}
} }
ByteArray DRMProcessor::encryptWithDeviceKey(const unsigned char* data, unsigned int len) ByteArray DRMProcessor::encryptWithDeviceKey(const unsigned char* data, unsigned int len)
@ -1342,9 +1251,8 @@ namespace gourou
uPDFParser::Integer* ebxVersion; uPDFParser::Integer* ebxVersion;
std::vector<uPDFParser::Object*> objects = parser.objects(); std::vector<uPDFParser::Object*> objects = parser.objects();
std::vector<uPDFParser::Object*>::iterator it, ebxIt; std::vector<uPDFParser::Object*>::iterator it;
std::vector<uPDFParser::Object*>::reverse_iterator rIt; std::vector<uPDFParser::Object*>::reverse_iterator rIt;
std::vector<uPDFParser::Object*> ebxObjects;
unsigned char decryptedKey[16]; unsigned char decryptedKey[16];
int ebxId; int ebxId;
@ -1403,7 +1311,7 @@ namespace gourou
if (object->objectId() == ebxId) if (object->objectId() == ebxId)
{ {
ebxObjects.push_back(object); // object->deleteKey("Filter");
continue; continue;
} }
@ -1513,33 +1421,6 @@ namespace gourou
} }
} }
/* Delete objects that reference EBX objects, except in trailer */
for(it = objects.begin(); it != objects.end(); it++)
{
uPDFParser::Object* object = *it;
if (object->hasKey("Encrypt") && (*object)["Encrypt"]->type() == uPDFParser::DataType::REFERENCE)
{
uPDFParser::Reference* encrypt = (uPDFParser::Reference*)(*object)["Encrypt"];
/* Delete EBX objects */
for(ebxIt = ebxObjects.begin(); ebxIt != ebxObjects.end(); ebxIt++)
{
if (encrypt->value() == (*ebxIt)->objectId())
{
GOUROU_LOG(ERROR, "Delete stream id " << object->objectId());
parser.removeObject(object);
break;
}
}
}
}
/* Delete EBX objects */
for(it = ebxObjects.begin(); it != ebxObjects.end(); it++)
parser.removeObject(*it);
uPDFParser::Object& trailer = parser.getTrailer(); uPDFParser::Object& trailer = parser.getTrailer();
trailer.deleteKey("Encrypt"); trailer.deleteKey("Encrypt");

View file

@ -29,13 +29,24 @@ namespace gourou
if (!node) if (!node)
EXCEPTION(FFI_INVALID_LOAN_TOKEN, "No loanToken element in document"); EXCEPTION(FFI_INVALID_LOAN_TOKEN, "No loanToken element in document");
node = doc.select_node("/envelope/fulfillmentResult/fulfillment").node(); node = doc.select_node("/envelope/loanToken/loan").node();
if (node) if (node)
properties["id"] = node.first_child().value(); properties["id"] = node.first_child().value();
else else
{ {
EXCEPTION(FFI_INVALID_LOAN_TOKEN, "No fulfillment element in document"); node = doc.select_node("/envelope/fulfillmentResult/resourceItemInfo/licenseToken/permissions/display/loan").node();
if (node)
properties["id"] = node.first_child().value();
else
{
node = doc.select_node("/envelope/fulfillmentResult/resourceItemInfo/licenseToken/permissions/play/loan").node();
if (node)
properties["id"] = node.first_child().value();
else
EXCEPTION(FFI_INVALID_LOAN_TOKEN, "No loanToken/loan element in document");
}
} }
node = doc.select_node("/envelope/loanToken/operatorURL").node(); node = doc.select_node("/envelope/loanToken/operatorURL").node();

View file

@ -6,10 +6,9 @@ TARGETS=$(TARGET_BINARIES) launcher
MAN_PAGES=acsmdownloader adept_activate adept_remove adept_loan_mgt MAN_PAGES=acsmdownloader adept_activate adept_remove adept_loan_mgt
CXXFLAGS=-Wall -fPIC -I$(ROOT)/include -fmacro-prefix-map=$(ROOT)/= -fdata-sections -ffunction-sections CXXFLAGS=-Wall -fPIC -I$(ROOT)/include
STATIC_DEP= STATIC_DEP=
# LDFLAGS += -Wl,--gc-sections
LDFLAGS += -L$(ROOT) -lcrypto -lzip -lz -lcurl -lpugixml LDFLAGS += -L$(ROOT) -lcrypto -lzip -lz -lcurl -lpugixml
ifneq ($(STATIC_UTILS),) ifneq ($(STATIC_UTILS),)
@ -32,7 +31,7 @@ COMMON_LIB = utils.a
all: $(TARGETS) all: $(TARGETS)
${COMMON_LIB}: $(COMMON_DEPS) ${COMMON_LIB}: $(COMMON_DEPS)
$(CXX) $(CXXFLAGS) $(COMMON_DEPS) -c $(CXX) $(CXXFLAGS) $(COMMON_DEPS) $(LDFLAGS) -c
$(AR) crs $@ $(COMMON_OBJECTS) $(AR) crs $@ $(COMMON_OBJECTS)
%: %.cpp $(COMMON_LIB) $(STATIC_DEP) %: %.cpp $(COMMON_LIB) $(STATIC_DEP)

View file

@ -46,7 +46,6 @@ static bool exportPrivateKey = false;
static const char* outputFile = 0; static const char* outputFile = 0;
static const char* outputDir = 0; static const char* outputDir = 0;
static bool resume = false; static bool resume = false;
static bool notify = true;
class ACSMDownloader class ACSMDownloader
@ -64,30 +63,29 @@ public:
if (exportPrivateKey) if (exportPrivateKey)
{ {
std::string filename; std::string filename;
if (outputFile) if (!outputFile)
filename = outputFile;
else
{
filename = std::string("Adobe_PrivateLicenseKey--") + user->getUsername() + ".der"; filename = std::string("Adobe_PrivateLicenseKey--") + user->getUsername() + ".der";
else
filename = outputFile;
if (outputDir) if (outputDir)
{
if (!fileExists(outputDir))
mkpath(outputDir);
filename = std::string(outputDir) + "/" + filename; filename = std::string(outputDir) + "/" + filename;
} }
createPath(filename.c_str());
processor.exportPrivateLicenseKey(filename); processor.exportPrivateLicenseKey(filename);
std::cout << "Private license key exported to " << filename << std::endl; std::cout << "Private license key exported to " << filename << std::endl;
} }
else else
{ {
gourou::FulfillmentItem* item = processor.fulfill(acsmFile, notify); gourou::FulfillmentItem* item = processor.fulfill(acsmFile);
std::string filename; std::string filename;
if (outputFile) if (!outputFile)
filename = outputFile;
else
{ {
filename = item->getMetadata("title"); filename = item->getMetadata("title");
if (filename == "") if (filename == "")
@ -97,13 +95,18 @@ public:
// Remove invalid characters // Remove invalid characters
std::replace(filename.begin(), filename.end(), '/', '_'); std::replace(filename.begin(), filename.end(), '/', '_');
} }
}
else
filename = outputFile;
if (outputDir) if (outputDir)
{
if (!fileExists(outputDir))
mkpath(outputDir);
filename = std::string(outputDir) + "/" + filename; filename = std::string(outputDir) + "/" + filename;
} }
createPath(filename.c_str());
gourou::DRMProcessor::ITEM_TYPE type = processor.download(item, filename, resume); gourou::DRMProcessor::ITEM_TYPE type = processor.download(item, filename, resume);
if (!outputFile) if (!outputFile)
@ -182,12 +185,11 @@ static void usage(const char* cmd)
std::cout << basename((char*)cmd) << " download EPUB file from ACSM request file" << std::endl << std::endl; std::cout << basename((char*)cmd) << " download EPUB file from ACSM request file" << std::endl << std::endl;
std::cout << "Usage: " << basename((char*)cmd) << " [OPTIONS] file.acsm" << std::endl << std::endl; std::cout << "Usage: " << basename((char*)cmd) << " [OPTIONS] file.acsm" << std::endl << std::endl;
std::cout << "Global Options:" << std::endl; std::cout << "Global Options:" << std::endl;
std::cout << " " << "-O|--output-dir" << "\t" << "Optional output directory were to put result (default ./) (not compatible with -o)" << std::endl; std::cout << " " << "-O|--output-dir" << "\t" << "Optional output directory were to put result (default ./)" << std::endl;
std::cout << " " << "-o|--output-file" << "\t" << "Optional output filename (default <title.(epub|pdf|der)>) (not compatible with -O)" << std::endl; std::cout << " " << "-o|--output-file" << "\t" << "Optional output filename (default <title.(epub|pdf|der)>)" << std::endl;
std::cout << " " << "-f|--acsm-file" << "\t" << "Backward compatibility: ACSM request file for epub download" << std::endl; std::cout << " " << "-f|--acsm-file" << "\t" << "Backward compatibility: ACSM request file for epub download" << std::endl;
std::cout << " " << "-e|--export-private-key"<< "\t" << "Export private key in DER format" << std::endl; std::cout << " " << "-e|--export-private-key"<< "\t" << "Export private key in DER format" << std::endl;
std::cout << " " << "-r|--resume" << "\t\t" << "Try to resume download (in case of previous failure)" << std::endl; std::cout << " " << "-r|--resume" << "\t\t" << "Try to resume download (in case of previous failure)" << std::endl;
std::cout << " " << "-N|--no-notify" << "\t\t" << "Don't notify server, even if requested" << std::endl;
std::cout << " " << "-v|--verbose" << "\t\t" << "Increase verbosity, can be set multiple times" << std::endl; std::cout << " " << "-v|--verbose" << "\t\t" << "Increase verbosity, can be set multiple times" << std::endl;
std::cout << " " << "-V|--version" << "\t\t" << "Display libgourou version" << std::endl; std::cout << " " << "-V|--version" << "\t\t" << "Display libgourou version" << std::endl;
std::cout << " " << "-h|--help" << "\t\t" << "This help" << std::endl; std::cout << " " << "-h|--help" << "\t\t" << "This help" << std::endl;
@ -230,14 +232,13 @@ int main(int argc, char** argv)
{"acsm-file", required_argument, 0, 'f' }, {"acsm-file", required_argument, 0, 'f' },
{"export-private-key",no_argument, 0, 'e' }, {"export-private-key",no_argument, 0, 'e' },
{"resume", no_argument, 0, 'r' }, {"resume", no_argument, 0, 'r' },
{"no-notify", no_argument, 0, 'N' },
{"verbose", no_argument, 0, 'v' }, {"verbose", no_argument, 0, 'v' },
{"version", no_argument, 0, 'V' }, {"version", no_argument, 0, 'V' },
{"help", no_argument, 0, 'h' }, {"help", no_argument, 0, 'h' },
{0, 0, 0, 0 } {0, 0, 0, 0 }
}; };
c = getopt_long(argc, argv, "D:d:a:k:O:o:f:erNvVh", c = getopt_long(argc, argv, "D:d:a:k:O:o:f:ervVh",
long_options, &option_index); long_options, &option_index);
if (c == -1) if (c == -1)
break; break;
@ -275,9 +276,6 @@ int main(int argc, char** argv)
case 'r': case 'r':
resume = true; resume = true;
break; break;
case 'N':
notify = false;
break;
case 'v': case 'v':
verbose++; verbose++;
break; break;
@ -305,12 +303,6 @@ int main(int argc, char** argv)
return -1; return -1;
} }
if (outputDir && outputFile)
{
std::cout << "Error : you cannot use both -o and -O" << std::endl;
return -1;
}
ACSMDownloader downloader; ACSMDownloader downloader;
int i; int i;
@ -341,7 +333,7 @@ int main(int argc, char** argv)
} }
else else
{ {
if (!pathExists(acsmFile)) if (!fileExists(acsmFile))
{ {
std::cout << "Error : " << acsmFile << " doesn't exists" << std::endl; std::cout << "Error : " << acsmFile << " doesn't exists" << std::endl;
ret = -1; ret = -1;

View file

@ -240,7 +240,7 @@ int main(int argc, char** argv)
if (_outputDir[0] == '.' || _outputDir[0] != '/') if (_outputDir[0] == '.' || _outputDir[0] != '/')
{ {
// realpath doesn't works if file/dir doesn't exists // realpath doesn't works if file/dir doesn't exists
if (pathExists(_outputDir)) if (fileExists(_outputDir))
outputDir = strdup(realpath(_outputDir, 0)); outputDir = strdup(realpath(_outputDir, 0));
else else
outputDir = strdup(abspath(_outputDir)); outputDir = strdup(abspath(_outputDir));
@ -250,7 +250,7 @@ int main(int argc, char** argv)
} }
std::string pass; std::string pass;
if (pathExists(outputDir)) if (fileExists(outputDir))
{ {
int key; int key;

View file

@ -52,7 +52,6 @@ static const char* devicekeyFile = "devicesalt";
static bool list = false; static bool list = false;
static const char* returnID = 0; static const char* returnID = 0;
static const char* deleteID = 0; static const char* deleteID = 0;
static bool notify = true;
struct Loan struct Loan
{ {
@ -109,7 +108,7 @@ private:
std::string loanDir = std::string(adeptDir) + std::string("/") + LOANS_DIR; std::string loanDir = std::string(adeptDir) + std::string("/") + LOANS_DIR;
if (!pathExists(loanDir.c_str())) if (!fileExists(loanDir.c_str()))
return; return;
dp = opendir (loanDir.c_str()); dp = opendir (loanDir.c_str());
@ -183,13 +182,8 @@ private:
loan->bookName = node.first_child().value(); loan->bookName = node.first_child().value();
struct tm tm; struct tm tm;
#ifdef __ANDROID__
res = strptime(loan->validity.c_str(), "%Y-%m-%dT%H:%M:%S%z", &tm);
#else
res = strptime(loan->validity.c_str(), "%Y-%m-%dT%H:%M:%S%Z", &tm); res = strptime(loan->validity.c_str(), "%Y-%m-%dT%H:%M:%S%Z", &tm);
#endif if (*res == 0)
if (res != NULL && *res == 0)
{ {
if (mktime(&tm) <= time(NULL)) if (mktime(&tm) <= time(NULL))
loan->validity = " (Expired)"; loan->validity = " (Expired)";
@ -229,12 +223,7 @@ private:
maxSizeBookName = loan->bookName.size(); maxSizeBookName = loan->bookName.size();
} }
/* Manage empty names */ if (maxSizeBookName > MAX_SIZE_BOOK_NAME)
if (maxSizeBookName == 0)
maxSizeBookName = sizeof("No name ")-1;
else if (maxSizeBookName < 4)
maxSizeBookName = 4;
else if (maxSizeBookName > MAX_SIZE_BOOK_NAME)
maxSizeBookName = MAX_SIZE_BOOK_NAME; maxSizeBookName = MAX_SIZE_BOOK_NAME;
else if ((maxSizeBookName % 2)) else if ((maxSizeBookName % 2))
maxSizeBookName++; maxSizeBookName++;
@ -281,9 +270,7 @@ private:
std::cout << kv.first; std::cout << kv.first;
std::cout << " "; std::cout << " ";
if (loan->bookName.size() == 0) if (loan->bookName.size() > MAX_SIZE_BOOK_NAME)
bookName = std::string("No name ");
else if (loan->bookName.size() > MAX_SIZE_BOOK_NAME)
bookName = std::string(loan->bookName.c_str(), MAX_SIZE_BOOK_NAME); bookName = std::string(loan->bookName.c_str(), MAX_SIZE_BOOK_NAME);
else else
bookName = loan->bookName; bookName = loan->bookName;
@ -309,7 +296,7 @@ private:
return; return;
} }
processor.returnLoan(loan->id, loan->operatorURL, notify); processor.returnLoan(loan->id, loan->operatorURL);
deleteID = returnID; deleteID = returnID;
if (deleteLoan(false)) if (deleteLoan(false))
@ -355,7 +342,6 @@ static void usage(const char* cmd)
std::cout << " " << "-l|--list" << "\t\t" << "List all loaned books" << std::endl; std::cout << " " << "-l|--list" << "\t\t" << "List all loaned books" << std::endl;
std::cout << " " << "-r|--return" << "\t\t" << "Return a loaned book" << std::endl; std::cout << " " << "-r|--return" << "\t\t" << "Return a loaned book" << std::endl;
std::cout << " " << "-d|--delete" << "\t\t" << "Delete a loan entry without returning it" << std::endl; std::cout << " " << "-d|--delete" << "\t\t" << "Delete a loan entry without returning it" << std::endl;
std::cout << " " << "-N|--no-notify" << "\t\t" << "Don't notify server, even if requested" << std::endl;
std::cout << " " << "-v|--verbose" << "\t\t" << "Increase verbosity, can be set multiple times" << std::endl; std::cout << " " << "-v|--verbose" << "\t\t" << "Increase verbosity, can be set multiple times" << std::endl;
std::cout << " " << "-V|--version" << "\t\t" << "Display libgourou version" << std::endl; std::cout << " " << "-V|--version" << "\t\t" << "Display libgourou version" << std::endl;
std::cout << " " << "-h|--help" << "\t\t" << "This help" << std::endl; std::cout << " " << "-h|--help" << "\t\t" << "This help" << std::endl;
@ -389,7 +375,6 @@ int main(int argc, char** argv)
{"list", no_argument, 0, 'l' }, {"list", no_argument, 0, 'l' },
{"return", no_argument, 0, 'r' }, {"return", no_argument, 0, 'r' },
{"delete", no_argument, 0, 'd' }, {"delete", no_argument, 0, 'd' },
{"no-notify", no_argument, 0, 'N' },
{"verbose", no_argument, 0, 'v' }, {"verbose", no_argument, 0, 'v' },
{"version", no_argument, 0, 'V' }, {"version", no_argument, 0, 'V' },
{"help", no_argument, 0, 'h' }, {"help", no_argument, 0, 'h' },
@ -417,9 +402,6 @@ int main(int argc, char** argv)
deleteID = optarg; deleteID = optarg;
actions++; actions++;
break; break;
case 'N':
notify = false;
break;
case 'v': case 'v':
verbose++; verbose++;
break; break;

View file

@ -81,13 +81,16 @@ public:
gourou::DRMProcessor processor(&client, deviceFile, activationFile, devicekeyFile); gourou::DRMProcessor processor(&client, deviceFile, activationFile, devicekeyFile);
std::string filename; std::string filename;
if (outputFile) if (!outputFile)
filename = outputFile;
else
{
filename = std::string(inputFile); filename = std::string(inputFile);
else
filename = outputFile;
if (outputDir) if (outputDir)
{
if (!fileExists(outputDir))
mkpath(outputDir);
filename = std::string(outputDir) + "/" + filename; filename = std::string(outputDir) + "/" + filename;
} }
@ -100,8 +103,6 @@ public:
EXCEPTION(gourou::DRM_FORMAT_NOT_SUPPORTED, "Unsupported file format of " << filename); EXCEPTION(gourou::DRM_FORMAT_NOT_SUPPORTED, "Unsupported file format of " << filename);
} }
createPath(filename.c_str());
if (inputFile != filename) if (inputFile != filename)
{ {
unlink(filename.c_str()); unlink(filename.c_str());
@ -146,8 +147,8 @@ static void usage(const char* cmd)
std::cout << "Usage: " << basename((char*)cmd) << " [OPTIONS] file(.epub|pdf)" << std::endl << std::endl; std::cout << "Usage: " << basename((char*)cmd) << " [OPTIONS] file(.epub|pdf)" << std::endl << std::endl;
std::cout << "Global Options:" << std::endl; std::cout << "Global Options:" << std::endl;
std::cout << " " << "-O|--output-dir" << "\t" << "Optional output directory were to put result (default ./) (not compatible with -o)" << std::endl; std::cout << " " << "-O|--output-dir" << "\t" << "Optional output directory were to put result (default ./)" << std::endl;
std::cout << " " << "-o|--output-file" << "\t" << "Optional output filename (default inplace DRM removal>) (not compatible with -O)" << std::endl; std::cout << " " << "-o|--output-file" << "\t" << "Optional output filename (default inplace DRM removal>)" << std::endl;
std::cout << " " << "-f|--input-file" << "\t" << "Backward compatibility: EPUB/PDF file to process" << std::endl; std::cout << " " << "-f|--input-file" << "\t" << "Backward compatibility: EPUB/PDF file to process" << std::endl;
std::cout << " " << "-v|--verbose" << "\t\t" << "Increase verbosity, can be set multiple times" << std::endl; std::cout << " " << "-v|--verbose" << "\t\t" << "Increase verbosity, can be set multiple times" << std::endl;
std::cout << " " << "-V|--version" << "\t\t" << "Display libgourou version" << std::endl; std::cout << " " << "-V|--version" << "\t\t" << "Display libgourou version" << std::endl;
@ -258,12 +259,6 @@ int main(int argc, char** argv)
return -1; return -1;
} }
if (outputDir && outputFile)
{
std::cout << "Error : you cannot use both -o and -O" << std::endl;
return -1;
}
ADEPTRemove remover; ADEPTRemove remover;
int i; int i;

View file

@ -30,7 +30,6 @@
#include <algorithm> #include <algorithm>
#include <cctype> #include <cctype>
#include <locale> #include <locale>
#include <stdlib.h>
#define OPENSSL_NO_DEPRECATED 1 #define OPENSSL_NO_DEPRECATED 1
@ -49,42 +48,18 @@
#include <libgourou_common.h> #include <libgourou_common.h>
#include "drmprocessorclientimpl.h" #include "drmprocessorclientimpl.h"
static int error_cb(const char *str, size_t len, void *u)
{
std::cout << str << std::endl;
return 0;
}
DRMProcessorClientImpl::DRMProcessorClientImpl(): DRMProcessorClientImpl::DRMProcessorClientImpl():
legacy(0), deflt(0) legacy(0), deflt(0)
{ {
#if OPENSSL_VERSION_MAJOR >= 3 #if OPENSSL_VERSION_MAJOR >= 3
legacy = OSSL_PROVIDER_load(NULL, "legacy"); legacy = OSSL_PROVIDER_load(NULL, "legacy");
if (!legacy) if (!legacy)
{
ERR_print_errors_cb(error_cb, NULL);
EXCEPTION(gourou::CLIENT_OSSL_ERROR, "Error, OpenSSL legacy provider not available"); EXCEPTION(gourou::CLIENT_OSSL_ERROR, "Error, OpenSSL legacy provider not available");
}
deflt = OSSL_PROVIDER_load(NULL, "default"); deflt = OSSL_PROVIDER_load(NULL, "default");
if (!deflt) if (!deflt)
EXCEPTION(gourou::CLIENT_OSSL_ERROR, "Error, OpenSSL default provider not available"); EXCEPTION(gourou::CLIENT_OSSL_ERROR, "Error, OpenSSL default provider not available");
OSSL_PROVIDER_load(NULL, "base");
#endif #endif
#ifdef WIN32
strcpy(cookiejar, "C:\\temp\\libgourou_cookie_jar_XXXXXX");
#else
strcpy(cookiejar, "/tmp/libgourou_cookie_jar_XXXXXX");
#endif
int fd = mkstemp(cookiejar);
if (fd >= 0)
close(fd);
else
{
EXCEPTION(gourou::CLIENT_FILE_ERROR, "mkstemp error");
}
} }
DRMProcessorClientImpl::~DRMProcessorClientImpl() DRMProcessorClientImpl::~DRMProcessorClientImpl()
@ -96,8 +71,6 @@ DRMProcessorClientImpl::~DRMProcessorClientImpl()
if (deflt) if (deflt)
OSSL_PROVIDER_unload(deflt); OSSL_PROVIDER_unload(deflt);
#endif #endif
unlink(cookiejar);
} }
/* Digest interface */ /* Digest interface */
@ -147,7 +120,6 @@ void DRMProcessorClientImpl::randBytes(unsigned char* bytesOut, unsigned int len
#define HTTP_REQ_MAX_RETRY 5 #define HTTP_REQ_MAX_RETRY 5
#define DISPLAY_THRESHOLD 10*1024 // Threshold to display download progression #define DISPLAY_THRESHOLD 10*1024 // Threshold to display download progression
static unsigned downloadedBytes; static unsigned downloadedBytes;
static int lastPercent = -1;
static int downloadProgress(void *clientp, curl_off_t dltotal, curl_off_t dlnow, static int downloadProgress(void *clientp, curl_off_t dltotal, curl_off_t dlnow,
curl_off_t ultotal, curl_off_t ulnow) curl_off_t ultotal, curl_off_t ulnow)
@ -159,11 +131,7 @@ static int downloadProgress(void *clientp, curl_off_t dltotal, curl_off_t dlnow,
if (dltotal) if (dltotal)
percent = (dlnow * 100) / dltotal; percent = (dlnow * 100) / dltotal;
if (lastPercent != percent)
{
std::cout << "\rDownload " << percent << "%" << std::flush; std::cout << "\rDownload " << percent << "%" << std::flush;
lastPercent = percent;
}
} }
return 0; return 0;
@ -259,7 +227,6 @@ std::string DRMProcessorClientImpl::sendHTTPRequest(const std::string& URL, cons
} }
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, list); curl_easy_setopt(curl, CURLOPT_HTTPHEADER, list);
curl_easy_setopt(curl, CURLOPT_COOKIEJAR, cookiejar);
if (POSTData.size()) if (POSTData.size())
{ {
@ -284,7 +251,6 @@ std::string DRMProcessorClientImpl::sendHTTPRequest(const std::string& URL, cons
curl_easy_setopt(curl, CURLOPT_XFERINFOFUNCTION, downloadProgress); curl_easy_setopt(curl, CURLOPT_XFERINFOFUNCTION, downloadProgress);
curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 0); curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 0);
lastPercent = -1;
for (int i=0; i<HTTP_REQ_MAX_RETRY; i++) for (int i=0; i<HTTP_REQ_MAX_RETRY; i++)
{ {
@ -320,18 +286,11 @@ std::string DRMProcessorClientImpl::sendHTTPRequest(const std::string& URL, cons
} }
curl_slist_free_all(list); curl_slist_free_all(list);
long http_code = 400;
curl_easy_getinfo (curl, CURLINFO_RESPONSE_CODE, &http_code);
curl_easy_cleanup(curl); curl_easy_cleanup(curl);
if (res != CURLE_OK) if (res != CURLE_OK)
EXCEPTION(gourou::CLIENT_NETWORK_ERROR, "Error " << curl_easy_strerror(res)); EXCEPTION(gourou::CLIENT_NETWORK_ERROR, "Error " << curl_easy_strerror(res));
if (http_code >= 400)
EXCEPTION(gourou::CLIENT_HTTP_ERROR, "HTTP Error code " << http_code);
if ((downloadedBytes >= DISPLAY_THRESHOLD || replyData.size() >= DISPLAY_THRESHOLD) && if ((downloadedBytes >= DISPLAY_THRESHOLD || replyData.size() >= DISPLAY_THRESHOLD) &&
gourou::logLevel >= gourou::LG_LOG_WARN) gourou::logLevel >= gourou::LG_LOG_WARN)
std::cout << std::endl; std::cout << std::endl;
@ -355,39 +314,11 @@ void DRMProcessorClientImpl::padWithPKCS1(unsigned char* out, unsigned int outLe
0x00 0x01 0xff * n 0x00 dataIn 0x00 0x01 0xff * n 0x00 dataIn
*/ */
memset(out, 0xFF, outLength - inLength - 1); memset(out, 0xFF, outLength);
out[0] = 0x0; out[0] = 0x0;
out[1] = 0x1; out[1] = 0x1;
out[outLength - inLength - 1] = 0x00; out[outLength - inLength - 1] = 0x00;
memcpy(&out[outLength - inLength], in, inLength);
}
void DRMProcessorClientImpl::padWithPKCS1Type2(unsigned char* out, unsigned int outLength,
const unsigned char* in, unsigned int inLength)
{
if (outLength < (inLength + 3))
EXCEPTION(gourou::CLIENT_RSA_ERROR, "Not enough space for PKCS1 padding");
/*
PKCS1v5 type 2 Padding is :
0x00 0x02 0xXX * n 0x00 dataIn
XX is random non zero data
*/
RAND_bytes(&out[2], outLength - inLength - 1);
for(unsigned int i=2; i<outLength - inLength - 1; i++)
{
while (out[i] == 0)
RAND_bytes(&out[i], 1);
}
out[0] = 0x0;
out[1] = 0x2;
out[outLength - inLength - 1] = 0x00;
memcpy(&out[outLength - inLength], in, inLength); memcpy(&out[outLength - inLength], in, inLength);
} }
@ -479,45 +410,33 @@ void DRMProcessorClientImpl::RSAPublicEncrypt(const unsigned char* RSAKey, unsig
unsigned char* res) unsigned char* res)
{ {
size_t outlen; size_t outlen;
unsigned char* tmp;
X509 * x509 = d2i_X509(0, &RSAKey, RSAKeyLength); X509 * x509 = d2i_X509(0, &RSAKey, RSAKeyLength);
if (!x509) if (!x509)
EXCEPTION(gourou::CLIENT_INVALID_CERTIFICATE, "Invalid certificate"); EXCEPTION(gourou::CLIENT_INVALID_CERTIFICATE, "Invalid certificate");
EVP_PKEY_CTX *ctx; EVP_PKEY_CTX *ctx;
EVP_PKEY * pkey = X509_get_pubkey(x509); EVP_PKEY * evpKey = X509_get_pubkey(x509);
if (!pkey) if (!evpKey)
EXCEPTION(gourou::CLIENT_NO_PUB_KEY, "No public key in certificate"); EXCEPTION(gourou::CLIENT_NO_PUB_KEY, "No public key in certificate");
ctx = EVP_PKEY_CTX_new(pkey, NULL); ctx = EVP_PKEY_CTX_new(evpKey, NULL);
if (EVP_PKEY_encrypt_init(ctx) <= 0) if (EVP_PKEY_encrypt_init(ctx) <= 0)
EXCEPTION(gourou::CLIENT_RSA_ERROR, ERR_error_string(ERR_get_error(), NULL)); EXCEPTION(gourou::CLIENT_RSA_ERROR, ERR_error_string(ERR_get_error(), NULL));
if (EVP_PKEY_CTX_set_rsa_padding(ctx, RSA_NO_PADDING) <= 0) if (EVP_PKEY_CTX_set_rsa_padding(ctx, RSA_PKCS1_PADDING) <= 0)
EXCEPTION(gourou::CLIENT_RSA_ERROR, ERR_error_string(ERR_get_error(), NULL)); EXCEPTION(gourou::CLIENT_RSA_ERROR, ERR_error_string(ERR_get_error(), NULL));
outlen = EVP_PKEY_get_size(pkey); int ret = EVP_PKEY_encrypt(ctx, res, &outlen, data, dataLength);
tmp = (unsigned char*)malloc(outlen);
/*
PKCS1 functions are no more exported.
Some OpenSSL libraries still use type 1
*/
padWithPKCS1Type2(tmp, outlen, data, dataLength);
int ret = EVP_PKEY_encrypt(ctx, res, &outlen, tmp, outlen);
EVP_PKEY_CTX_free(ctx); EVP_PKEY_CTX_free(ctx);
free(tmp);
EVP_PKEY_free(pkey);
if (ret < 0) if (ret < 0)
EXCEPTION(gourou::CLIENT_RSA_ERROR, ERR_error_string(ERR_get_error(), NULL)); EXCEPTION(gourou::CLIENT_RSA_ERROR, ERR_error_string(ERR_get_error(), NULL));
EVP_PKEY_free(evpKey);
} }
void* DRMProcessorClientImpl::generateRSAKey(int keyLengthBits) void* DRMProcessorClientImpl::generateRSAKey(int keyLengthBits)
@ -531,6 +450,7 @@ void* DRMProcessorClientImpl::generateRSAKey(int keyLengthBits)
EVP_PKEY_keygen_init(ctx); EVP_PKEY_keygen_init(ctx);
EVP_PKEY_CTX_set_rsa_keygen_bits(ctx, keyLengthBits); EVP_PKEY_CTX_set_rsa_keygen_bits(ctx, keyLengthBits);
EVP_PKEY_CTX_set_rsa_padding(ctx, RSA_PKCS1_PADDING);
EVP_PKEY_CTX_set1_rsa_keygen_pubexp(ctx, bn); EVP_PKEY_CTX_set1_rsa_keygen_pubexp(ctx, bn);
EVP_PKEY_keygen(ctx, &key); EVP_PKEY_keygen(ctx, &key);

View file

@ -130,16 +130,12 @@ private:
void padWithPKCS1(unsigned char* out, unsigned int outLength, void padWithPKCS1(unsigned char* out, unsigned int outLength,
const unsigned char* in, unsigned int inLength); const unsigned char* in, unsigned int inLength);
void padWithPKCS1Type2(unsigned char* out, unsigned int outLength,
const unsigned char* in, unsigned int inLength);
#if OPENSSL_VERSION_MAJOR >= 3 #if OPENSSL_VERSION_MAJOR >= 3
OSSL_PROVIDER *legacy, *deflt; OSSL_PROVIDER *legacy, *deflt;
#else #else
void *legacy, *deflt; void *legacy, *deflt;
#endif #endif
char cookiejar[64];
}; };
#endif #endif

View file

@ -23,7 +23,7 @@ int main(int argc, char** argv)
fullPath = std::string(mountPoint) + util; fullPath = std::string(mountPoint) + util;
if (std::string(util) == "launcher" || !pathExists(fullPath.c_str())) if (std::string(util) == "launcher" || !fileExists(fullPath.c_str()))
fullPath = std::string(mountPoint) + DEFAULT_UTIL; fullPath = std::string(mountPoint) + DEFAULT_UTIL;
free(argv0); free(argv0);

View file

@ -33,7 +33,6 @@
#include <stdio.h> #include <stdio.h>
#include <fcntl.h> #include <fcntl.h>
#include <limits.h> #include <limits.h>
#include <libgen.h>
#include <iostream> #include <iostream>
@ -52,10 +51,10 @@ void version(void)
std::cout << "Current libgourou version : " << gourou::DRMProcessor::VERSION << std::endl ; std::cout << "Current libgourou version : " << gourou::DRMProcessor::VERSION << std::endl ;
} }
bool pathExists(const char* path) bool fileExists(const char* filename)
{ {
struct stat _stat; struct stat _stat;
int ret = stat(path, &_stat); int ret = stat(filename, &_stat);
return (ret == 0); return (ret == 0);
} }
@ -68,15 +67,15 @@ const char* findFile(const char* filename, bool inDefaultDirs)
if (adeptDir && adeptDir[0]) if (adeptDir && adeptDir[0])
{ {
path = adeptDir + std::string("/") + filename; path = adeptDir + std::string("/") + filename;
if (pathExists(path.c_str())) if (fileExists(path.c_str()))
return strdup(path.c_str()); return strdup(path.c_str());
} }
path = gourou::DRMProcessor::getDefaultAdeptDir() + filename; path = gourou::DRMProcessor::getDefaultAdeptDir() + filename;
if (pathExists(path.c_str())) if (fileExists(path.c_str()))
return strdup(path.c_str()); return strdup(path.c_str());
if (pathExists(filename)) if (fileExists(filename))
return strdup(filename); return strdup(filename);
if (!inDefaultDirs) return 0; if (!inDefaultDirs) return 0;
@ -84,7 +83,7 @@ const char* findFile(const char* filename, bool inDefaultDirs)
for (int i=0; i<(int)ARRAY_SIZE(defaultDirs); i++) for (int i=0; i<(int)ARRAY_SIZE(defaultDirs); i++)
{ {
path = std::string(defaultDirs[i]) + filename; path = std::string(defaultDirs[i]) + filename;
if (pathExists(path.c_str())) if (fileExists(path.c_str()))
return strdup(path.c_str()); return strdup(path.c_str());
} }
@ -153,12 +152,3 @@ void fileCopy(const char* in, const char* out)
close (fdIn); close (fdIn);
close (fdOut); close (fdOut);
} }
void createPath(const char* filename)
{
char* basepath = strdup(filename);
char* outputDir = dirname(basepath);
if (outputDir && !pathExists(outputDir))
mkpath(outputDir);
free(basepath);
}

View file

@ -50,9 +50,9 @@ void version(void);
const char* findFile(const char* filename, bool inDefaultDirs=true); const char* findFile(const char* filename, bool inDefaultDirs=true);
/** /**
* @brief Does the file (or directory) exists * @brief Does the file (or directory exists)
*/ */
bool pathExists(const char* path); bool fileExists(const char* filename);
/** /**
* @brief Recursively created dir * @brief Recursively created dir
@ -64,9 +64,4 @@ void mkpath(const char *dir);
*/ */
void fileCopy(const char* in, const char* out); void fileCopy(const char* in, const char* out);
/**
* @brief Create intermediate directories if it does not exists
*/
void createPath(const char* filename);
#endif #endif