Initial commit

This commit is contained in:
Grégory Soutadé 2021-07-03 21:57:53 +02:00
commit d5ce4d625e
25 changed files with 4153 additions and 0 deletions

8
.gitignore vendored Normal file
View File

@ -0,0 +1,8 @@
obj
lib
*.a
*.so
*~
utils/acsmdownloader
utils/activate
.adept

165
LICENSE Normal file
View File

@ -0,0 +1,165 @@
GNU LESSER GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
This version of the GNU Lesser General Public License incorporates
the terms and conditions of version 3 of the GNU General Public
License, supplemented by the additional permissions listed below.
0. Additional Definitions.
As used herein, "this License" refers to version 3 of the GNU Lesser
General Public License, and the "GNU GPL" refers to version 3 of the GNU
General Public License.
"The Library" refers to a covered work governed by this License,
other than an Application or a Combined Work as defined below.
An "Application" is any work that makes use of an interface provided
by the Library, but which is not otherwise based on the Library.
Defining a subclass of a class defined by the Library is deemed a mode
of using an interface provided by the Library.
A "Combined Work" is a work produced by combining or linking an
Application with the Library. The particular version of the Library
with which the Combined Work was made is also called the "Linked
Version".
The "Minimal Corresponding Source" for a Combined Work means the
Corresponding Source for the Combined Work, excluding any source code
for portions of the Combined Work that, considered in isolation, are
based on the Application, and not on the Linked Version.
The "Corresponding Application Code" for a Combined Work means the
object code and/or source code for the Application, including any data
and utility programs needed for reproducing the Combined Work from the
Application, but excluding the System Libraries of the Combined Work.
1. Exception to Section 3 of the GNU GPL.
You may convey a covered work under sections 3 and 4 of this License
without being bound by section 3 of the GNU GPL.
2. Conveying Modified Versions.
If you modify a copy of the Library, and, in your modifications, a
facility refers to a function or data to be supplied by an Application
that uses the facility (other than as an argument passed when the
facility is invoked), then you may convey a copy of the modified
version:
a) under this License, provided that you make a good faith effort to
ensure that, in the event an Application does not supply the
function or data, the facility still operates, and performs
whatever part of its purpose remains meaningful, or
b) under the GNU GPL, with none of the additional permissions of
this License applicable to that copy.
3. Object Code Incorporating Material from Library Header Files.
The object code form of an Application may incorporate material from
a header file that is part of the Library. You may convey such object
code under terms of your choice, provided that, if the incorporated
material is not limited to numerical parameters, data structure
layouts and accessors, or small macros, inline functions and templates
(ten or fewer lines in length), you do both of the following:
a) Give prominent notice with each copy of the object code that the
Library is used in it and that the Library and its use are
covered by this License.
b) Accompany the object code with a copy of the GNU GPL and this license
document.
4. Combined Works.
You may convey a Combined Work under terms of your choice that,
taken together, effectively do not restrict modification of the
portions of the Library contained in the Combined Work and reverse
engineering for debugging such modifications, if you also do each of
the following:
a) Give prominent notice with each copy of the Combined Work that
the Library is used in it and that the Library and its use are
covered by this License.
b) Accompany the Combined Work with a copy of the GNU GPL and this license
document.
c) For a Combined Work that displays copyright notices during
execution, include the copyright notice for the Library among
these notices, as well as a reference directing the user to the
copies of the GNU GPL and this license document.
d) Do one of the following:
0) Convey the Minimal Corresponding Source under the terms of this
License, and the Corresponding Application Code in a form
suitable for, and under terms that permit, the user to
recombine or relink the Application with a modified version of
the Linked Version to produce a modified Combined Work, in the
manner specified by section 6 of the GNU GPL for conveying
Corresponding Source.
1) Use a suitable shared library mechanism for linking with the
Library. A suitable mechanism is one that (a) uses at run time
a copy of the Library already present on the user's computer
system, and (b) will operate properly with a modified version
of the Library that is interface-compatible with the Linked
Version.
e) Provide Installation Information, but only if you would otherwise
be required to provide such information under section 6 of the
GNU GPL, and only to the extent that such information is
necessary to install and execute a modified version of the
Combined Work produced by recombining or relinking the
Application with a modified version of the Linked Version. (If
you use option 4d0, the Installation Information must accompany
the Minimal Corresponding Source and Corresponding Application
Code. If you use option 4d1, you must provide the Installation
Information in the manner specified by section 6 of the GNU GPL
for conveying Corresponding Source.)
5. Combined Libraries.
You may place library facilities that are a work based on the
Library side by side in a single library together with other library
facilities that are not Applications and are not covered by this
License, and convey such a combined library under terms of your
choice, if you do both of the following:
a) Accompany the combined library with a copy of the same work based
on the Library, uncombined with any other library facilities,
conveyed under the terms of this License.
b) Give prominent notice with the combined library that part of it
is a work based on the Library, and explaining where to find the
accompanying uncombined form of the same work.
6. Revised Versions of the GNU Lesser General Public License.
The Free Software Foundation may publish revised and/or new versions
of the GNU Lesser General Public License from time to time. Such new
versions will be similar in spirit to the present version, but may
differ in detail to address new problems or concerns.
Each version is given a distinguishing version number. If the
Library as you received it specifies that a certain numbered version
of the GNU Lesser General Public License "or any later version"
applies to it, you have the option of following the terms and
conditions either of that published version or of any later version
published by the Free Software Foundation. If the Library as you
received it does not specify a version number of the GNU Lesser
General Public License, you may choose any version of the GNU Lesser
General Public License ever published by the Free Software Foundation.
If the Library as you received it specifies that a proxy can decide
whether future versions of the GNU Lesser General Public License shall
apply, that proxy's public statement of acceptance of any version is
permanent authorization for you to choose that version for the
Library.

55
Makefile Normal file
View File

@ -0,0 +1,55 @@
AR ?= $(CROSS)ar
CXX ?= $(CROSS)g++
CXXFLAGS=-Wall -fPIC -I./include -I./lib -I./lib/pugixml/src/
LDFLAGS=
ifneq ($(DEBUG),)
CXXFLAGS += -ggdb -O0
else
CXXFLAGS += -O2
endif
SRCDIR := src
INCDIR := inc
BUILDDIR := obj
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
OBJECTS := $(patsubst $(SRCDIR)/%,$(BUILDDIR)/%,$(SOURCES:.$(SRCEXT)=.$(OBJEXT)))
.PHONY: utils
all: lib obj libgourou utils
lib:
mkdir lib
./scripts/setup.sh
obj:
mkdir obj
$(BUILDDIR)/%.$(OBJEXT): $(SRCDIR)/%.$(SRCEXT)
$(CXX) $(CXXFLAGS) -c $^ -o $@
libgourou: libgourou.a libgourou.so
libgourou.a: $(OBJECTS)
$(AR) crs $@ obj/*.o
libgourou.so: libgourou.a
$(CXX) obj/*.o $(LDFLAGS) -o $@ -shared
utils:
make -C utils ROOT=$(PWD) CXX=$(CXX) AR=$(AR) DEBUG=$(DEBUG)
clean:
rm -rf libgourou.a libgourou.so obj
make -C utils clean
ultraclean: clean
rm -rf lib
make -C utils ultraclean

85
README.md Normal file
View File

@ -0,0 +1,85 @@
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.
Architecture
------------
Like RMSDK, libgourou has a client/server scheme. All platform specific functions (crypto, network...) has to be implemented in a client class (that derives from DRMProcessorClient) while server implements ADEPT protocol.
A reference implementation using Qt, OpenSSL and libzip is provided (in _utils_ directory).
Main fucntions to use from gourou::DRMProcessor are :
* Get an ePub from an ACSM file : _fulfill()_ and _download()_
* Create a new device : _createDRMProcessor()_
* Register a new device : _signIn()_ and _activateDevice()_
You can import configuration from (at least) :
* 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.
Dependencies
------------
For libgourou :
* None
For utils :
* QT5Core
* QT5Network
* OpenSSL
* libzip
Compilation
-----------
User _make_
_make_ [CROSS=XXX] [DEBUG=1]
CROSS can define a cross compiler prefix (ie arm-linux-gnueabihf-)
DEBUG can be set to compile in DEBUG mode
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>
Then a _./.adept_ directory is created with all configuration file
To download an ePub :
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$PWD
./utils/acsmdownloader -f <ACSM_FILE>
Copyright
---------
Grégory Soutadé
License
-------
libgourou : LGPL v3 or later
utils : BSD

143
include/bytearray.h Normal file
View File

@ -0,0 +1,143 @@
/*
Copyright 2021 Grégory Soutadé
This file is part of libgourou.
libgourou is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
libgourou is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
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/>.
*/
#ifndef _BYTEARRAY_H_
#define _BYTEARRAY_H_
#include <map>
#include <string>
namespace gourou
{
/**
* @brief Utility class for byte array management.
*
* It's an equivalent of QByteArray
*
* Data handled is first copied in a newly allocated buffer
* and then shared between all copies until last object is destroyed
*/
class ByteArray
{
public:
/**
* @brief Create an empty byte array
*/
ByteArray();
/**
* @brief Initialize ByteArray with a copy of data
*
* @param data Data to be copied
* @param length Length of data
*/
ByteArray(const unsigned char* data, unsigned int length);
/**
* @brief Initialize ByteArray with a copy of data
*
* @param data Data to be copied
* @param length Optional length of data. If length == -1, it use strlen(data) as length
*/
ByteArray(const char* data, int length=-1);
/**
* @brief Initialize ByteArray with a copy of str
*
* @param str Use internal data of str
*/
ByteArray(const std::string& str);
ByteArray(const ByteArray& other);
~ByteArray();
/**
* @brief Encode "other" data into base64 and put it into a ByteArray
*/
static ByteArray fromBase64(const ByteArray& other);
/**
* @brief Encode data into base64 and put it into a ByteArray
*
* @param data Data to be encoded
* @param length Optional length of data. If length == -1, it use strlen(data) as length
*/
static ByteArray fromBase64(const char* data, int length=-1);
/**
* @brief Encode str into base64 and put it into a ByteArray
*
* @param str Use internal data of str
*/
static ByteArray fromBase64(const std::string& str);
/**
* @brief Return a string with base64 encoded internal data
*/
std::string toBase64();
/**
* @brief Return a string with human readable hex encoded internal data
*/
std::string toHex();
/**
* @brief Append a byte to internal data
*/
void append(unsigned char c);
/**
* @brief Append data to internal data
*/
void append(const unsigned char* data, unsigned int length);
/**
* @brief Append str to internal data
*/
void append(const char* str);
/**
* @brief Append str to internal data
*/
void append(const std::string& str);
/**
* @brief Get internal data. Must bot be modified nor freed
*/
const unsigned char* data() {return _data;}
/**
* @brief Get internal data length
*/
unsigned int length() {return _length;}
ByteArray& operator=(const ByteArray& other);
private:
void initData(const unsigned char* data, unsigned int length);
void addRef();
void delRef();
const unsigned char* _data;
unsigned int _length;
static std::map<const unsigned char*, int> refCounter;
};
}
#endif

84
include/device.h Normal file
View File

@ -0,0 +1,84 @@
/*
Copyright 2021 Grégory Soutadé
This file is part of libgourou.
libgourou is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
libgourou is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
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/>.
*/
#ifndef _DEVICE_H_
#define _DEVICE_H_
namespace gourou
{
class DRMProcessor;
/**
* @brief This class is a container for device.xml (device info) and devicesalt (device private key). It should not be used by user.
*/
class Device
{
public:
static const int DEVICE_KEY_SIZE = 16;
static const int DEVICE_SERIAL_LEN = 10;
/**
* @brief Main Device constructor
*
* @param processor Instance of DRMProcessor
* @param deviceFile Path of device.xml
* @param deviceKeyFile Path of devicesalt
*/
Device(DRMProcessor* processor, const std::string& deviceFile, const std::string& deviceKeyFile);
/**
* @brief Return value of devicesalt file (DEVICE_KEY_SIZE len)
*/
const unsigned char* getDeviceKey();
/**
* @brief Get one value of device.xml (deviceClass, deviceSerial, deviceName, deviceType, jobbes, clientOS, clientLocale)
*/
std::string getProperty(const std::string& property, const std::string& _default=std::string(""));
std::string operator[](const std::string& property);
/**
* @brief Create device.xml and devicesalt files when they did not exists
*
* @param processor Instance of DRMProcessor
* @param dirName Directory where to put files (.adept)
* @param hobbes Hobbes (client version) to set
* @param randomSerial Create a random serial (new device each time) or not (serial computed from machine specs)
*/
static Device* createDevice(DRMProcessor* processor, const std::string& dirName, const std::string& hobbes, bool randomSerial);
private:
DRMProcessor* processor;
std::string deviceFile;
std::string deviceKeyFile;
unsigned char deviceKey[DEVICE_KEY_SIZE];
std::map<std::string, std::string> properties;
Device(DRMProcessor* processor);
std::string makeFingerprint(const std::string& serial);
std::string makeSerial(bool random);
void parseDeviceFile();
void parseDeviceKeyFile();
void createDeviceFile(const std::string& hobbes, bool randomSerial);
void createDeviceKeyFile();
};
}
#endif

View File

@ -0,0 +1,350 @@
/*
Copyright 2021 Grégory Soutadé
This file is part of libgourou.
libgourou is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
libgourou is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
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/>.
*/
#ifndef _DRMPROCESSORCLIENT_H_
#define _DRMPROCESSORCLIENT_H_
#include <string>
namespace gourou
{
/**
* @brief All fucntions that must be implemented by a client
* This allow libgourou to have only few external libraries dependencies
* and improve code portability
*/
class DigestInterface
{
public:
/**
* @brief Create a digest handler (for now only SHA1 is used)
*
* @param digestName Digest name to instanciate
*/
virtual void* createDigest(const std::string& digestName) = 0;
/**
* @brief Update digest engine with new data
*
* @param handler Digest handler
* @param data Data to digest
* @param length Length of data
*
* @return OK/KO
*/
virtual int digestUpdate(void* handler, unsigned char* data, unsigned int length) = 0;
/**
* @brief Finalize digest with remained buffered data and destroy handler
*
* @param handler Digest handler
* @param digestOut Digest result (buffer must be pre allocated with right size)
*
* @return OK/KO
*/
virtual int digestFinalize(void* handler, unsigned char* digestOut) = 0;
/**
* @brief Global digest function
*
* @param digestName Digest name to instanciate
* @param data Data to digest
* @param length Length of data
* @param digestOut Digest result (buffer must be pre allocated with right size)
*
* @return OK/KO
*/
virtual int digest(const std::string& digestName, unsigned char* data, unsigned int length, unsigned char* digestOut) = 0;
};
class RandomInterface
{
public:
/**
* @brief Generate random bytes
*
* @param bytesOut Buffer to fill with random bytes
* @param length Length of bytesOut
*/
virtual void randBytes(unsigned char* bytesOut, unsigned int length) = 0;
};
class HTTPInterface
{
public:
/**
* @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
*/
virtual std::string sendHTTPRequest(const std::string& URL, const std::string& POSTData=std::string(""), const std::string& contentType=std::string("")) = 0;
};
class RSAInterface
{
public:
enum RSA_KEY_TYPE {
RSA_KEY_PKCS12 = 0,
RSA_KEY_X509
};
/**
* @brief Encrypt data with RSA private key. Data is padded using PKCS1.5
*
* @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 data Data to encrypt
* @param dataLength Data length
* @param res Encryption result (pre allocated buffer)
*/
virtual void RSAPrivateEncrypt(const unsigned char* RSAKey, unsigned int RSAKeyLength,
const RSA_KEY_TYPE keyType, const std::string& password,
const unsigned char* data, unsigned dataLength,
unsigned char* res) = 0;
/**
* @brief Encrypt data with RSA public key. Data is padded using PKCS1.5
*
* @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 data Data to encrypt
* @param dataLength Data length
* @param res Encryption result (pre allocated buffer)
*/
virtual void RSAPublicEncrypt(const unsigned char* RSAKey, unsigned int RSAKeyLength,
const RSA_KEY_TYPE keyType,
const unsigned char* data, unsigned dataLength,
unsigned char* res) = 0;
/**
* @brief Generate RSA key. Expnonent is fixed (65537 / 0x10001)
*
* @param keyLengthBits Length of key (in bits) to generate
*
* @return generatedKey
*/
virtual void* generateRSAKey(int keyLengthBits) = 0;
/**
* @brief Destroy key previously generated
*
* @param handler Key to destroy
*/
virtual void destroyRSAHandler(void* handler) = 0;
/**
* @brief Extract public key (big number) from RSA handler
*
* @param handler RSA handler (generated key)
* @param keyOut Pre allocated buffer (if *keyOut != 0). If *keyOut is 0, memory is internally allocated (must be freed)
* @param keyOutLength Length of result
*/
virtual void extractRSAPublicKey(void* handler, unsigned char** keyOut, unsigned int* keyOutLength) = 0;
/**
* @brief Extract private key (big number) from RSA handler
*
* @param handler RSA handler (generated key)
* @param keyOut Pre allocated buffer (if *keyOut != 0). If *keyOut is 0, memory is internally allocated (must be freed)
* @param keyOutLength Length of result
*/
virtual void extractRSAPrivateKey(void* handler, unsigned char** keyOut, unsigned int* keyOutLength) = 0;
};
class CryptoInterface
{
public:
enum CHAINING_MODE {
CHAIN_ECB=0,
CHAIN_CBC
};
/**
* @brief Do AES encryption. If length of data is not multiple of 16, PKCS#5 padding is done
*
* @param chaining Chaining mode
* @param key AES key
* @param keyLength AES key length
* @param iv IV key
* @param ivLength IV key length
* @param dataIn Data to encrypt
* @param dataInLength Data length
* @param dataOut Encrypted data
* @param dataOutLength Length of encrypted data
*/
virtual void AESEncrypt(CHAINING_MODE chaining,
const unsigned char* key, unsigned int keyLength,
const unsigned char* iv, unsigned int ivLength,
const unsigned char* dataIn, unsigned int dataInLength,
unsigned char* dataOut, unsigned int* dataOutLength) = 0;
/**
* @brief Init AES CBC encryption
*
* @param chaining Chaining mode
* @param key AES key
* @param keyLength AES key length
* @param iv IV key
* @param ivLength IV key length
*
* @return AES handler
*/
virtual void* AESEncryptInit(CHAINING_MODE chaining,
const unsigned char* key, unsigned int keyLength,
const unsigned char* iv=0, unsigned int ivLength=0) = 0;
/**
* @brief Encrypt data
*
* @param handler AES handler
* @param dataIn Data to encrypt
* @param dataInLength Data length
* @param dataOut Encrypted data
* @param dataOutLength Length of encrypted data
*/
virtual void AESEncryptUpdate(void* handler, const unsigned char* dataIn, unsigned int dataInLength,
unsigned char* dataOut, unsigned int* dataOutLength) = 0;
/**
* @brief Finalize AES encryption (pad and encrypt last block if needed)
* Destroy handler at the end
*
* @param handler AES handler
* @param dataOut Last block of encrypted data
* @param dataOutLength Length of encrypted data
*/
virtual void AESEncryptFinalize(void* handler, unsigned char* dataOut, unsigned int* dataOutLength) = 0;
/**
* @brief Do AES decryption. If length of data is not multiple of 16, PKCS#5 padding is done
*
* @param chaining Chaining mode
* @param key AES key
* @param keyLength AES key length
* @param iv IV key
* @param ivLength IV key length
* @param dataIn Data to encrypt
* @param dataInLength Data length
* @param dataOut Encrypted data
* @param dataOutLength Length of encrypted data
*/
virtual void AESDecrypt(CHAINING_MODE chaining,
const unsigned char* key, unsigned int keyLength,
const unsigned char* iv, unsigned int ivLength,
const unsigned char* dataIn, unsigned int dataInLength,
unsigned char* dataOut, unsigned int* dataOutLength) = 0;
/**
* @brief Init AES decryption
*
* @param chaining Chaining mode
* @param key AES key
* @param keyLength AES key length
* @param iv IV key
* @param ivLength IV key length
*
* @return AES handler
*/
virtual void* AESDecryptInit(CHAINING_MODE chaining,
const unsigned char* key, unsigned int keyLength,
const unsigned char* iv=0, unsigned int ivLength=0) = 0;
/**
* @brief Decrypt data
*
* @param handler AES handler
* @param dataIn Data to decrypt
* @param dataInLength Data length
* @param dataOut Decrypted data
* @param dataOutLength Length of decrypted data
*/
virtual void AESDecryptUpdate(void* handler, const unsigned char* dataIn, unsigned int dataInLength,
unsigned char* dataOut, unsigned int* dataOutLength) = 0;
/**
* @brief Finalize AES decryption (decrypt last block and remove padding if it is set).
* Destroy handler at the end
*
* @param handler AES handler
* @param dataOut Last block decrypted data
* @param dataOutLength Length of decrypted data
*/
virtual void AESDecryptFinalize(void* handler, unsigned char* dataOut, unsigned int* dataOutLength) = 0;
};
class ZIPInterface
{
public:
/**
* @brief Open a zip file and return an handler
*
* @param path Path of zip file
*
* @return ZIP file handler
*/
virtual void* zipOpen(const std::string& path) = 0;
/**
* @brief Read zip internal file
*
* @param handler ZIP file handler
* @param path Internal path inside zip file
*
* @return File content
*/
virtual std::string zipReadFile(void* handler, const std::string& path) = 0;
/**
* @brief Write zip internal file
*
* @param handler ZIP file handler
* @param path Internal path inside zip file
* @param content Internal file content
*/
virtual void zipWriteFile(void* handler, const std::string& path, const std::string& content) = 0;
/**
* @brief Delete zip internal file
*
* @param handler ZIP file handler
* @param path Internal path inside zip file
*/
virtual void zipDeleteFile(void* handler, const std::string& path) = 0;
/**
* @brief Close ZIP file handler
*
* @param handler ZIP file handler
*/
virtual void zipClose(void* handler) = 0;
};
class DRMProcessorClient: public DigestInterface, public RandomInterface, public HTTPInterface, \
public RSAInterface, public CryptoInterface, public ZIPInterface
{};
}
#endif

View File

@ -0,0 +1,65 @@
/*
Copyright 2021 Grégory Soutadé
This file is part of libgourou.
libgourou is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
libgourou is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
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/>.
*/
#ifndef _FULFILLMENT_ITEM_H_
#define _FULFILLMENT_ITEM_H_
#include "bytearray.h"
#include <pugixml.hpp>
namespace gourou
{
class User;
/**
* @brief This class is a container for a fulfillment object
*/
class FulfillmentItem
{
public:
FulfillmentItem(pugi::xml_document& doc, User* user);
/**
* @brief Return metadata value from ACSM metadata section
*
* @param name Name of key to return
*/
std::string getMetadata(std::string name);
/**
* @brief Return rights generated by ACS server (XML format)
*/
std::string getRights();
/**
* @brief Return epub download URL
*/
std::string getDownloadURL();
private:
pugi::xml_node metadatas;
pugi::xml_document rights;
std::string downloadURL;
void buildRights(const pugi::xml_node& licenseToken, User* user);
};
}
#endif

190
include/libgourou.h Normal file
View File

@ -0,0 +1,190 @@
/*
Copyright 2021 Grégory Soutadé
This file is part of libgourou.
libgourou is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
libgourou is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
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/>.
*/
#ifndef _LIBGOUROU_H_
#define _LIBGOUROU_H_
#include "bytearray.h"
#include "device.h"
#include "user.h"
#include "fulfillment_item.h"
#include "drmprocessorclient.h"
#include <pugixml.hpp>
#ifndef HOBBES_DEFAULT_VERSION
#define HOBBES_DEFAULT_VERSION "10.0.4"
#endif
#ifndef DEFAULT_ADEPT_DIR
#define DEFAULT_ADEPT_DIR "./.adept"
#endif
#ifndef ACS_SERVER
#define ACS_SERVER "http://adeactivate.adobe.com/adept"
#endif
namespace gourou
{
/**
* @brief Main class that handle all ADEPTS functions (fulfill, download, signIn, activate)
*/
class DRMProcessor
{
public:
/**
* @brief Main constructor. To be used once all is configured (user has signedIn, device is activated)
*
* @param client Client processor
* @param deviceFile Path of device.xml
* @param activationFile Path of activation.xml
* @param deviceKeyFile Path of devicesalt
*/
DRMProcessor(DRMProcessorClient* client, const std::string& deviceFile, const std::string& activationFile, const std::string& deviceKeyFile);
~DRMProcessor();
/**
* @brief Fulfill ACSM file to server in order to retrieve ePub fulfillment item
*
* @param ACSMFile Path of ACSMFile
*
* @return a FulfillmentItem if all is OK
*/
FulfillmentItem* fulfill(const std::string& ACSMFile);
/**
* @brief Once fulfilled, ePub file needs to be downloaded.
* During this operation, DRM information is added into downloaded file
*
* @param item Item from fulfill() method
* @param path Output file path
*/
void download(FulfillmentItem* item, std::string path);
/**
* @brief SignIn into ACS Server (required to activate device)
*
* @param adobeID AdobeID username
* @param adobePassword Adobe password
*/
void signIn(const std::string& adobeID, const std::string& adobePassword);
/**
* @brief Activate newly created device (user must have successfuly signedIn before)
*/
void activateDevice();
/**
* @brief Create a new ADEPT environment (device.xml, devicesalt and activation.xml).
*
* @param client Client processor
* @param randomSerial Always generate a new device (or not)
* @param dirName Directory where to put generated files (.adept)
* @param hobbes Override hobbes default version
* @param ACSServer Override main ACS server (default adeactivate.adobe.com)
*/
static DRMProcessor* createDRMProcessor(DRMProcessorClient* client,
bool randomSerial=false, const std::string& dirName=std::string(DEFAULT_ADEPT_DIR),
const std::string& hobbes=std::string(HOBBES_DEFAULT_VERSION),
const std::string& ACSServer=ACS_SERVER);
/**
* @brief Get current log level
*/
static int getLogLevel();
/**
* @brief Set log level (higher number for verbose output)
*/
static void setLogLevel(int logLevel);
/**
* Functions used internally, should not be called by user
*/
/**
* @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
*/
ByteArray sendRequest(const std::string& URL, const std::string& POSTData=std::string(), const char* contentType=0);
/**
* @brief Send HTTP POST request to URL with document as POSTData
*/
ByteArray sendRequest(const pugi::xml_document& document, const std::string& url);
/**
* @brief In place encrypt data with private device key
*/
ByteArray encryptWithDeviceKey(const unsigned char* data, unsigned int len);
/**
* @brief In place decrypt data with private device key
*/
ByteArray decryptWithDeviceKey(const unsigned char* data, unsigned int len);
/**
* @brief Return base64 encoded value of RSA public key
*/
std::string serializeRSAPublicKey(void* rsa);
/**
* @brief Return base64 encoded value of RSA private key encrypted with private device key
*/
std::string serializeRSAPrivateKey(void* rsa);
/**
* @brief Get current user
*/
User* getUser() { return user; }
/**
* @brief Get current device
*/
Device* getDevice() { return device; }
/**
* @brief Get current client
*/
DRMProcessorClient* getClient() { return client; }
private:
gourou::DRMProcessorClient* client;
gourou::Device* device;
gourou::User* user;
DRMProcessor(DRMProcessorClient* client);
void pushString(void* sha_ctx, const std::string& string);
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);
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);
};
}
#endif

350
include/libgourou_common.h Normal file
View File

@ -0,0 +1,350 @@
/*
Copyright 2021 Grégory Soutadé
This file is part of libgourou.
libgourou is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
libgourou is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
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/>.
*/
#ifndef _LIBGOUROU_COMMON_H_
#define _LIBGOUROU_COMMON_H_
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <pugixml.hpp>
#include <exception>
#include <iostream>
#include <sstream>
#include <iomanip>
#include <algorithm>
#include <string.h>
#include <libgourou_log.h>
#include "bytearray.h"
namespace gourou
{
/**
* Some common utilities
*/
#define ADOBE_ADEPT_NS "http://ns.adobe.com/adept"
static const int SHA1_LEN = 20;
static const int RSA_KEY_SIZE = 128;
static const int RSA_KEY_SIZE_BITS = (RSA_KEY_SIZE*8);
enum GOUROU_ERROR {
GOUROU_DEVICE_DOES_NOT_MATCH = 0x1000,
GOUROU_INVALID_CLIENT,
GOUROU_TAG_NOT_FOUND,
GOUROU_ADEPT_ERROR,
GOUROU_FILE_ERROR
};
enum FULFILL_ERROR {
FF_ACSM_FILE_NOT_EXISTS = 0x1100,
FF_INVALID_ACSM_FILE,
FF_NO_HMAC_IN_ACSM_FILE,
FF_NOT_ACTIVATED,
FF_NO_OPERATOR_URL
};
enum DOWNLOAD_ERROR {
DW_NO_ITEM = 0x1200,
};
enum SIGNIN_ERROR {
SIGN_INVALID_CREDENTIALS = 0x1300,
};
enum ACTIVATE_ERROR {
ACTIVATE_NOT_SIGNEDIN = 0x1400
};
enum DEV_ERROR {
DEV_MKPATH = 0x2000,
DEV_MAC_ERROR,
DEV_INVALID_DEVICE_FILE,
DEV_INVALID_DEVICE_KEY_FILE,
DEV_INVALID_DEV_PROPERTY,
};
enum USER_ERROR {
USER_MKPATH = 0x3000,
USER_INVALID_ACTIVATION_FILE,
USER_NO_AUTHENTICATION_URL,
USER_NO_PROPERTY,
};
enum FULFILL_ITEM_ERROR {
FFI_INVALID_FULFILLMENT_DATA = 0x4000
};
enum CLIENT_ERROR {
CLIENT_BAD_PARAM = 0x5000,
CLIENT_INVALID_PKCS12,
CLIENT_INVALID_CERTIFICATE,
CLIENT_NO_PRIV_KEY,
CLIENT_RSA_ERROR,
CLIENT_BAD_CHAINING,
CLIENT_BAD_KEY_SIZE,
CLIENT_BAD_ZIP_FILE,
CLIENT_ZIP_ERROR,
CLIENT_GENERIC_EXCEPTION
};
/**
* Generic exception class
*/
class Exception : public std::exception
{
public:
Exception(int code, const char* message, const char* file, int line):
code(code), line(line), file(file)
{
std::stringstream msg;
msg << "Exception code : 0x" << std::setbase(16) << code << std::endl;
msg << "Message : " << message << std::endl;
if (logLevel >= DEBUG)
msg << "File : " << file << ":" << std::setbase(10) << line << std::endl;
fullmessage = strdup(msg.str().c_str());
}
~Exception()
{
free(fullmessage);
}
const char * what () const throw () { return fullmessage; }
int getErrorCode() {return code;}
private:
int code, line;
const char* message, *file;
char* fullmessage;
};
/**
* @brief Throw an exception
*/
#define EXCEPTION(code, message) \
{std::stringstream __msg;__msg << message; throw gourou::Exception(code, __msg.str().c_str(), __FILE__, __LINE__);}
/**
* Stream writer for pugi::xml
*/
class StringXMLWriter : public pugi::xml_writer
{
public:
virtual void write(const void* data, size_t size)
{
result.append(static_cast<const char*>(data), size);
}
const std::string& getResult() {return result;}
private:
std::string result;
};
static const char* ws = " \t\n\r\f\v";
/**
* @brief trim from end of string (right)
*/
inline std::string& rtrim(std::string& s, const char* t = ws)
{
s.erase(s.find_last_not_of(t) + 1);
return s;
}
/**
* @brief trim from beginning of string (left)
*/
inline std::string& ltrim(std::string& s, const char* t = ws)
{
s.erase(0, s.find_first_not_of(t));
return s;
}
/**
* @brief trim from both ends of string (right then left)
*/
inline std::string& trim(std::string& s, const char* t = ws)
{
return ltrim(rtrim(s, t), t);
}
/**
* @brief Extract text node from tag in document
* It can throw an exception if tag does not exists
* or just return an empty value
*/
static inline std::string extractTextElem(const pugi::xml_document& 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
*
* @param root Root node where to put child
* @param name Tag name for child
* @param value Text child value of tag element
*/
static inline void appendTextElem(pugi::xml_node& root, const std::string& name, const std::string& value)
{
pugi::xml_node node = root.append_child(name.c_str());
node.append_child(pugi::node_pcdata).set_value(value.c_str());
}
/**
* @brief Write data in a file. If it already exists, it's truncated
*/
static inline void writeFile(std::string path, const unsigned char* data, unsigned int length)
{
int fd = open(path.c_str(), O_CREAT|O_WRONLY|O_TRUNC, S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH);
if (fd <= 0)
EXCEPTION(GOUROU_FILE_ERROR, "Unable to create " << path);
if (write(fd, data, length) != length)
EXCEPTION(GOUROU_FILE_ERROR, "Write error for file " << path);
close (fd);
}
/**
* @brief Write data in a file. If it already exists, it's truncated
*/
static inline void writeFile(std::string path, ByteArray& data)
{
writeFile(path, data.data(), data.length());
}
/**
* @brief Write data in a file. If it already exists, it's truncated
*/
static inline void writeFile(std::string path, const std::string& data)
{
writeFile(path, (const unsigned char*)data.c_str(), data.length());
}
/**
* Read data from file
*/
static inline void readFile(std::string path, const unsigned char* data, unsigned int length)
{
int fd = open(path.c_str(), O_RDONLY);
if (fd <= 0)
EXCEPTION(GOUROU_FILE_ERROR, "Unable to open " << path);
if (read(fd, (void*)data, length) != length)
EXCEPTION(GOUROU_FILE_ERROR, "Read error for file " << path);
close (fd);
}
#define PATH_MAX_STRING_SIZE 256
// https://gist.github.com/ChisholmKyle/0cbedcd3e64132243a39
/* recursive mkdir */
static inline int mkdir_p(const char *dir, const mode_t mode) {
char tmp[PATH_MAX_STRING_SIZE];
char *p = NULL;
struct stat sb;
size_t len;
/* copy path */
len = strnlen (dir, PATH_MAX_STRING_SIZE);
if (len == 0 || len == PATH_MAX_STRING_SIZE) {
return -1;
}
memcpy (tmp, dir, len);
tmp[len] = '\0';
/* remove trailing slash */
if(tmp[len - 1] == '/') {
tmp[len - 1] = '\0';
}
/* check if path exists and is a directory */
if (stat (tmp, &sb) == 0) {
if (S_ISDIR (sb.st_mode)) {
return 0;
}
}
/* recursive mkdir */
for(p = tmp + 1; *p; p++) {
if(*p == '/') {
*p = 0;
/* test path */
if (stat(tmp, &sb) != 0) {
/* path does not exist - create directory */
if (mkdir(tmp, mode) < 0) {
return -1;
}
} else if (!S_ISDIR(sb.st_mode)) {
/* not a directory */
return -1;
}
*p = '/';
}
}
/* test path */
if (stat(tmp, &sb) != 0) {
/* path does not exist - create directory */
if (mkdir(tmp, mode) < 0) {
return -1;
}
} else if (!S_ISDIR(sb.st_mode)) {
/* not a directory */
return -1;
}
return 0;
}
}
#endif

50
include/libgourou_log.h Normal file
View File

@ -0,0 +1,50 @@
/*
Copyright 2021 Grégory Soutadé
This file is part of libgourou.
libgourou is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
libgourou is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
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/>.
*/
#ifndef _LIBGOUROU_LOG_H_
#define _LIBGOUROU_LOG_H_
#include <iostream>
namespace gourou {
enum GOUROU_LOG_LEVEL {
ERROR,
WARN,
INFO,
DEBUG,
TRACE
};
extern GOUROU_LOG_LEVEL logLevel;
#define GOUROU_LOG(__lvl, __msg) if (__lvl <= gourou::logLevel) {std::cout << __msg << std::endl << std::flush;}
#define GOUROU_LOG_FUNC() GOUROU_LOG(TRACE, __FUNCTION__ << "() @ " << __FILE__ << ":" << __LINE__)
/**
* @brief Get current log level
*/
GOUROU_LOG_LEVEL getLogLevel();
/**
* @brief Set log level
*/
void setLogLevel(GOUROU_LOG_LEVEL level);
}