8 Commits
v0.3.2 ... v0.4

Author SHA1 Message Date
Grégory Soutadé
47a38b1ebc Update README 2021-09-09 21:00:43 +02:00
Grégory Soutadé
d8333531d2 Update README_package.md 2021-08-26 08:15:18 +02:00
Grégory Soutadé
cb4a26289e Fix a bug in utils : bad management of mandatory files 2021-08-26 08:13:02 +02:00
Grégory Soutadé
fb812964e0 Bad repo URL & make command for uPDFParser library 2021-08-25 22:02:48 +02:00
Grégory Soutadé
8a2b22ca9b Update Makefile and README.md 2021-08-25 21:54:52 +02:00
Grégory Soutadé
2ac917619e Add "export private key" feature 2021-08-25 21:53:54 +02:00
Grégory Soutadé
3d9e343734 Add support for PDF (needs uPDFParser library) 2021-08-21 21:12:52 +02:00
Grégory Soutadé
8bc346d139 Add fetchLicenseServiceCertificate() because not all signing certificates (to be included into rights.xml) are the same than the one fetched at authentication 2021-08-21 20:46:09 +02:00
17 changed files with 480 additions and 92 deletions

2
.gitignore vendored
View File

@@ -4,5 +4,5 @@ lib
*.so *.so
*~ *~
utils/acsmdownloader utils/acsmdownloader
utils/activate utils/adept_activate
.adept .adept

View File

@@ -2,8 +2,26 @@
AR ?= $(CROSS)ar AR ?= $(CROSS)ar
CXX ?= $(CROSS)g++ CXX ?= $(CROSS)g++
CXXFLAGS=-Wall -fPIC -I./include -I./lib -I./lib/pugixml/src/ UPDFPARSERLIB = ./lib/updfparser/libupdfparser.a
LDFLAGS=
CXXFLAGS=-Wall -fPIC -I./include -I./lib -I./lib/pugixml/src/ -I./lib/updfparser/include
LDFLAGS = $(UPDFPARSERLIB)
BUILD_STATIC ?= 0
BUILD_SHARED ?= 1
BUILD_UTILS ?= 1
TARGETS =
ifneq ($(BUILD_STATIC), 0)
TARGETS += libgourou.a
endif
ifneq ($(BUILD_SHARED), 0)
TARGETS += libgourou.so
endif
ifneq ($(BUILD_UTILS), 0)
TARGETS += build_utils
endif
ifneq ($(DEBUG),) ifneq ($(DEBUG),)
CXXFLAGS += -ggdb -O0 CXXFLAGS += -ggdb -O0
@@ -21,9 +39,7 @@ OBJEXT := o
SOURCES = src/libgourou.cpp src/user.cpp src/device.cpp src/fulfillment_item.cpp src/bytearray.cpp src/pugixml.cpp SOURCES = src/libgourou.cpp src/user.cpp src/device.cpp src/fulfillment_item.cpp src/bytearray.cpp src/pugixml.cpp
OBJECTS := $(patsubst $(SRCDIR)/%,$(BUILDDIR)/%,$(SOURCES:.$(SRCEXT)=.$(OBJEXT))) OBJECTS := $(patsubst $(SRCDIR)/%,$(BUILDDIR)/%,$(SOURCES:.$(SRCEXT)=.$(OBJEXT)))
.PHONY: utils all: lib obj $(TARGETS)
all: lib obj libgourou utils
lib: lib:
mkdir lib mkdir lib
@@ -38,12 +54,12 @@ $(BUILDDIR)/%.$(OBJEXT): $(SRCDIR)/%.$(SRCEXT)
libgourou: libgourou.a libgourou.so libgourou: libgourou.a libgourou.so
libgourou.a: $(OBJECTS) libgourou.a: $(OBJECTS)
$(AR) crs $@ obj/*.o $(AR) crs $@ obj/*.o $(LDFLAGS)
libgourou.so: libgourou.a libgourou.so: $(OBJECTS) $(UPDFPARSERLIB)
$(CXX) obj/*.o $(LDFLAGS) -o $@ -shared $(CXX) obj/*.o $(LDFLAGS) -o $@ -shared
utils: build_utils:
make -C utils ROOT=$(PWD) CXX=$(CXX) AR=$(AR) DEBUG=$(DEBUG) STATIC_UTILS=$(STATIC_UTILS) make -C utils ROOT=$(PWD) CXX=$(CXX) AR=$(AR) DEBUG=$(DEBUG) STATIC_UTILS=$(STATIC_UTILS)
clean: clean:

View File

@@ -1,7 +1,7 @@
Introduction Introduction
------------ ------------
libgourou is a free implementation of Adobe's ADEPT protocol used to add DRM on ePub files. It overcome the lacks 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
@@ -26,6 +26,8 @@ Or create a new one. Be careful : there is a limited number of devices that can
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 wants to remove DRM, you can export your private key and import it within [Calibre](https://calibre-ebook.com/) an its DeDRM plugin.
Dependencies Dependencies
------------ ------------
@@ -47,14 +49,21 @@ Compilation
Use _make_ command Use _make_ command
make [CROSS=XXX] [DEBUG=1] [STATIC_UTILS=1] make [CROSS=XXX] [DEBUG=(0*|1)] [STATIC_UTILS=(0*|1)] [BUILD_UTILS=(0|1*)] [BUILD_STATIC=(0*|1)] [BUILD_SHARED=(0|1*)]
CROSS can define a cross compiler prefix (ie arm-linux-gnueabihf-) CROSS can define a cross compiler prefix (ie arm-linux-gnueabihf-)
DEBUG can be set to compile in DEBUG mode DEBUG can be set to compile in DEBUG mode
BUILD_UTILS to build utils or not
STATIC_UTILS to build utils with static library (libgourou.a) instead of default dynamic one (libgourou.so) STATIC_UTILS to build utils with static library (libgourou.a) instead of default dynamic one (libgourou.so)
BUILD_STATIC build libgourou.a if 1, nothing if 0, can be combined with BUILD_SHARED
BUILD_SHARED build libgourou.so if 1, nothing if 0, can be combined with BUILD_STATIC
* Default value
Utils Utils
----- -----
@@ -66,11 +75,16 @@ You can import configuration from your eReader or create a new one with utils/ac
Then a _./.adept_ directory is created with all configuration file Then a _./.adept_ directory is created with all configuration file
To download an ePub : To download an ePub/PDF :
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$PWD export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$PWD
./utils/acsmdownloader -f <ACSM_FILE> ./utils/acsmdownloader -f <ACSM_FILE>
To export your private key :
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$PWD
./utils/acsmdownloader --export-private-key [-o adobekey_1.der]
Copyright Copyright
--------- ---------

View File

@@ -1,7 +1,7 @@
Introduction Introduction
------------ ------------
libgourou is a free implementation of Adobe's ADEPT protocol used to add DRM on ePub files. It overcome the lacks 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.
@@ -24,18 +24,22 @@ For utils :
Utils Utils
----- -----
You can import configuration from your eReader or create a new one with activate : You can import configuration from your eReader or create a new one with utils/activate :
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$PWD export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$PWD
./activate -u <AdobeID USERNAME> ./utils/activate -u <AdobeID USERNAME>
Then a _./.adept_ directory is created with all configuration file Then a _./.adept_ directory is created with all configuration file
To download an ePub : To download an ePub/PDF :
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$PWD export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$PWD
./acsmdownloader -f <ACSM_FILE> ./utils/acsmdownloader -f <ACSM_FILE>
To export your private key :
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$PWD
./utils/acsmdownloader --export-private-key [-o adobekey_1.der]
Sources Sources

View File

@@ -21,6 +21,7 @@
#define _DRMPROCESSORCLIENT_H_ #define _DRMPROCESSORCLIENT_H_
#include <string> #include <string>
#include <bytearray.h>
namespace gourou namespace gourou
{ {
@@ -96,8 +97,11 @@ namespace gourou
* @param URL HTTP URL * @param URL HTTP URL
* @param POSTData POST data if needed, if not set, a GET request is done * @param POSTData POST data if needed, if not set, a GET request is done
* @param contentType Optional content type of POST Data * @param contentType Optional content type of POST Data
* @param responseHeaders Optional Response headers of HTTP request
*
* @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("")) = 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) = 0;
}; };
class RSAInterface class RSAInterface
@@ -355,6 +359,27 @@ namespace gourou
* @param handler ZIP file handler * @param handler ZIP file handler
*/ */
virtual void zipClose(void* handler) = 0; virtual void zipClose(void* handler) = 0;
/**
* @brief Inflate algorithm
*
* @param data Data to inflate
* @param result Zipped data
* @param wbits Window bits value for libz
*/
virtual void inflate(std::string data, gourou::ByteArray& result,
int wbits=-15) = 0;
/**
* @brief Deflate algorithm
*
* @param data Data to deflate
* @param result Unzipped data
* @param wbits Window bits value for libz
* @param compressionLevel Compression level for libz
*/
virtual void deflate(std::string data, gourou::ByteArray& result,
int wbits=-15, int compressionLevel=8) = 0;
}; };
class DRMProcessorClient: public DigestInterface, public RandomInterface, public HTTPInterface, \ class DRMProcessorClient: public DigestInterface, public RandomInterface, public HTTPInterface, \

View File

@@ -53,10 +53,16 @@ namespace gourou
*/ */
std::string getDownloadURL(); std::string getDownloadURL();
/**
* @brief Return resource value
*/
std::string getResource();
private: private:
pugi::xml_node metadatas; pugi::xml_node metadatas;
pugi::xml_document rights; pugi::xml_document rights;
std::string downloadURL; std::string downloadURL;
std::string resource;
void buildRights(const pugi::xml_node& licenseToken, User* user); void buildRights(const pugi::xml_node& licenseToken, User* user);
}; };

View File

@@ -40,7 +40,7 @@
#define ACS_SERVER "http://adeactivate.adobe.com/adept" #define ACS_SERVER "http://adeactivate.adobe.com/adept"
#endif #endif
#define LIBGOUROU_VERSION "0.3.2" #define LIBGOUROU_VERSION "0.4"
namespace gourou namespace gourou
{ {
@@ -53,6 +53,7 @@ namespace gourou
static const std::string VERSION; static const std::string VERSION;
enum ITEM_TYPE { EPUB=0, PDF };
/** /**
* @brief Main constructor. To be used once all is configured (user has signedIn, device is activated) * @brief Main constructor. To be used once all is configured (user has signedIn, device is activated)
* *
@@ -80,8 +81,10 @@ namespace gourou
* *
* @param item Item from fulfill() method * @param item Item from fulfill() method
* @param path Output file path * @param path Output file path
*
* @return Type of downloaded item
*/ */
void download(FulfillmentItem* item, std::string path); ITEM_TYPE download(FulfillmentItem* item, std::string path);
/** /**
* @brief SignIn into ACS Server (required to activate device) * @brief SignIn into ACS Server (required to activate device)
@@ -130,8 +133,11 @@ namespace gourou
* @param URL HTTP URL * @param URL HTTP URL
* @param POSTData POST data if needed, if not set, a GET request is done * @param POSTData POST data if needed, if not set, a GET request is done
* @param contentType Optional content type of POST Data * @param contentType Optional content type of POST Data
* @param responseHeaders Optional Response headers of HTTP request
*
* @return data of HTTP response
*/ */
ByteArray sendRequest(const std::string& URL, const std::string& POSTData=std::string(), const char* contentType=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);
/** /**
* @brief Send HTTP POST request to URL with document as POSTData * @brief Send HTTP POST request to URL with document as POSTData
@@ -158,6 +164,8 @@ namespace gourou
*/ */
std::string serializeRSAPrivateKey(void* rsa); std::string serializeRSAPrivateKey(void* rsa);
void exportPrivateLicenseKey(std::string path);
/** /**
* @brief Get current user * @brief Get current user
*/ */
@@ -194,6 +202,8 @@ namespace gourou
void buildActivateReq(pugi::xml_document& activateReq); void buildActivateReq(pugi::xml_document& activateReq);
ByteArray sendFulfillRequest(const pugi::xml_document& document, const std::string& url); ByteArray sendFulfillRequest(const pugi::xml_document& document, const std::string& url);
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,
const std::string& operatorURL);
}; };
} }

View File

@@ -233,6 +233,32 @@ namespace gourou
return trim(res); return trim(res);
} }
static inline std::string extractTextElem(const pugi::xml_node& doc, const char* tagName, bool throwOnNull=true)
{
pugi::xpath_node xpath_node = doc.select_node(tagName);
if (!xpath_node)
{
if (throwOnNull)
EXCEPTION(GOUROU_TAG_NOT_FOUND, "Tag " << tagName << " not found");
return "";
}
pugi::xml_node node = xpath_node.node().first_child();
if (!node)
{
if (throwOnNull)
EXCEPTION(GOUROU_TAG_NOT_FOUND, "Text element for tag " << tagName << " not found");
return "";
}
std::string res = node.value();
return trim(res);
}
/** /**
* @brief Append an element to root with a sub text element * @brief Append an element to root with a sub text element
* *
@@ -267,7 +293,7 @@ namespace gourou
*/ */
static inline void writeFile(std::string path, ByteArray& data) static inline void writeFile(std::string path, ByteArray& data)
{ {
writeFile(path, data.data(), data.length()); writeFile(path, data.data(), data.length()-1);
} }
/** /**

View File

@@ -21,6 +21,8 @@
#define _USER_H_ #define _USER_H_
#include <string> #include <string>
#include <map>
#include "bytearray.h" #include "bytearray.h"
#include <pugixml.hpp> #include <pugixml.hpp>
@@ -46,7 +48,7 @@ namespace gourou
std::string& getDeviceFingerprint(); std::string& getDeviceFingerprint();
std::string& getUsername(); std::string& getUsername();
std::string& getLoginMethod(); std::string& getLoginMethod();
std::string& getCertificate(); std::string getLicenseServiceCertificate(std::string url);
std::string& getAuthenticationCertificate(); std::string& getAuthenticationCertificate();
std::string& getPrivateLicenseKey(); std::string& getPrivateLicenseKey();
@@ -95,7 +97,7 @@ namespace gourou
std::string deviceFingerprint; std::string deviceFingerprint;
std::string username; std::string username;
std::string loginMethod; std::string loginMethod;
std::string certificate; std::map<std::string,std::string> licenseServiceCertificates;
std::string authenticationCertificate; std::string authenticationCertificate;
std::string privateLicenseKey; std::string privateLicenseKey;

View File

@@ -12,3 +12,11 @@ fi
if [ ! -d lib/base64 ] ; then if [ ! -d lib/base64 ] ; then
git clone https://gist.github.com/f0fd86b6c73063283afe550bc5d77594.git lib/base64 git clone https://gist.github.com/f0fd86b6c73063283afe550bc5d77594.git lib/base64
fi fi
# uPDFParser
if [ ! -d lib/updfparser ] ; then
git clone git://soutade.fr/updfparser.git lib/updfparser
pushd lib/updfparser
make BUILD_STATIC=1 BUILD_SHARED=0
popd
fi

View File

@@ -36,6 +36,12 @@ namespace gourou
if (downloadURL == "") if (downloadURL == "")
EXCEPTION(FFI_INVALID_FULFILLMENT_DATA, "No download URL in document"); EXCEPTION(FFI_INVALID_FULFILLMENT_DATA, "No download URL in document");
node = doc.select_node("/envelope/fulfillmentResult/resourceItemInfo/resource").node();
resource = node.first_child().value();
if (resource == "")
EXCEPTION(FFI_INVALID_FULFILLMENT_DATA, "No resource in document");
pugi::xml_node licenseToken = doc.select_node("/envelope/fulfillmentResult/resourceItemInfo/licenseToken").node(); pugi::xml_node licenseToken = doc.select_node("/envelope/fulfillmentResult/resourceItemInfo/licenseToken").node();
if (!licenseToken) if (!licenseToken)
@@ -56,11 +62,13 @@ namespace gourou
if (!newLicenseToken.attribute("xmlns")) if (!newLicenseToken.attribute("xmlns"))
newLicenseToken.append_attribute("xmlns") = ADOBE_ADEPT_NS; newLicenseToken.append_attribute("xmlns") = ADOBE_ADEPT_NS;
pugi::xml_node licenseServiceInfo = root.append_child("licenseServiceInfo"); pugi::xml_node licenseServiceInfo = root.append_child("adept:licenseServiceInfo");
licenseServiceInfo.append_attribute("xmlns") = ADOBE_ADEPT_NS; pugi::xml_node licenseURL = licenseToken.select_node("licenseURL").node();
licenseServiceInfo.append_copy(licenseToken.select_node("licenseURL").node()); licenseURL.set_name("adept:licenseURL");
pugi::xml_node certificate = licenseServiceInfo.append_child("certificate"); licenseServiceInfo.append_copy(licenseURL);
certificate.append_child(pugi::node_pcdata).set_value(user->getCertificate().c_str()); pugi::xml_node certificate = licenseServiceInfo.append_child("adept:certificate");
std::string certificateValue = user->getLicenseServiceCertificate(licenseURL.first_child().value());
certificate.append_child(pugi::node_pcdata).set_value(certificateValue.c_str());
} }
std::string FulfillmentItem::getMetadata(std::string name) std::string FulfillmentItem::getMetadata(std::string name)
@@ -88,4 +96,9 @@ namespace gourou
{ {
return downloadURL; return downloadURL;
} }
std::string FulfillmentItem::getResource()
{
return resource;
}
} }

View File

@@ -22,6 +22,8 @@
#include <time.h> #include <time.h>
#include <vector> #include <vector>
#include <uPDFParser.h>
#include <libgourou.h> #include <libgourou.h>
#include <libgourou_common.h> #include <libgourou_common.h>
#include <libgourou_log.h> #include <libgourou_log.h>
@@ -33,6 +35,7 @@
#define ASN_TEXT 0x04 #define ASN_TEXT 0x04
#define ASN_ATTRIBUTE 0x05 #define ASN_ATTRIBUTE 0x05
namespace gourou namespace gourou
{ {
GOUROU_LOG_LEVEL logLevel = WARN; GOUROU_LOG_LEVEL logLevel = WARN;
@@ -298,11 +301,11 @@ namespace gourou
appendTextElem(root, "adept:expiration", buffer); appendTextElem(root, "adept:expiration", buffer);
} }
ByteArray DRMProcessor::sendRequest(const std::string& URL, const std::string& POSTdata, const char* contentType) ByteArray DRMProcessor::sendRequest(const std::string& URL, const std::string& POSTdata, const char* contentType, std::map<std::string, std::string>* responseHeaders)
{ {
if (contentType == 0) if (contentType == 0)
contentType = ""; contentType = "";
std::string reply = client->sendHTTPRequest(URL, POSTdata, contentType); std::string reply = client->sendHTTPRequest(URL, POSTdata, contentType, responseHeaders);
pugi::xml_document replyDoc; pugi::xml_document replyDoc;
replyDoc.load_buffer(reply.c_str(), reply.length()); replyDoc.load_buffer(reply.c_str(), reply.length());
@@ -453,6 +456,49 @@ namespace gourou
appendTextElem(activationToken, "adept:device", user->getDeviceUUID()); appendTextElem(activationToken, "adept:device", user->getDeviceUUID());
} }
void DRMProcessor::fetchLicenseServiceCertificate(const std::string& licenseURL,
const std::string& operatorURL)
{
if (user->getLicenseServiceCertificate(licenseURL) != "")
return;
std::string licenseServiceInfoReq = operatorURL + "/LicenseServiceInfo?licenseURL=" + licenseURL;
ByteArray replyData;
replyData = sendRequest(licenseServiceInfoReq);
pugi::xml_document licenseServicesDoc;
licenseServicesDoc.load_buffer(replyData.data(), replyData.length());
// Add new license certificate
pugi::xml_document activationDoc;
user->readActivation(activationDoc);
pugi::xml_node root;
pugi::xpath_node xpathRes = activationDoc.select_node("//adept:licenseServices");
// Create adept:licenseServices if it doesn't exists
if (!xpathRes)
{
xpathRes = activationDoc.select_node("/activationInfo");
root = xpathRes.node();
root = root.append_child("adept:licenseServices");
root.append_attribute("xmlns:adept") = ADOBE_ADEPT_NS;
}
else
root = xpathRes.node();
root = root.append_child("adept:licenseServiceInfo");
std::string certificate = extractTextElem(licenseServicesDoc,
"/licenseServiceInfo/certificate");
appendTextElem(root, "adept:licenseURL", licenseURL);
appendTextElem(root, "adept:certificate", certificate);
user->updateActivationFile(activationDoc);
}
FulfillmentItem* DRMProcessor::fulfill(const std::string& ACSMFile) FulfillmentItem* DRMProcessor::fulfill(const std::string& ACSMFile)
{ {
if (!user->getPKCS12().length()) if (!user->getPKCS12().length())
@@ -495,15 +541,16 @@ namespace gourou
EXCEPTION(FF_NO_OPERATOR_URL, "OperatorURL not found in ACSM document"); EXCEPTION(FF_NO_OPERATOR_URL, "OperatorURL not found in ACSM document");
std::string operatorURL = node.node().first_child().value(); std::string operatorURL = node.node().first_child().value();
operatorURL = trim(operatorURL) + "/Fulfill"; operatorURL = trim(operatorURL);
std::string fulfillURL = operatorURL + "/Fulfill";
operatorAuth(operatorURL); operatorAuth(fulfillURL);
ByteArray replyData; ByteArray replyData;
try try
{ {
replyData = sendRequest(fulfillReq, operatorURL); replyData = sendRequest(fulfillReq, fulfillURL);
} }
catch (gourou::Exception& e) catch (gourou::Exception& e)
{ {
@@ -515,8 +562,8 @@ namespace gourou
if (e.getErrorCode() == GOUROU_ADEPT_ERROR && if (e.getErrorCode() == GOUROU_ADEPT_ERROR &&
errorMsg.find("E_ADEPT_DISTRIBUTOR_AUTH") != std::string::npos) errorMsg.find("E_ADEPT_DISTRIBUTOR_AUTH") != std::string::npos)
{ {
doOperatorAuth(operatorURL); doOperatorAuth(fulfillURL);
replyData = sendRequest(fulfillReq, operatorURL); replyData = sendRequest(fulfillReq, fulfillURL);
} }
else else
{ {
@@ -528,15 +575,23 @@ namespace gourou
fulfillReply.load_string((const char*)replyData.data()); fulfillReply.load_string((const char*)replyData.data());
std::string licenseURL = extractTextElem(fulfillReply, "//licenseToken/licenseURL");
fetchLicenseServiceCertificate(licenseURL, operatorURL);
return new FulfillmentItem(fulfillReply, user); return new FulfillmentItem(fulfillReply, user);
} }
void DRMProcessor::download(FulfillmentItem* item, std::string path) DRMProcessor::ITEM_TYPE DRMProcessor::download(FulfillmentItem* item, std::string path)
{ {
ITEM_TYPE res = EPUB;
if (!item) if (!item)
EXCEPTION(DW_NO_ITEM, "No item"); EXCEPTION(DW_NO_ITEM, "No item");
ByteArray replyData = sendRequest(item->getDownloadURL()); std::map<std::string, std::string> headers;
ByteArray replyData = sendRequest(item->getDownloadURL(), "", 0, &headers);
writeFile(path, replyData); writeFile(path, replyData);
@@ -544,10 +599,54 @@ namespace gourou
std::string rightsStr = item->getRights(); std::string rightsStr = item->getRights();
if (headers.count("Content-Type") && headers["Content-Type"] == "application/pdf")
res = PDF;
if (res == EPUB)
{
void* handler = client->zipOpen(path); void* handler = client->zipOpen(path);
client->zipWriteFile(handler, "META-INF/rights.xml", rightsStr); client->zipWriteFile(handler, "META-INF/rights.xml", rightsStr);
client->zipClose(handler); client->zipClose(handler);
} }
else if (res == PDF)
{
uPDFParser::Parser parser;
try
{
GOUROU_LOG(DEBUG, "Parse PDF");
parser.parse(path);
}
catch(std::invalid_argument& e)
{
GOUROU_LOG(ERROR, "Invalid PDF");
return res;
}
std::vector<uPDFParser::Object*> objects = parser.objects();
std::vector<uPDFParser::Object*>::reverse_iterator it;
for(it = objects.rbegin(); it != objects.rend(); it++)
{
// Update EBX_HANDLER with rights
if ((*it)->hasKey("Filter") && (**it)["Filter"]->str() == "/EBX_HANDLER")
{
uPDFParser::Object* ebx = (*it)->clone();
(*ebx)["ADEPT_ID"] = new uPDFParser::String(item->getResource());
(*ebx)["EBX_BOOKID"] = new uPDFParser::String(item->getResource());
ByteArray zipped;
client->deflate(rightsStr, zipped);
(*ebx)["ADEPT_LICENSE"] = new uPDFParser::String(zipped.toBase64());
parser.addObject(ebx);
break;
}
}
parser.write(path, true);
}
return res;
}
void DRMProcessor::buildSignInRequest(pugi::xml_document& signInRequest, void DRMProcessor::buildSignInRequest(pugi::xml_document& signInRequest,
const std::string& adobeID, const std::string& adobePassword, const std::string& adobeID, const std::string& adobePassword,
@@ -800,6 +899,19 @@ namespace gourou
return res.toBase64(); return res.toBase64();
} }
void DRMProcessor::exportPrivateLicenseKey(std::string path)
{
int fd = open(path.c_str(), O_CREAT|O_TRUNC|O_WRONLY, S_IRWXU);
if (fd <= 0)
EXCEPTION(GOUROU_FILE_ERROR, "Unable to open " << path);
ByteArray privateLicenseKey = ByteArray::fromBase64(user->getPrivateLicenseKey());
/* In adobekey.py, we get base64 decoded data [26:] */
write(fd, privateLicenseKey.data()+26, privateLicenseKey.length()-1-26);
close(fd);
}
int DRMProcessor::getLogLevel() {return (int)gourou::logLevel;} int DRMProcessor::getLogLevel() {return (int)gourou::logLevel;}
void DRMProcessor::setLogLevel(int logLevel) {gourou::logLevel = (GOUROU_LOG_LEVEL)logLevel;} void DRMProcessor::setLogLevel(int logLevel) {gourou::logLevel = (GOUROU_LOG_LEVEL)logLevel;}
} }

View File

@@ -48,7 +48,6 @@ namespace gourou {
uuid = gourou::extractTextElem(activationDoc, "//adept:user", throwOnNull); uuid = gourou::extractTextElem(activationDoc, "//adept:user", throwOnNull);
deviceUUID = gourou::extractTextElem(activationDoc, "//device", throwOnNull); deviceUUID = gourou::extractTextElem(activationDoc, "//device", throwOnNull);
deviceFingerprint = gourou::extractTextElem(activationDoc, "//fingerprint", throwOnNull); deviceFingerprint = gourou::extractTextElem(activationDoc, "//fingerprint", throwOnNull);
certificate = gourou::extractTextElem(activationDoc, "//adept:certificate", throwOnNull);
authenticationCertificate = gourou::extractTextElem(activationDoc, "//adept:authenticationCertificate", throwOnNull); authenticationCertificate = gourou::extractTextElem(activationDoc, "//adept:authenticationCertificate", throwOnNull);
privateLicenseKey = gourou::extractTextElem(activationDoc, "//adept:privateLicenseKey", throwOnNull); privateLicenseKey = gourou::extractTextElem(activationDoc, "//adept:privateLicenseKey", throwOnNull);
username = gourou::extractTextElem(activationDoc, "//adept:username", throwOnNull); username = gourou::extractTextElem(activationDoc, "//adept:username", throwOnNull);
@@ -61,6 +60,15 @@ namespace gourou {
if (throwOnNull) if (throwOnNull)
EXCEPTION(USER_INVALID_ACTIVATION_FILE, "Invalid activation file"); EXCEPTION(USER_INVALID_ACTIVATION_FILE, "Invalid activation file");
} }
pugi::xpath_node_set nodeSet = activationDoc.select_nodes("//adept:licenseServices/adept:licenseServiceInfo");
for (pugi::xpath_node_set::const_iterator it = nodeSet.begin();
it != nodeSet.end(); ++it)
{
std::string url = gourou::extractTextElem(it->node(), "adept:licenseURL");
std::string certificate = gourou::extractTextElem(it->node(), "adept:certificate");
licenseServiceCertificates[url] = certificate;
}
} }
catch(gourou::Exception& e) catch(gourou::Exception& e)
{ {
@@ -74,7 +82,6 @@ namespace gourou {
std::string& User::getDeviceFingerprint() { return deviceFingerprint; } std::string& User::getDeviceFingerprint() { return deviceFingerprint; }
std::string& User::getUsername() { return username; } std::string& User::getUsername() { return username; }
std::string& User::getLoginMethod() { return loginMethod; } std::string& User::getLoginMethod() { return loginMethod; }
std::string& User::getCertificate() { return certificate; }
std::string& User::getAuthenticationCertificate() { return authenticationCertificate; } std::string& User::getAuthenticationCertificate() { return authenticationCertificate; }
std::string& User::getPrivateLicenseKey() { return privateLicenseKey; } std::string& User::getPrivateLicenseKey() { return privateLicenseKey; }
@@ -200,4 +207,13 @@ namespace gourou {
return user; return user;
} }
std::string User::getLicenseServiceCertificate(std::string url)
{
if (licenseServiceCertificates.count(trim(url)))
return licenseServiceCertificates[trim(url)];
return "";
}
} }

View File

@@ -5,7 +5,7 @@ CXXFLAGS=-Wall `pkg-config --cflags Qt5Core Qt5Network` -fPIC -I$(ROOT)/include
ifneq ($(STATIC_UTILS),) ifneq ($(STATIC_UTILS),)
LDFLAGS=`pkg-config --libs Qt5Core Qt5Network` -L$(ROOT) $(ROOT)/libgourou.a -lcrypto -lzip LDFLAGS=`pkg-config --libs Qt5Core Qt5Network` -L$(ROOT) $(ROOT)/libgourou.a -lcrypto -lzip
else else
LDFLAGS=`pkg-config --libs Qt5Core Qt5Network` -L$(ROOT) -lgourou -lcrypto -lzip LDFLAGS=`pkg-config --libs Qt5Core Qt5Network` -L$(ROOT) -lgourou -lcrypto -lzip -lz
endif endif
ifneq ($(DEBUG),) ifneq ($(DEBUG),)

View File

@@ -46,6 +46,7 @@ static const char* deviceFile = "device.xml";
static const char* activationFile = "activation.xml"; static const char* activationFile = "activation.xml";
static const char* devicekeyFile = "devicesalt"; static const char* devicekeyFile = "devicesalt";
static const char* acsmFile = 0; static const char* acsmFile = 0;
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 const char* defaultDirs[] = { static const char* defaultDirs[] = {
@@ -71,7 +72,29 @@ public:
{ {
DRMProcessorClientImpl client; DRMProcessorClientImpl client;
gourou::DRMProcessor processor(&client, deviceFile, activationFile, devicekeyFile); gourou::DRMProcessor processor(&client, deviceFile, activationFile, devicekeyFile);
gourou::User* user = processor.getUser();
if (exportPrivateKey)
{
std::string filename;
if (!outputFile)
filename = std::string("Adobe_PrivateLicenseKey--") + user->getUsername() + ".der";
if (outputDir)
{
QDir dir(outputDir);
if (!dir.exists(outputDir))
dir.mkpath(outputDir);
filename = std::string(outputDir) + "/" + filename;
}
processor.exportPrivateLicenseKey(filename);
std::cout << "Private license key exported to " << filename << std::endl;
}
else
{
gourou::FulfillmentItem* item = processor.fulfill(acsmFile); gourou::FulfillmentItem* item = processor.fulfill(acsmFile);
std::string filename; std::string filename;
@@ -79,9 +102,7 @@ public:
{ {
filename = item->getMetadata("title"); filename = item->getMetadata("title");
if (filename == "") if (filename == "")
filename = "output.epub"; filename = "output";
else
filename += ".epub";
} }
else else
filename = outputFile; filename = outputFile;
@@ -95,8 +116,21 @@ public:
filename = std::string(outputDir) + "/" + filename; filename = std::string(outputDir) + "/" + filename;
} }
processor.download(item, filename); gourou::DRMProcessor::ITEM_TYPE type = processor.download(item, filename);
if (!outputFile)
{
std::string finalName = filename;
if (type == gourou::DRMProcessor::ITEM_TYPE::PDF)
finalName += ".pdf";
else
finalName += ".epub";
QDir dir;
dir.rename(filename.c_str(), finalName.c_str());
filename = finalName;
}
std::cout << "Created " << filename << std::endl; std::cout << "Created " << filename << std::endl;
}
} catch(std::exception& e) } catch(std::exception& e)
{ {
std::cout << e.what() << std::endl; std::cout << e.what() << std::endl;
@@ -139,14 +173,15 @@ static void usage(const char* cmd)
{ {
std::cout << "Download EPUB file from ACSM request file" << std::endl; std::cout << "Download EPUB file from ACSM request file" << std::endl;
std::cout << "Usage: " << cmd << " [(-d|--device-file) device.xml] [(-a|--activation-file) activation.xml] [(-s|--device-key-file) devicesalt] [(-O|--output-dir) dir] [(-o|--output-file) output.epub] [(-v|--verbose)] [(-h|--help)] (-f|--acsm-file) file.acsm" << std::endl << std::endl; std::cout << "Usage: " << cmd << " [(-d|--device-file) device.xml] [(-a|--activation-file) activation.xml] [(-s|--device-key-file) devicesalt] [(-O|--output-dir) dir] [(-o|--output-file) output(.epub|.pdf|.der)] [(-v|--verbose)] [(-h|--help)] (-f|--acsm-file) file.acsm|(-e|--export-private-key)" << std::endl << std::endl;
std::cout << " " << "-d|--device-file" << "\t" << "device.xml file from eReader" << std::endl; std::cout << " " << "-d|--device-file" << "\t" << "device.xml file from eReader" << std::endl;
std::cout << " " << "-a|--activation-file" << "\t" << "activation.xml file from eReader" << std::endl; std::cout << " " << "-a|--activation-file" << "\t" << "activation.xml file from eReader" << std::endl;
std::cout << " " << "-k|--device-key-file" << "\t" << "private device key file (eg devicesalt/devkey.bin) from eReader" << std::endl; std::cout << " " << "-k|--device-key-file" << "\t" << "private device key file (eg devicesalt/devkey.bin) from eReader" << std::endl;
std::cout << " " << "-O|--output-dir" << "\t" << "Optional output directory were to put result (default ./)" << 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 epub filename (default <title.epub>)" << std::endl; std::cout << " " << "-o|--output-file" << "\t" << "Optional output filename (default <title.(epub|pdf|der)>)" << std::endl;
std::cout << " " << "-f|--acsm-file" << "\t" << "ACSM request file for epub download" << std::endl; std::cout << " " << "-f|--acsm-file" << "\t" << "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 << " " << "-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;
@@ -175,13 +210,14 @@ int main(int argc, char** argv)
{"output-dir", required_argument, 0, 'O' }, {"output-dir", required_argument, 0, 'O' },
{"output-file", required_argument, 0, 'o' }, {"output-file", required_argument, 0, 'o' },
{"acsm-file", required_argument, 0, 'f' }, {"acsm-file", required_argument, 0, 'f' },
{"export-private-key",no_argument, 0, 'e' },
{"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:a:k:O:o:f:vVh", c = getopt_long(argc, argv, "d:a:k:O:o:f:evVh",
long_options, &option_index); long_options, &option_index);
if (c == -1) if (c == -1)
break; break;
@@ -205,6 +241,9 @@ int main(int argc, char** argv)
case 'o': case 'o':
outputFile = optarg; outputFile = optarg;
break; break;
case 'e':
exportPrivateKey = true;
break;
case 'v': case 'v':
verbose++; verbose++;
break; break;
@@ -222,7 +261,7 @@ int main(int argc, char** argv)
gourou::DRMProcessor::setLogLevel(verbose); gourou::DRMProcessor::setLogLevel(verbose);
if (!acsmFile || (outputDir && !outputDir[0]) || if ((!acsmFile && !exportPrivateKey) || (outputDir && !outputDir[0]) ||
(outputFile && !outputFile[0])) (outputFile && !outputFile[0]))
{ {
usage(argv[0]); usage(argv[0]);
@@ -233,17 +272,33 @@ int main(int argc, char** argv)
ACSMDownloader downloader(&app); ACSMDownloader downloader(&app);
int i; int i;
bool hasErrors = false;
const char* orig;
for (i=0; i<(int)ARRAY_SIZE(files); i++) for (i=0; i<(int)ARRAY_SIZE(files); i++)
{ {
orig = *files[i];
*files[i] = findFile(*files[i]); *files[i] = findFile(*files[i]);
if (!*files[i]) if (!*files[i])
{ {
std::cout << "Error : " << *files[i] << " doesn't exists" << std::endl; std::cout << "Error : " << orig << " doesn't exists, did you activate your device ?" << std::endl;
ret = -1; ret = -1;
goto end; hasErrors = true;
} }
} }
if (hasErrors)
goto end;
if (exportPrivateKey)
{
if (acsmFile)
{
usage(argv[0]);
return -1;
}
}
else
{
QFile file(acsmFile); QFile file(acsmFile);
if (!file.exists()) if (!file.exists())
{ {
@@ -251,6 +306,7 @@ int main(int argc, char** argv)
ret = -1; ret = -1;
goto end; goto end;
} }
}
QThreadPool::globalInstance()->start(&downloader); QThreadPool::globalInstance()->start(&downloader);

View File

@@ -38,6 +38,7 @@
#include <QFile> #include <QFile>
#include <zip.h> #include <zip.h>
#include <zlib.h>
#include <libgourou_common.h> #include <libgourou_common.h>
#include <libgourou_log.h> #include <libgourou_log.h>
@@ -82,7 +83,7 @@ void DRMProcessorClientImpl::randBytes(unsigned char* bytesOut, unsigned int len
} }
/* HTTP interface */ /* HTTP interface */
std::string DRMProcessorClientImpl::sendHTTPRequest(const std::string& URL, const std::string& POSTData, const std::string& contentType) std::string DRMProcessorClientImpl::sendHTTPRequest(const std::string& URL, const std::string& POSTData, const std::string& contentType, std::map<std::string, std::string>* responseHeaders)
{ {
QNetworkRequest request(QUrl(URL.c_str())); QNetworkRequest request(QUrl(URL.c_str()));
QNetworkAccessManager networkManager; QNetworkAccessManager networkManager;
@@ -121,12 +122,12 @@ std::string DRMProcessorClientImpl::sendHTTPRequest(const std::string& URL, cons
if (reply->error() != QNetworkReply::NoError) if (reply->error() != QNetworkReply::NoError)
EXCEPTION(gourou::CLIENT_NETWORK_ERROR, "Error " << reply->errorString().toStdString()); EXCEPTION(gourou::CLIENT_NETWORK_ERROR, "Error " << reply->errorString().toStdString());
if (gourou::logLevel >= gourou::DEBUG)
{
QList<QByteArray> headers = reply->rawHeaderList(); QList<QByteArray> headers = reply->rawHeaderList();
for (int i = 0; i < headers.size(); ++i) { 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; std::cout << headers[i].constData() << " : " << reply->rawHeader(headers[i]).constData() << std::endl;
} if (responseHeaders)
(*responseHeaders)[headers[i].constData()] = reply->rawHeader(headers[i]).constData();
} }
replyData = reply->readAll(); replyData = reply->readAll();
@@ -420,3 +421,78 @@ void DRMProcessorClientImpl::zipClose(void* handler)
{ {
zip_close((zip_t*)handler); zip_close((zip_t*)handler);
} }
void DRMProcessorClientImpl::inflate(std::string data, gourou::ByteArray& result,
int wbits)
{
unsigned int dataSize = data.size()*2;
unsigned char* buffer = new unsigned char[dataSize];
z_stream infstream;
infstream.zalloc = Z_NULL;
infstream.zfree = Z_NULL;
infstream.opaque = Z_NULL;
infstream.avail_in = (uInt)data.size();
infstream.next_in = (Bytef *)data.c_str(); // input char array
infstream.avail_out = (uInt)dataSize; // size of output
infstream.next_out = (Bytef *)buffer; // output char array
int ret = inflateInit2(&infstream, wbits);
ret = ::inflate(&infstream, Z_SYNC_FLUSH);
while (ret == Z_OK || ret == Z_STREAM_END)
{
result.append(buffer, dataSize-infstream.avail_out);
if (ret == Z_STREAM_END) break;
infstream.avail_out = (uInt)dataSize; // size of output
infstream.next_out = (Bytef *)buffer; // output char array
ret = ::inflate(&infstream, Z_SYNC_FLUSH);
}
inflateEnd(&infstream);
delete[] buffer;
if (ret != Z_OK && ret != Z_STREAM_END && ret != Z_BUF_ERROR)
EXCEPTION(gourou::CLIENT_ZIP_ERROR, zError(ret));
}
void DRMProcessorClientImpl::deflate(std::string data, gourou::ByteArray& result,
int wbits, int compressionLevel)
{
unsigned int dataSize = data.size();
unsigned char* buffer = new unsigned char[dataSize];
z_stream defstream;
defstream.zalloc = Z_NULL;
defstream.zfree = Z_NULL;
defstream.opaque = Z_NULL;
defstream.avail_in = (uInt)data.size();
defstream.next_in = (Bytef *)data.c_str(); // input char array
defstream.avail_out = (uInt)dataSize; // size of output
defstream.next_out = (Bytef *)buffer; // output char array
int ret = deflateInit2(&defstream, Z_DEFAULT_COMPRESSION, Z_DEFLATED, wbits,
compressionLevel, Z_DEFAULT_STRATEGY);
ret = ::deflate(&defstream, Z_SYNC_FLUSH);
while (ret == Z_OK || ret == Z_STREAM_END)
{
result.append(buffer, dataSize-defstream.avail_out);
if (ret == Z_STREAM_END) break;
defstream.avail_out = (uInt)dataSize; // size of output
defstream.next_out = (Bytef *)buffer; // output char array
ret = ::deflate(&defstream, Z_SYNC_FLUSH);
}
deflateEnd(&defstream);
delete[] buffer;
if (ret != Z_OK && ret != Z_STREAM_END && ret != Z_BUF_ERROR)
EXCEPTION(gourou::CLIENT_ZIP_ERROR, zError(ret));
}

View File

@@ -46,7 +46,7 @@ class DRMProcessorClientImpl : public gourou::DRMProcessorClient
virtual void randBytes(unsigned char* bytesOut, unsigned int length); virtual void randBytes(unsigned char* bytesOut, unsigned int length);
/* HTTP interface */ /* HTTP interface */
virtual std::string sendHTTPRequest(const std::string& URL, const std::string& POSTData=std::string(""), const std::string& contentType=std::string("")); 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 void RSAPrivateEncrypt(const unsigned char* RSAKey, unsigned int RSAKeyLength, virtual void RSAPrivateEncrypt(const unsigned char* RSAKey, unsigned int RSAKeyLength,
const RSA_KEY_TYPE keyType, const std::string& password, const RSA_KEY_TYPE keyType, const std::string& password,
@@ -108,6 +108,10 @@ class DRMProcessorClientImpl : public gourou::DRMProcessorClient
virtual void zipClose(void* handler); virtual void zipClose(void* handler);
virtual void inflate(std::string data, gourou::ByteArray& result, int wbits=-15);
virtual void deflate(std::string data, gourou::ByteArray& result,
int wbits=-15, int compressionLevel=8);
}; };
#endif #endif