29 Commits
v0.1 ... v0.4.2

Author SHA1 Message Date
Grégory Soutadé
89a5408c2d Update README_package.md 2021-11-03 13:54:44 +01:00
Grégory Soutadé
56e4fda760 Utils: Forgot to pass responseHeaders parameters when we have a redirection 2021-11-03 13:54:04 +01:00
Grégory Soutadé
59c801da08 Fix STATIC_BUILD errors 2021-09-28 18:14:41 +02:00
Grégory Soutadé
f01bf9e837 Fix a bug : export key doesn't supports output in a target file 2021-09-28 15:14:18 +02:00
Grégory Soutadé
c57aba8061 Fix an error in utils Makefile : forgot -lz in static build 2021-09-28 15:02:50 +02:00
Grégory Soutadé
33ea9e7d65 Raise an exception if no EBX_HANDLER is found in PDF 2021-09-28 15:01:04 +02:00
Grégory Soutadé
dc15fc7197 Remove implicit handle of final \0 in ByteArray 2021-09-28 14:58:41 +02:00
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
Grégory Soutadé
7f5a4900e0 Typo fix 2021-08-21 20:37:07 +02:00
Grégory Soutadé
d8f882a277 Fix a bug : gmtime() should used in place of localtime() to keep date in UTC and not local timezone 2021-08-12 21:11:40 +02:00
Grégory Soutadé
bdaceba2e0 Fix bad utils return value on error. Build network errors with a message and not just an error code. 2021-07-29 21:14:48 +02:00
Grégory Soutadé
b5eb0efd31 Force ACSM files to be parsed using UTF8 locale 2021-07-17 09:34:03 +02:00
Grégory Soutadé
20b4e69c3e Rename too generic "activate" util in "adept_activate" 2021-07-16 20:44:40 +02:00
Grégory Soutadé
878e4e7453 ACSM data mut be unescaped before parsing
Add a copy constructor to Exception
Start an authentication when E_ADEPT_DISTRIBUTOR_AUTH error is received during fulfill
Don't download external libraries if it already exists
2021-07-16 20:38:32 +02:00
Grégory Soutadé
1eebc88913 Update README.md 2021-07-12 21:26:53 +02:00
Grégory Soutadé
460f452783 Fix an error in hashNode : a tag can define multiple xml namespaces 2021-07-12 14:23:18 +02:00
Grégory Soutadé
8881a58c95 Handle 'Location' header reply in drmprocessorclientimpl 2021-07-10 12:51:36 +02:00
Grégory Soutadé
0e90b89382 v0.2 Fix a lot of things :
* add common method signNode() and addNonde()
  * Try to auth to operator if not already set
  * Fix an error in ADEPT protocol : attributes must be hashed in alphabetical order, not in reverse one
  * Update DRMProcessorClient
2021-07-09 21:55:39 +02:00
Grégory Soutadé
7adc6a0fc1 Fix bug : -o option not handled in acsmdownloader 2021-07-08 12:59:56 +02:00
Grégory Soutadé
429fe34e8e Add README_package.md 2021-07-06 22:16:26 +02:00
Grégory Soutadé
49a74cdd6f Set default login method to "AdobeID" 2021-07-06 21:04:48 +02:00
Grégory Soutadé
c85db73583 Fix misplaced break into switch 2021-07-06 20:52:43 +02:00
23 changed files with 900 additions and 197 deletions

2
.gitignore vendored
View File

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

View File

@@ -2,8 +2,26 @@
AR ?= $(CROSS)ar
CXX ?= $(CROSS)g++
CXXFLAGS=-Wall -fPIC -I./include -I./lib -I./lib/pugixml/src/
LDFLAGS=
UPDFPARSERLIB = ./lib/updfparser/libupdfparser.a
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),)
CXXFLAGS += -ggdb -O0
@@ -18,12 +36,10 @@ TARGETDIR := bin
SRCEXT := cpp
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)))
.PHONY: utils
all: lib obj libgourou utils
all: lib obj $(TARGETS)
lib:
mkdir lib
@@ -38,12 +54,12 @@ $(BUILDDIR)/%.$(OBJEXT): $(SRCDIR)/%.$(SRCEXT)
libgourou: libgourou.a libgourou.so
libgourou.a: $(OBJECTS)
$(AR) crs $@ obj/*.o
$(AR) crs $@ obj/*.o ./lib/updfparser/obj/*.o
libgourou.so: libgourou.a
libgourou.so: $(OBJECTS) $(UPDFPARSERLIB)
$(CXX) obj/*.o $(LDFLAGS) -o $@ -shared
utils:
build_utils:
make -C utils ROOT=$(PWD) CXX=$(CXX) AR=$(AR) DEBUG=$(DEBUG) STATIC_UTILS=$(STATIC_UTILS)
clean:

View File

@@ -1,7 +1,7 @@
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
@@ -19,13 +19,15 @@ Main fucntions to use from gourou::DRMProcessor are :
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
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.
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
------------
@@ -47,14 +49,21 @@ Compilation
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-)
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)
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
-----
@@ -62,15 +71,20 @@ Utils
You can import configuration from your eReader or create a new one with utils/activate :
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$PWD
./utils/activate -u <USERNAME>
./utils/activate -u <AdobeID USERNAME>
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
./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
---------
@@ -85,3 +99,10 @@ License
libgourou : LGPL v3 or later
utils : BSD
Special thanks
--------------
* _Jens_ for all test samples and utils testing

72
README_package.md Normal file
View File

@@ -0,0 +1,72 @@
Introduction
------------
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.
Dependencies
------------
For libgourou :
* None
For utils :
* QT5Core
* QT5Network
* OpenSSL
* libzip
Utils
-----
You can import configuration from your eReader or create a new one with utils/activate :
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$PWD
./activate -u <AdobeID USERNAME>
Then a _./.adept_ directory is created with all configuration file
To download an ePub/PDF :
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$PWD
./acsmdownloader -f <ACSM_FILE>
To export your private key :
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$PWD
./acsmdownloader --export-private-key [-o adobekey_1.der]
Sources
-------
http://indefero.soutade.fr/p/libgourou
Copyright
---------
Grégory Soutadé
License
-------
libgourou : LGPL v3 or later
utils : BSD
Special thanks
--------------
* _Jens_ for all test samples and utils testing

View File

@@ -14,7 +14,7 @@
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with libgourou. If not, see <http://www.gnu.org/licenses/>.
along with libgourou. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef _BYTEARRAY_H_

View File

@@ -14,7 +14,7 @@
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with libgourou. If not, see <http://www.gnu.org/licenses/>.
along with libgourou. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef _DEVICE_H_
@@ -48,7 +48,7 @@ namespace gourou
const unsigned char* getDeviceKey();
/**
* @brief Get one value of device.xml (deviceClass, deviceSerial, deviceName, deviceType, jobbes, clientOS, clientLocale)
* @brief Get one value of device.xml (deviceClass, deviceSerial, deviceName, deviceType, hobbes, clientOS, clientLocale)
*/
std::string getProperty(const std::string& property, const std::string& _default=std::string(""));
std::string operator[](const std::string& property);

View File

@@ -14,13 +14,14 @@
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with libgourou. If not, see <http://www.gnu.org/licenses/>.
along with libgourou. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef _DRMPROCESSORCLIENT_H_
#define _DRMPROCESSORCLIENT_H_
#include <string>
#include <bytearray.h>
namespace gourou
{
@@ -93,11 +94,14 @@ namespace gourou
/**
* @brief Send HTTP (GET or POST) request
*
* @param URL HTTP URL
* @param POSTData POST data if needed, if not set, a GET request is done
* @param contentType Optional content type of POST Data
* @param URL HTTP URL
* @param POSTData POST data if needed, if not set, a GET request is done
* @param contentType Optional content type of POST Data
* @param responseHeaders Optional Response headers of HTTP request
*
* @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
@@ -173,6 +177,20 @@ namespace gourou
* @param keyOutLength Length of result
*/
virtual void extractRSAPrivateKey(void* handler, unsigned char** keyOut, unsigned int* keyOutLength) = 0;
/**
* @brief Extract certificate from PKCS12 blob
*
* @param RSAKey RSA key in binary form
* @param RSAKeyLength RSA key length
* @param keyType Key type
* @param password Optional password for RSA PKCS12 certificate
* @param certOut Result certificate
* @param certOutLength Result certificate length
*/
virtual void extractCertificate(const unsigned char* RSAKey, unsigned int RSAKeyLength,
const RSA_KEY_TYPE keyType, const std::string& password,
unsigned char** certOut, unsigned int* certOutLength) = 0;
};
class CryptoInterface
@@ -341,6 +359,27 @@ namespace gourou
* @param handler ZIP file handler
*/
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, \

View File

@@ -14,7 +14,7 @@
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with libgourou. If not, see <http://www.gnu.org/licenses/>.
along with libgourou. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef _FULFILLMENT_ITEM_H_
@@ -53,10 +53,16 @@ namespace gourou
*/
std::string getDownloadURL();
/**
* @brief Return resource value
*/
std::string getResource();
private:
pugi::xml_node metadatas;
pugi::xml_document rights;
std::string downloadURL;
std::string resource;
void buildRights(const pugi::xml_node& licenseToken, User* user);
};

View File

@@ -14,7 +14,7 @@
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with libgourou. If not, see <http://www.gnu.org/licenses/>.
along with libgourou. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef _LIBGOUROU_H_
@@ -40,7 +40,7 @@
#define ACS_SERVER "http://adeactivate.adobe.com/adept"
#endif
#define LIBGOUROU_VERSION "0.1"
#define LIBGOUROU_VERSION "0.4.2"
namespace gourou
{
@@ -53,6 +53,7 @@ namespace gourou
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)
*
@@ -80,8 +81,10 @@ namespace gourou
*
* @param item Item from fulfill() method
* @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)
@@ -130,8 +133,11 @@ namespace gourou
* @param URL HTTP URL
* @param POSTData POST data if needed, if not set, a GET request is done
* @param contentType Optional content type of POST Data
* @param responseHeaders Optional Response headers of HTTP request
*
* @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
@@ -158,6 +164,8 @@ namespace gourou
*/
std::string serializeRSAPrivateKey(void* rsa);
void exportPrivateLicenseKey(std::string path);
/**
* @brief Get current user
*/
@@ -184,10 +192,18 @@ namespace gourou
void pushTag(void* sha_ctx, uint8_t tag);
void hashNode(const pugi::xml_node& root, void *sha_ctx, std::map<std::string,std::string> nsHash);
void hashNode(const pugi::xml_node& root, unsigned char* sha_out);
std::string signNode(const pugi::xml_node& rootNode);
void addNonce(pugi::xml_node& root);
void buildAuthRequest(pugi::xml_document& authReq);
void buildInitLicenseServiceRequest(pugi::xml_document& initLicReq, std::string operatorURL);
void doOperatorAuth(std::string operatorURL);
void operatorAuth(std::string operatorURL);
void buildFulfillRequest(pugi::xml_document& acsmDoc, pugi::xml_document& fulfillReq);
void buildActivateReq(pugi::xml_document& activateReq);
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 fetchLicenseServiceCertificate(const std::string& licenseURL,
const std::string& operatorURL);
};
}

View File

@@ -14,7 +14,7 @@
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with libgourou. If not, see <http://www.gnu.org/licenses/>.
along with libgourou. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef _LIBGOUROU_COMMON_H_
@@ -68,6 +68,7 @@ namespace gourou
enum DOWNLOAD_ERROR {
DW_NO_ITEM = 0x1200,
DW_NO_EBX_HANDLER,
};
enum SIGNIN_ERROR {
@@ -107,8 +108,8 @@ namespace gourou
CLIENT_BAD_KEY_SIZE,
CLIENT_BAD_ZIP_FILE,
CLIENT_ZIP_ERROR,
CLIENT_GENERIC_EXCEPTION
CLIENT_GENERIC_EXCEPTION,
CLIENT_NETWORK_ERROR,
};
/**
@@ -128,6 +129,14 @@ namespace gourou
fullmessage = strdup(msg.str().c_str());
}
Exception(const Exception& other)
{
this->code = other.code;
this->line = line;
this->file = file;
this->fullmessage = strdup(other.fullmessage);
}
~Exception()
{
free(fullmessage);
@@ -225,6 +234,32 @@ namespace gourou
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
*

View File

@@ -14,7 +14,7 @@
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with libgourou. If not, see <http://www.gnu.org/licenses/>.
along with libgourou. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef _LIBGOUROU_LOG_H_

View File

@@ -14,13 +14,15 @@
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with libgourou. If not, see <http://www.gnu.org/licenses/>.
along with libgourou. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef _USER_H_
#define _USER_H_
#include <string>
#include <map>
#include "bytearray.h"
#include <pugixml.hpp>
@@ -46,7 +48,7 @@ namespace gourou
std::string& getDeviceFingerprint();
std::string& getUsername();
std::string& getLoginMethod();
std::string& getCertificate();
std::string getLicenseServiceCertificate(std::string url);
std::string& getAuthenticationCertificate();
std::string& getPrivateLicenseKey();
@@ -70,6 +72,11 @@ namespace gourou
*/
std::string getProperty(const std::string property);
/**
* @brief Get all nodes with property name
*/
pugi::xpath_node_set getProperties(const std::string property);
/**
* @brief Create activation.xml and devicesalt files if they did not exists
*
@@ -90,7 +97,7 @@ namespace gourou
std::string deviceFingerprint;
std::string username;
std::string loginMethod;
std::string certificate;
std::map<std::string,std::string> licenseServiceCertificates;
std::string authenticationCertificate;
std::string privateLicenseKey;

View File

@@ -1,10 +1,22 @@
#!/bin/bash
# Pugixml
git clone https://github.com/zeux/pugixml.git lib/pugixml
pushd lib/pugixml
git checkout latest
popd
if [ ! -d lib/pugixml ] ; then
git clone https://github.com/zeux/pugixml.git lib/pugixml
pushd lib/pugixml
git checkout latest
popd
fi
# Base64
git clone https://gist.github.com/f0fd86b6c73063283afe550bc5d77594.git lib/base64
if [ ! -d lib/base64 ] ; then
git clone https://gist.github.com/f0fd86b6c73063283afe550bc5d77594.git lib/base64
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

@@ -14,7 +14,7 @@
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with libgourou. If not, see <http://www.gnu.org/licenses/>.
along with libgourou. If not, see <http://www.gnu.org/licenses/>.
*/
#include <string.h>
@@ -37,14 +37,14 @@ namespace gourou
ByteArray::ByteArray(const char* data, int length)
{
if (length == -1)
length = strlen(data) + 1;
length = strlen(data);
initData((const unsigned char*)data, (unsigned int) length);
}
ByteArray::ByteArray(const std::string& str)
{
initData((unsigned char*)str.c_str(), (unsigned int)str.length() + 1);
initData((unsigned char*)str.c_str(), (unsigned int)str.length());
}
void ByteArray::initData(const unsigned char* data, unsigned int length)

View File

@@ -14,7 +14,7 @@
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with libgourou. If not, see <http://www.gnu.org/licenses/>.
along with libgourou. If not, see <http://www.gnu.org/licenses/>.
*/
#include <sys/stat.h>

View File

@@ -14,7 +14,7 @@
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with libgourou. If not, see <http://www.gnu.org/licenses/>.
along with libgourou. If not, see <http://www.gnu.org/licenses/>.
*/
#include <fulfillment_item.h>
@@ -36,6 +36,12 @@ namespace gourou
if (downloadURL == "")
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();
if (!licenseToken)
@@ -56,11 +62,13 @@ namespace gourou
if (!newLicenseToken.attribute("xmlns"))
newLicenseToken.append_attribute("xmlns") = ADOBE_ADEPT_NS;
pugi::xml_node licenseServiceInfo = root.append_child("licenseServiceInfo");
licenseServiceInfo.append_attribute("xmlns") = ADOBE_ADEPT_NS;
licenseServiceInfo.append_copy(licenseToken.select_node("licenseURL").node());
pugi::xml_node certificate = licenseServiceInfo.append_child("certificate");
certificate.append_child(pugi::node_pcdata).set_value(user->getCertificate().c_str());
pugi::xml_node licenseServiceInfo = root.append_child("adept:licenseServiceInfo");
pugi::xml_node licenseURL = licenseToken.select_node("licenseURL").node();
licenseURL.set_name("adept:licenseURL");
licenseServiceInfo.append_copy(licenseURL);
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)
@@ -88,4 +96,9 @@ namespace gourou
{
return downloadURL;
}
std::string FulfillmentItem::getResource()
{
return resource;
}
}

View File

@@ -14,12 +14,15 @@
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with libgourou. If not, see <http://www.gnu.org/licenses/>.
along with libgourou. If not, see <http://www.gnu.org/licenses/>.
*/
#include <arpa/inet.h>
#include <sys/time.h>
#include <time.h>
#include <vector>
#include <uPDFParser.h>
#include <libgourou.h>
#include <libgourou_common.h>
@@ -32,6 +35,7 @@
#define ASN_TEXT 0x04
#define ASN_ATTRIBUTE 0x05
namespace gourou
{
GOUROU_LOG_LEVEL logLevel = WARN;
@@ -131,7 +135,8 @@ namespace gourou
ns = attrName.substr(attrName.find(':')+1);
nsHash[ns] = ait->value();
break;
// Don't break here because we may multiple xmlns definitions
// break;
}
}
@@ -156,13 +161,28 @@ namespace gourou
pushString(sha_ctx, name);
// Must be parsed in reverse order
for (pugi::xml_attribute attr = root.last_attribute();
attr; attr = attr.previous_attribute())
std::vector<std::string> attributes;
pugi::xml_attribute attr;
for (attr = root.first_attribute();
attr; attr = attr.next_attribute())
{
if (std::string(attr.name()).find("xmlns") != std::string::npos)
continue;
attributes.push_back(attr.name());
}
// Attributes must be handled in alphabetical order
std::sort(attributes.begin(), attributes.end());
std::vector<std::string>::iterator attributesIt;
for(attributesIt = attributes.begin();
attributesIt != attributes.end();
attributesIt++)
{
attr = root.attribute(attributesIt->c_str());
pushTag(sha_ctx, ASN_ATTRIBUTE);
pushString(sha_ctx, "");
@@ -213,14 +233,79 @@ namespace gourou
for(int i=0; i<(int)SHA1_LEN; i++)
printf("%02x ", sha_out[i]);
printf("\n");
}
}
ByteArray DRMProcessor::sendRequest(const std::string& URL, const std::string& POSTdata, const char* contentType)
std::string DRMProcessor::signNode(const pugi::xml_node& rootNode)
{
// Compute hash
unsigned char sha_out[SHA1_LEN];
hashNode(rootNode, sha_out);
// Sign with private key
unsigned char res[RSA_KEY_SIZE];
ByteArray deviceKey(device->getDeviceKey(), Device::DEVICE_KEY_SIZE);
std::string pkcs12 = user->getPKCS12();
ByteArray privateRSAKey = ByteArray::fromBase64(pkcs12);
client->RSAPrivateEncrypt(privateRSAKey.data(), privateRSAKey.length(),
RSAInterface::RSA_KEY_PKCS12, deviceKey.toBase64().data(),
sha_out, sizeof(sha_out), res);
if (logLevel >= DEBUG)
{
printf("Sig : ");
for(int i=0; i<(int)sizeof(res); i++)
printf("%02x ", res[i]);
printf("\n");
}
ByteArray signature(res, sizeof(res));
return signature.toBase64();
}
void DRMProcessor::addNonce(pugi::xml_node& root)
{
/*
r4 = tp->time
r3 = 0
r2 = tm->militime
r0 = 0x6f046000
r1 = 0x388a
r3 += high(r4*1000)
r2 += low(r4*1000)
r0 += r2
r1 += r3
*/
struct timeval tv;
gettimeofday(&tv, 0);
uint32_t nonce32[2] = {0x6f046000, 0x388a};
uint64_t bigtime = tv.tv_sec*1000;
nonce32[0] += (bigtime & 0xFFFFFFFF) + (tv.tv_usec/1000);
nonce32[1] += ((bigtime >> 32) & 0xFFFFFFFF);
ByteArray nonce((const unsigned char*)&nonce32, sizeof(nonce32));
uint32_t tmp = 0;
nonce.append((const unsigned char*)&tmp, sizeof(tmp));
appendTextElem(root, "adept:nonce", nonce.toBase64().data());
time_t _time = time(0) + 10*60; // Cur time + 10 minutes
struct tm* tm_info = gmtime(&_time);
char buffer[32];
strftime(buffer, sizeof(buffer), "%Y-%m-%dT%H:%M:%SZ", tm_info);
appendTextElem(root, "adept:expiration", buffer);
}
ByteArray DRMProcessor::sendRequest(const std::string& URL, const std::string& POSTdata, const char* contentType, std::map<std::string, std::string>* responseHeaders)
{
if (contentType == 0)
contentType = "";
std::string reply = client->sendHTTPRequest(URL, POSTdata, contentType);
std::string reply = client->sendHTTPRequest(URL, POSTdata, contentType, responseHeaders);
pugi::xml_document replyDoc;
replyDoc.load_buffer(reply.c_str(), reply.length());
@@ -243,6 +328,107 @@ namespace gourou
return sendRequest(url, xmlStr, (const char*)"application/vnd.adobe.adept+xml");
}
void DRMProcessor::buildAuthRequest(pugi::xml_document& authReq)
{
pugi::xml_node decl = authReq.append_child(pugi::node_declaration);
decl.append_attribute("version") = "1.0";
pugi::xml_node root = authReq.append_child("adept:credentials");
root.append_attribute("xmlns:adept") = ADOBE_ADEPT_NS;
appendTextElem(root, "adept:user", user->getUUID());
ByteArray deviceKey(device->getDeviceKey(), Device::DEVICE_KEY_SIZE);
unsigned char* pkcs12 = 0;
unsigned int pkcs12Length;
ByteArray pkcs12Cert = ByteArray::fromBase64(user->getPKCS12());
client->extractCertificate(pkcs12Cert.data(), pkcs12Cert.length(),
RSAInterface::RSA_KEY_PKCS12, deviceKey.toBase64().data(),
&pkcs12, &pkcs12Length);
ByteArray privateCertificate(pkcs12, pkcs12Length);
free(pkcs12);
appendTextElem(root, "adept:certificate", privateCertificate.toBase64());
appendTextElem(root, "adept:licenseCertificate", user->getProperty("//adept:licenseCertificate"));
appendTextElem(root, "adept:authenticationCertificate", user->getProperty("//adept:authenticationCertificate"));
}
void DRMProcessor::buildInitLicenseServiceRequest(pugi::xml_document& initLicReq, std::string operatorURL)
{
pugi::xml_node decl = initLicReq.append_child(pugi::node_declaration);
decl.append_attribute("version") = "1.0";
pugi::xml_node root = initLicReq.append_child("adept:licenseServiceRequest");
root.append_attribute("xmlns:adept") = ADOBE_ADEPT_NS;
root.append_attribute("identity") = "user";
appendTextElem(root, "adept:operatorURL", operatorURL);
addNonce(root);
appendTextElem(root, "adept:user", user->getUUID());
std::string signature = signNode(root);
appendTextElem(root, "adept:signature", signature);
}
void DRMProcessor::doOperatorAuth(std::string operatorURL)
{
pugi::xml_document authReq;
buildAuthRequest(authReq);
std::string authURL = operatorURL;
unsigned int fulfillPos = authURL.rfind("Fulfill");
if (fulfillPos == (authURL.size() - (sizeof("Fulfill")-1)))
authURL = authURL.substr(0, fulfillPos-1);
ByteArray replyData = sendRequest(authReq, authURL + "/Auth");
pugi::xml_document initLicReq;
std::string activationURL = user->getProperty("//adept:activationURL");
buildInitLicenseServiceRequest(initLicReq, authURL);
sendRequest(initLicReq, activationURL + "/InitLicenseService");
}
void DRMProcessor::operatorAuth(std::string operatorURL)
{
pugi::xpath_node_set operatorList = user->getProperties("//adept:operatorURL");
for (pugi::xpath_node_set::const_iterator operatorIt = operatorList.begin();
operatorIt != operatorList.end(); ++operatorIt)
{
std::string value = operatorIt->node().first_child().value();
if (trim(value) == operatorURL)
{
GOUROU_LOG(DEBUG, "Already authenticated to operator " << operatorURL);
return;
}
}
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)
{
pugi::xml_node decl = fulfillReq.append_child(pugi::node_declaration);
@@ -270,6 +456,49 @@ namespace gourou
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)
{
if (!user->getPKCS12().length())
@@ -277,7 +506,7 @@ namespace gourou
pugi::xml_document acsmDoc;
if (!acsmDoc.load_file(ACSMFile.c_str(), pugi::parse_ws_pcdata_single))
if (!acsmDoc.load_file(ACSMFile.c_str(), pugi::parse_ws_pcdata_single|pugi::parse_escapes, pugi::encoding_utf8))
EXCEPTION(FF_INVALID_ACSM_FILE, "Invalid ACSM file " << ACSMFile);
GOUROU_LOG(INFO, "Fulfill " << ACSMFile);
@@ -300,59 +529,69 @@ namespace gourou
hmacParentNode.remove_child(hmacNode);
// Compute hash
unsigned char sha_out[SHA1_LEN];
hashNode(rootNode, sha_out);
// Sign with private key
unsigned char res[RSA_KEY_SIZE];
ByteArray deviceKey(device->getDeviceKey(), Device::DEVICE_KEY_SIZE);
std::string pkcs12 = user->getPKCS12();
ByteArray privateRSAKey = ByteArray::fromBase64(pkcs12);
client->RSAPrivateEncrypt(privateRSAKey.data(), privateRSAKey.length(),
RSAInterface::RSA_KEY_PKCS12, deviceKey.toBase64().data(),
sha_out, sizeof(sha_out), res);
if (logLevel >= DEBUG)
{
printf("Sig : ");
for(int i=0; i<(int)sizeof(res); i++)
printf("%02x ", res[i]);
printf("\n");
}
std::string signature = signNode(rootNode);
// Add removed HMAC
appendTextElem(hmacParentNode, hmacNode.name(), hmacNode.first_child().value());
// Add base64 encoded signature
ByteArray signature(res, sizeof(res));
std::string b64Signature = signature.toBase64();
appendTextElem(rootNode, "adept:signature", b64Signature);
appendTextElem(rootNode, "adept:signature", signature);
pugi::xpath_node node = acsmDoc.select_node("//operatorURL");
if (!node)
EXCEPTION(FF_NO_OPERATOR_URL, "OperatorURL not found in ACSM document");
std::string operatorURL = node.node().first_child().value();
operatorURL = trim(operatorURL) + "/Fulfill";
operatorURL = trim(operatorURL);
std::string fulfillURL = operatorURL + "/Fulfill";
ByteArray replyData = sendRequest(fulfillReq, operatorURL);
operatorAuth(fulfillURL);
ByteArray replyData;
try
{
replyData = sendRequest(fulfillReq, fulfillURL);
}
catch (gourou::Exception& e)
{
/*
Operator requires authentication even if it's already in
our operator list
*/
std::string errorMsg(e.what());
if (e.getErrorCode() == GOUROU_ADEPT_ERROR &&
errorMsg.find("E_ADEPT_DISTRIBUTOR_AUTH") != std::string::npos)
{
doOperatorAuth(fulfillURL);
replyData = sendRequest(fulfillReq, fulfillURL);
}
else
{
throw e;
}
}
pugi::xml_document fulfillReply;
fulfillReply.load_string((const char*)replyData.data());
std::string licenseURL = extractTextElem(fulfillReply, "//licenseToken/licenseURL");
fetchLicenseServiceCertificate(licenseURL, operatorURL);
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)
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);
@@ -360,9 +599,60 @@ namespace gourou
std::string rightsStr = item->getRights();
void* handler = client->zipOpen(path);
client->zipWriteFile(handler, "META-INF/rights.xml", rightsStr);
client->zipClose(handler);
if (headers.count("Content-Type") && headers["Content-Type"] == "application/pdf")
res = PDF;
if (res == EPUB)
{
void* handler = client->zipOpen(path);
client->zipWriteFile(handler, "META-INF/rights.xml", rightsStr);
client->zipClose(handler);
}
else if (res == PDF)
{
uPDFParser::Parser parser;
bool EBXHandlerFound = false;
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")
{
EBXHandlerFound = true;
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;
}
}
if (EBXHandlerFound)
parser.write(path, true);
else
{
EXCEPTION(DW_NO_EBX_HANDLER, "EBX_HANDLER not found");
}
}
return res;
}
void DRMProcessor::buildSignInRequest(pugi::xml_document& signInRequest,
@@ -373,7 +663,11 @@ namespace gourou
decl.append_attribute("version") = "1.0";
pugi::xml_node signIn = signInRequest.append_child("adept:signIn");
signIn.append_attribute("xmlns:adept") = ADOBE_ADEPT_NS;
signIn.append_attribute("method") = user->getLoginMethod().c_str();
std::string loginMethod = user->getLoginMethod();
if (loginMethod.size())
signIn.append_attribute("method") = loginMethod.c_str();
else
signIn.append_attribute("method") = "AdobeID";
unsigned char encryptedSignInData[RSA_KEY_SIZE];
const unsigned char* deviceKey = device->getDeviceKey();
@@ -504,37 +798,7 @@ namespace gourou
appendTextElem(targetDevice, "adept:deviceType", (*device)["deviceType"]);
appendTextElem(targetDevice, "adept:fingerprint", (*device)["fingerprint"]);
/*
r4 = tp->time
r3 = 0
r2 = tm->militime
r0 = 0x6f046000
r1 = 0x388a
r3 += high(r4*1000)
r2 += low(r4*1000)
r0 += r2
r1 += r3
*/
struct timeval tv;
gettimeofday(&tv, 0);
uint32_t nonce32[2] = {0x6f046000, 0x388a};
uint64_t bigtime = tv.tv_sec*1000;
nonce32[0] += (bigtime & 0xFFFFFFFF) + (tv.tv_usec/1000);
nonce32[1] += ((bigtime >> 32) & 0xFFFFFFFF);
ByteArray nonce((const unsigned char*)&nonce32, sizeof(nonce32));
uint32_t tmp = 0;
nonce.append((const unsigned char*)&tmp, sizeof(tmp));
appendTextElem(root, "adept:nonce", nonce.toBase64().data());
time_t _time = time(0) + 10*60; // Cur time + 10 minutes
struct tm* tm_info = localtime(&_time);
char buffer[32];
strftime(buffer, sizeof(buffer), "%Y-%m-%dT%H:%M:%SZ", tm_info);
appendTextElem(root, "adept:expiration", buffer);
addNonce(root);
appendTextElem(root, "adept:user", user->getUUID());
}
@@ -547,28 +811,12 @@ namespace gourou
buildActivateReq(activateReq);
// Compute hash
unsigned char sha_out[SHA1_LEN];
pugi::xml_node root = activateReq.select_node("adept:activate").node();
hashNode(root, sha_out);
// Sign with private key
ByteArray RSAKey = ByteArray::fromBase64(user->getPKCS12());
unsigned char res[RSA_KEY_SIZE];
ByteArray deviceKey(device->getDeviceKey(), Device::DEVICE_KEY_SIZE);
client->RSAPrivateEncrypt(RSAKey.data(), RSAKey.length(), RSAInterface::RSA_KEY_PKCS12,
deviceKey.toBase64().c_str(),
sha_out, sizeof(sha_out),
res);
// Add base64 encoded signature
ByteArray signature(res, sizeof(res));
std::string b64Signature = signature.toBase64();
std::string signature = signNode(root);
root = activateReq.select_node("adept:activate").node();
appendTextElem(root, "adept:signature", b64Signature);
appendTextElem(root, "adept:signature", signature);
pugi::xml_document activationDoc;
user->readActivation(activationDoc);
@@ -658,6 +906,19 @@ namespace gourou
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()-26);
close(fd);
}
int DRMProcessor::getLogLevel() {return (int)gourou::logLevel;}
void DRMProcessor::setLogLevel(int logLevel) {gourou::logLevel = (GOUROU_LOG_LEVEL)logLevel;}
}

View File

@@ -14,7 +14,7 @@
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with libgourou. If not, see <http://www.gnu.org/licenses/>.
along with libgourou. If not, see <http://www.gnu.org/licenses/>.
*/
#include <libgourou.h>
@@ -48,7 +48,6 @@ namespace gourou {
uuid = gourou::extractTextElem(activationDoc, "//adept:user", throwOnNull);
deviceUUID = gourou::extractTextElem(activationDoc, "//device", throwOnNull);
deviceFingerprint = gourou::extractTextElem(activationDoc, "//fingerprint", throwOnNull);
certificate = gourou::extractTextElem(activationDoc, "//adept:certificate", throwOnNull);
authenticationCertificate = gourou::extractTextElem(activationDoc, "//adept:authenticationCertificate", throwOnNull);
privateLicenseKey = gourou::extractTextElem(activationDoc, "//adept:privateLicenseKey", throwOnNull);
username = gourou::extractTextElem(activationDoc, "//adept:username", throwOnNull);
@@ -61,6 +60,15 @@ namespace gourou {
if (throwOnNull)
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)
{
@@ -74,7 +82,6 @@ namespace gourou {
std::string& User::getDeviceFingerprint() { return deviceFingerprint; }
std::string& User::getUsername() { return username; }
std::string& User::getLoginMethod() { return loginMethod; }
std::string& User::getCertificate() { return certificate; }
std::string& User::getAuthenticationCertificate() { return authenticationCertificate; }
std::string& User::getPrivateLicenseKey() { return privateLicenseKey; }
@@ -110,6 +117,11 @@ namespace gourou {
return trim(res);
}
pugi::xpath_node_set User::getProperties(const std::string property)
{
return activationDoc.select_nodes(property.c_str());
}
User* User::createUser(DRMProcessor* processor, const std::string& dirName, const std::string& ACSServer)
{
struct stat _stat;
@@ -195,4 +207,13 @@ namespace gourou {
return user;
}
std::string User::getLicenseServiceCertificate(std::string url)
{
if (licenseServiceCertificates.count(trim(url)))
return licenseServiceCertificates[trim(url)];
return "";
}
}

View File

@@ -1,11 +1,13 @@
TARGETS=acsmdownloader activate
TARGETS=acsmdownloader adept_activate
CXXFLAGS=-Wall `pkg-config --cflags Qt5Core Qt5Network` -fPIC -I$(ROOT)/include -I$(ROOT)/lib/pugixml/src/
LDFLAGS=`pkg-config --libs Qt5Core Qt5Network` -L$(ROOT) -lcrypto -lzip -lz
ifneq ($(STATIC_UTILS),)
LDFLAGS=`pkg-config --libs Qt5Core Qt5Network` -L$(ROOT) $(ROOT)/libgourou.a -lcrypto -lzip
LDFLAGS += $(ROOT)/libgourou.a
else
LDFLAGS=`pkg-config --libs Qt5Core Qt5Network` -L$(ROOT) -lgourou -lcrypto -lzip
LDFLAGS += -lgourou
endif
ifneq ($(DEBUG),)
@@ -19,7 +21,7 @@ all: $(TARGETS)
acsmdownloader: drmprocessorclientimpl.cpp acsmdownloader.cpp
$(CXX) $(CXXFLAGS) $^ $(LDFLAGS) -o $@
activate: drmprocessorclientimpl.cpp activate.cpp
adept_activate: drmprocessorclientimpl.cpp adept_activate.cpp
$(CXX) $(CXXFLAGS) $^ $(LDFLAGS) -o $@
clean:

View File

@@ -46,6 +46,7 @@ static const char* deviceFile = "device.xml";
static const char* activationFile = "activation.xml";
static const char* devicekeyFile = "devicesalt";
static const char* acsmFile = 0;
static bool exportPrivateKey = false;
static const char* outputFile = 0;
static const char* outputDir = 0;
static const char* defaultDirs[] = {
@@ -66,41 +67,79 @@ public:
void run()
{
int ret = 0;
try
{
DRMProcessorClientImpl client;
gourou::DRMProcessor processor(&client, deviceFile, activationFile, devicekeyFile);
gourou::User* user = processor.getUser();
gourou::FulfillmentItem* item = processor.fulfill(acsmFile);
std::string filename;
if (!outputFile)
if (exportPrivateKey)
{
filename = item->getMetadata("title");
if (filename == "")
filename = "output.epub";
std::string filename;
if (!outputFile)
filename = std::string("Adobe_PrivateLicenseKey--") + user->getUsername() + ".der";
else
filename += ".epub";
}
filename = outputFile;
if (outputDir)
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
{
QDir dir(outputDir);
if (!dir.exists(outputDir))
dir.mkpath(outputDir);
gourou::FulfillmentItem* item = processor.fulfill(acsmFile);
filename = std::string(outputDir) + "/" + filename;
std::string filename;
if (!outputFile)
{
filename = item->getMetadata("title");
if (filename == "")
filename = "output";
}
else
filename = outputFile;
if (outputDir)
{
QDir dir(outputDir);
if (!dir.exists(outputDir))
dir.mkpath(outputDir);
filename = std::string(outputDir) + "/" + 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;
}
processor.download(item, filename);
std::cout << "Created " << filename << std::endl;
} catch(std::exception& e)
{
std::cout << e.what() << std::endl;
this->app->exit(1);
ret = 1;
}
this->app->exit(0);
this->app->exit(ret);
}
private:
@@ -136,14 +175,15 @@ static void usage(const char* cmd)
{
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] [(-k|--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 << " " << "-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 << " " << "-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 << " " << "-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|--version" << "\t\t" << "Display libgourou version" << std::endl;
std::cout << " " << "-h|--help" << "\t\t" << "This help" << std::endl;
@@ -172,13 +212,14 @@ int main(int argc, char** argv)
{"output-dir", required_argument, 0, 'O' },
{"output-file", required_argument, 0, 'o' },
{"acsm-file", required_argument, 0, 'f' },
{"export-private-key",no_argument, 0, 'e' },
{"verbose", no_argument, 0, 'v' },
{"version", no_argument, 0, 'V' },
{"help", no_argument, 0, 'h' },
{0, 0, 0, 0 }
};
c = getopt_long(argc, argv, "d:a:k:O:o:f:vVh",
c = getopt_long(argc, argv, "d:a:k:O:o:f:evVh",
long_options, &option_index);
if (c == -1)
break;
@@ -202,6 +243,9 @@ int main(int argc, char** argv)
case 'o':
outputFile = optarg;
break;
case 'e':
exportPrivateKey = true;
break;
case 'v':
verbose++;
break;
@@ -219,7 +263,7 @@ int main(int argc, char** argv)
gourou::DRMProcessor::setLogLevel(verbose);
if (!acsmFile || (outputDir && !outputDir[0]) ||
if ((!acsmFile && !exportPrivateKey) || (outputDir && !outputDir[0]) ||
(outputFile && !outputFile[0]))
{
usage(argv[0]);
@@ -230,23 +274,40 @@ int main(int argc, char** argv)
ACSMDownloader downloader(&app);
int i;
bool hasErrors = false;
const char* orig;
for (i=0; i<(int)ARRAY_SIZE(files); i++)
{
orig = *files[i];
*files[i] = findFile(*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;
goto end;
hasErrors = true;
}
}
QFile file(acsmFile);
if (!file.exists())
{
std::cout << "Error : " << acsmFile << " doesn't exists" << std::endl;
ret = -1;
if (hasErrors)
goto end;
if (exportPrivateKey)
{
if (acsmFile)
{
usage(argv[0]);
return -1;
}
}
else
{
QFile file(acsmFile);
if (!file.exists())
{
std::cout << "Error : " << acsmFile << " doesn't exists" << std::endl;
ret = -1;
goto end;
}
}
QThreadPool::globalInstance()->start(&downloader);

View File

@@ -100,10 +100,10 @@ static std::string getpass(const char *prompt, bool show_asterisk=false)
}
class Activate: public QRunnable
class ADEPTActivate: public QRunnable
{
public:
Activate(QCoreApplication* app):
ADEPTActivate(QCoreApplication* app):
app(app)
{
setAutoDelete(false);
@@ -111,6 +111,7 @@ public:
void run()
{
int ret = 0;
try
{
DRMProcessorClientImpl client;
@@ -124,10 +125,10 @@ public:
} catch(std::exception& e)
{
std::cout << e.what() << std::endl;
this->app->exit(1);
ret = 1;
}
this->app->exit(0);
this->app->exit(ret);
}
private:
@@ -262,7 +263,7 @@ int main(int argc, char** argv)
QCoreApplication app(argc, argv);
Activate activate(&app);
ADEPTActivate activate(&app);
QThreadPool::globalInstance()->start(&activate);
ret = app.exec();

View File

@@ -38,6 +38,7 @@
#include <QFile>
#include <zip.h>
#include <zlib.h>
#include <libgourou_common.h>
#include <libgourou_log.h>
@@ -82,7 +83,7 @@ void DRMProcessorClientImpl::randBytes(unsigned char* bytesOut, unsigned int len
}
/* 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()));
QNetworkAccessManager networkManager;
@@ -111,6 +112,24 @@ std::string DRMProcessorClientImpl::sendHTTPRequest(const std::string& URL, cons
while (!reply->isFinished())
app->processEvents();
QByteArray location = reply->rawHeader("Location");
if (location.size() != 0)
{
GOUROU_LOG(gourou::DEBUG, "New location");
return sendHTTPRequest(location.constData(), POSTData, contentType, responseHeaders);
}
if (reply->error() != QNetworkReply::NoError)
EXCEPTION(gourou::CLIENT_NETWORK_ERROR, "Error " << reply->errorString().toStdString());
QList<QByteArray> headers = reply->rawHeaderList();
for (int i = 0; i < headers.size(); ++i) {
if (gourou::logLevel >= gourou::DEBUG)
std::cout << headers[i].constData() << " : " << reply->rawHeader(headers[i]).constData() << std::endl;
if (responseHeaders)
(*responseHeaders)[headers[i].constData()] = reply->rawHeader(headers[i]).constData();
}
replyData = reply->readAll();
if (reply->rawHeader("Content-Type") == "application/vnd.adobe.adept+xml")
{
@@ -213,6 +232,25 @@ void DRMProcessorClientImpl::extractRSAPrivateKey(void* handler, unsigned char**
EVP_PKEY_free(evpKey);
}
void DRMProcessorClientImpl::extractCertificate(const unsigned char* RSAKey, unsigned int RSAKeyLength,
const RSA_KEY_TYPE keyType, const std::string& password,
unsigned char** certOut, unsigned int* certOutLength)
{
PKCS12 * pkcs12;
EVP_PKEY* pkey = 0;
X509* cert = 0;
STACK_OF(X509)* ca;
pkcs12 = d2i_PKCS12(NULL, &RSAKey, RSAKeyLength);
if (!pkcs12)
EXCEPTION(gourou::CLIENT_INVALID_PKCS12, ERR_error_string(ERR_get_error(), NULL));
PKCS12_parse(pkcs12, password.c_str(), &pkey, &cert, &ca);
*certOutLength = i2d_X509(cert, certOut);
EVP_PKEY_free(pkey);
}
/* Crypto interface */
void DRMProcessorClientImpl::AESEncrypt(CHAINING_MODE chaining,
const unsigned char* key, unsigned int keyLength,
@@ -244,8 +282,8 @@ void* DRMProcessorClientImpl::AESEncryptInit(CHAINING_MODE chaining,
break;
default:
EXCEPTION(gourou::CLIENT_BAD_CHAINING, "Unknown chaining mode " << chaining);
break;
}
break;
default:
EVP_CIPHER_CTX_free(ctx);
EXCEPTION(gourou::CLIENT_BAD_KEY_SIZE, "Invalid key size " << keyLength);
@@ -383,3 +421,78 @@ void DRMProcessorClientImpl::zipClose(void* 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);
/* 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,
const RSA_KEY_TYPE keyType, const std::string& password,
@@ -63,6 +63,9 @@ class DRMProcessorClientImpl : public gourou::DRMProcessorClient
virtual void extractRSAPublicKey(void* RSAKeyHandler, unsigned char** keyOut, unsigned int* keyOutLength);
virtual void extractRSAPrivateKey(void* RSAKeyHandler, unsigned char** keyOut, unsigned int* keyOutLength);
virtual void extractCertificate(const unsigned char* RSAKey, unsigned int RSAKeyLength,
const RSA_KEY_TYPE keyType, const std::string& password,
unsigned char** certOut, unsigned int* certOutLength);
/* Crypto interface */
virtual void AESEncrypt(CHAINING_MODE chaining,
@@ -105,6 +108,10 @@ class DRMProcessorClientImpl : public gourou::DRMProcessorClient
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