57 Commits
v0.1 ... v0.5.2

Author SHA1 Message Date
Grégory Soutadé
16a13eed89 Update version & README 2022-02-22 21:26:19 +01:00
Grégory Soutadé
479869b7f2 Fix a use after free in adept_activate : pass string destroyed too early 2022-02-22 20:58:40 +01:00
Grégory Soutadé
41f1a1e980 Fix a bug in adept_activate : cannot ask for interactive password if output directory already exists 2022-02-22 20:58:36 +01:00
Grégory Soutadé
9648157bf7 Remove README_package.md 2022-02-22 20:58:34 +01:00
Grégory Soutadé
a97a915bc8 Rework HTTP request loop events (Thanks Milian) and display download progression 2022-02-22 20:58:32 +01:00
Grégory Soutadé
a623a3d796 Skip files with inflate errors during ePub decryption 2022-02-22 20:58:30 +01:00
Grégory Soutadé
7d161133c3 Check for key size before files decryption 2022-02-22 20:58:26 +01:00
Grégory Soutadé
7d93817e49 Add PKCS8 error 2022-02-22 20:58:14 +01:00
Grégory Soutadé
ef62fb921a Update version 2022-01-04 20:25:18 +01:00
Grégory Soutadé
e4c05bd6b3 Send a warning when libgourou can't handle encryption algorithm during a DRM removal attempt (+ keep encryption.xml in output) 2021-12-23 21:12:03 +01:00
Grégory Soutadé
f33891ef1c Rework ByteArray::resize() to keep buffer data 2021-12-23 21:11:18 +01:00
Grégory Soutadé
9f62cf3447 Merge branch 'master' of soutade.fr:libgourou 2021-12-18 17:46:56 +01:00
Grégory Soutadé
36553cdd2c Add support for anonymous accounts 2021-12-18 17:43:47 +01:00
Grégory Soutadé
ad6da2f8ab Update .gitignore 2021-12-18 17:42:53 +01:00
Grégory Soutadé
b8a4ca222e Add adept_remove util 2021-12-18 17:42:23 +01:00
Grégory Soutadé
f0ff97f7d7 Add support of DRM removal for PDF 2021-12-18 17:40:24 +01:00
Grégory Soutadé
4fe846f78e Fix error in inflate() implementation. Update zlib error messages 2021-12-18 17:39:01 +01:00
Grégory Soutadé
19aacf98a2 Make Encryption/Decryption method of DRMProcessorClient generic 2021-12-18 17:37:37 +01:00
Grégory Soutadé
a751327dab Add DRM removal for ePub only 2021-12-18 17:34:19 +01:00
Grégory Soutadé
a79bdd1e21 Fix copy/paste error 2021-12-08 20:30:02 +01:00
Grégory Soutadé
55ab41613e Update inflate/deflate with right flag (Z_FINISH, no Z_SYNC_FLUSH) 2021-11-29 15:38:56 +01:00
Grégory Soutadé
8129ec4423 Update version 2021-11-29 09:31:18 +01:00
Grégory Soutadé
9ab66ddba9 Bugfix : rework inflate/deflate that makes produce data 2021-11-29 08:40:12 +01:00
Grégory Soutadé
e5697378e9 Update version 2021-11-27 10:29:22 +01:00
Grégory Soutadé
fd8ce841eb Fix a nasty bug : fulfill reply data must be copied, not just referenced 2021-11-26 20:01:49 +01:00
Grégory Soutadé
8413b844db Forgot to add libgourou.a as a dependency in utils Makefile 2021-11-26 20:01:21 +01:00
Grégory Soutadé
dd6001805f Remove invalid characters from filename before writing a file 2021-11-26 20:00:36 +01:00
Grégory Soutadé
1f6e0ecdc8 Check for application/pdf as a substring and not as a full string for Content-Type and metadata in order to determine downloaded file type 2021-11-26 19:59:57 +01:00
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 1970 additions and 413 deletions

3
.gitignore vendored
View File

@@ -4,5 +4,6 @@ lib
*.so
*~
utils/acsmdownloader
utils/activate
utils/adept_activate
utils/adept_remove
.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
@@ -15,17 +15,20 @@ 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()_
* Remove DRM : _removeDRM()_
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 without adept_remove, you can export your private key and import it within [Calibre](https://calibre-ebook.com/) an its DeDRM plugin.
Dependencies
------------
@@ -47,30 +50,51 @@ 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
-----
You can import configuration from your eReader or create a new one with utils/activate :
You can import configuration from your eReader or create a new one with _utils/adept\_activate_ :
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$PWD
./utils/activate -u <USERNAME>
./utils/adept_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 (for DeDRM software) :
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$PWD
./utils/acsmdownloader --export-private-key [-o adobekey_1.der]
To remove ADEPT DRM :
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$PWD
./utils/adept_remove -f <encryptedFile>
You can get utils full options description with -h or --help switch
Copyright
---------
@@ -85,3 +109,11 @@ License
libgourou : LGPL v3 or later
utils : BSD
Special thanks
--------------
* _Jens_ for all test samples and utils testing
* _Milian_ for debug & code

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_
@@ -32,6 +32,7 @@ namespace gourou
*
* Data handled is first copied in a newly allocated buffer
* and then shared between all copies until last object is destroyed
* (internal reference counter == 0)
*/
class ByteArray
{
@@ -39,8 +40,18 @@ namespace gourou
/**
* @brief Create an empty byte array
*
* @param useMalloc If true, use malloc() instead of new[] for allocation
*/
ByteArray();
ByteArray(bool useMalloc=false);
/**
* @brief Create an empty byte array of length bytes
*
* @param length Length of data
* @param useMalloc If true, use malloc() instead of new[] for allocation
*/
ByteArray(unsigned int length, bool useMalloc=false);
/**
* @brief Initialize ByteArray with a copy of data
@@ -119,14 +130,38 @@ namespace gourou
void append(const std::string& str);
/**
* @brief Get internal data. Must bot be modified nor freed
* @brief Get internal data. Must bot be freed
*/
const unsigned char* data() {return _data;}
unsigned char* data() {return _data;}
/**
* @brief Get internal data and increment internal reference counter.
* Must bot be freed
*/
unsigned char* takeShadowData() {addRef() ; return _data;}
/**
* @brief Release shadow data. It can now be freed by ByteArray
*/
void releaseShadowData() {delRef();}
/**
* @brief Get internal data length
*/
unsigned int length() {return _length;}
unsigned int length() const {return _length;}
/**
* @brief Get internal data length
*/
unsigned int size() const {return length();}
/**
* @brief Increase or decrease internal buffer
* @param length New length of internal buffer
* @param keepData If true copy old data on new buffer, if false,
* create a new buffer with random data
*/
void resize(unsigned int length, bool keepData=true);
ByteArray& operator=(const ByteArray& other);
@@ -135,9 +170,10 @@ namespace gourou
void addRef();
void delRef();
const unsigned char* _data;
bool _useMalloc;
unsigned char* _data;
unsigned int _length;
static std::map<const unsigned char*, int> refCounter;
static std::map<unsigned char*, int> refCounter;
};
}
#endif

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
{
@@ -34,7 +35,7 @@ namespace gourou
{
public:
/**
* @brief Create a digest handler (for now only SHA1 is used)
* @brief Create a digest handler
*
* @param digestName Digest name to instanciate
*/
@@ -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
@@ -124,6 +128,22 @@ namespace gourou
const unsigned char* data, unsigned dataLength,
unsigned char* res) = 0;
/**
* @brief Decrypt 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 RSAPrivateDecrypt(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
*
@@ -173,19 +193,39 @@ 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
{
public:
enum CRYPTO_ALGO {
ALGO_AES=0,
ALGO_RC4
};
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
* @brief Do encryption. If length of data is not multiple of block size, PKCS#5 padding is done
*
* @param algo Algorithm to use
* @param chaining Chaining mode
* @param key AES key
* @param keyLength AES key length
@@ -196,52 +236,53 @@ namespace gourou
* @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;
virtual void Encrypt(CRYPTO_ALGO algo, 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
* @brief Init encryption
*
* @param chaining Chaining mode
* @param key AES key
* @param keyLength AES key length
* @param iv IV key
* @param ivLength IV key length
* @param key Key
* @param keyLength Key length
* @param iv Optional IV key
* @param ivLength Optional 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;
virtual void* EncryptInit(CRYPTO_ALGO algo, 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 handler Crypto 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;
virtual void EncryptUpdate(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)
* @brief Finalizeencryption (pad and encrypt last block if needed)
* Destroy handler at the end
*
* @param handler AES handler
* @param handler Crypto 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;
virtual void EncryptFinalize(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
* @brief Do decryption. If length of data is not multiple of block size, PKCS#5 padding is done
*
* @param algo Algorithm to use
* @param chaining Chaining mode
* @param key AES key
* @param keyLength AES key length
@@ -252,47 +293,47 @@ namespace gourou
* @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;
virtual void Decrypt(CRYPTO_ALGO algo, 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
* @brief Init decryption
*
* @param chaining Chaining mode
* @param key AES key
* @param keyLength AES key length
* @param key Key
* @param keyLength 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;
virtual void* DecryptInit(CRYPTO_ALGO algo, 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 handler Crypto 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;
virtual void DecryptUpdate(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).
* @brief Finalize decryption (decrypt last block and remove padding if it is set).
* Destroy handler at the end
*
* @param handler AES handler
* @param handler Crypto 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;
virtual void DecryptFinalize(void* handler, unsigned char* dataOut, unsigned int* dataOutLength) = 0;
};
@@ -313,19 +354,19 @@ namespace gourou
*
* @param handler ZIP file handler
* @param path Internal path inside zip file
*
* @return File content
* @param result Result buffer
* @param decompress If false, don't decompress read data
*/
virtual std::string zipReadFile(void* handler, const std::string& path) = 0;
virtual void zipReadFile(void* handler, const std::string& path, ByteArray& result, bool decompress=true) = 0;
/**
* @brief Write zip internal file
*
* @param handler ZIP file handler
* @param path Internal path inside zip file
* @param content Internal file content
* @param content File content
*/
virtual void zipWriteFile(void* handler, const std::string& path, const std::string& content) = 0;
virtual void zipWriteFile(void* handler, const std::string& path, ByteArray& content) = 0;
/**
* @brief Delete zip internal file
@@ -341,6 +382,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(gourou::ByteArray& 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(gourou::ByteArray& 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_
@@ -34,6 +34,12 @@ namespace gourou
class FulfillmentItem
{
public:
/**
* @brief Main constructor. Not to be called by user
*
* @param doc Fulfill reply
* @param user User pointer
*/
FulfillmentItem(pugi::xml_document& doc, User* user);
/**
@@ -53,10 +59,17 @@ namespace gourou
*/
std::string getDownloadURL();
/**
* @brief Return resource value
*/
std::string getResource();
private:
pugi::xml_document fulfillDoc;
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.5.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,11 @@ namespace gourou
*/
std::string serializeRSAPrivateKey(void* rsa);
/**
* @brief Export clear private license key into path
*/
void exportPrivateLicenseKey(std::string path);
/**
* @brief Get current user
*/
@@ -173,6 +184,12 @@ namespace gourou
*/
DRMProcessorClient* getClient() { return client; }
/**
* @brief Remove ADEPT DRM.
* Warning: for PDF format, filenameIn must be different than filenameOut
*/
void removeDRM(const std::string& filenameIn, const std::string& filenameOut, ITEM_TYPE type);
private:
gourou::DRMProcessorClient* client;
gourou::Device* device;
@@ -184,10 +201,25 @@ 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);
void decryptADEPTKey(const std::string& encryptedKey, unsigned char* decryptedKey);
void removeEPubDRM(const std::string& filenameIn, const std::string& filenameOut);
void generatePDFObjectKey(int version,
const unsigned char* masterKey, unsigned int masterKeyLength,
int objectId, int objectGenerationNumber,
unsigned char* keyOut);
void removePDFDRM(const std::string& filenameIn, const std::string& filenameOut);
};
}

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,19 @@ namespace gourou
CLIENT_BAD_KEY_SIZE,
CLIENT_BAD_ZIP_FILE,
CLIENT_ZIP_ERROR,
CLIENT_GENERIC_EXCEPTION
CLIENT_GENERIC_EXCEPTION,
CLIENT_NETWORK_ERROR,
CLIENT_INVALID_PKCS8
};
enum DRM_REMOVAL_ERROR {
DRM_ERR_ENCRYPTION_KEY = 0x6000,
DRM_VERSION_NOT_SUPPORTED,
DRM_FILE_ERROR,
DRM_FORMAT_NOT_SUPPORTED,
DRM_IN_OUT_EQUALS,
DRM_MISSING_PARAMETER,
DRM_INVALID_KEY_SIZE
};
/**
@@ -128,6 +140,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 +245,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>
@@ -24,33 +24,48 @@
namespace gourou
{
std::map<const unsigned char*, int> ByteArray::refCounter;
std::map<unsigned char*, int> ByteArray::refCounter;
ByteArray::ByteArray():_data(0), _length(0)
ByteArray::ByteArray(bool useMalloc):_useMalloc(useMalloc), _data(0), _length(0)
{}
ByteArray::ByteArray(const unsigned char* data, unsigned int length)
ByteArray::ByteArray(unsigned int length, bool useMalloc):
_useMalloc(useMalloc)
{
initData(0, length);
}
ByteArray::ByteArray(const unsigned char* data, unsigned int length):
_useMalloc(false)
{
initData(data, length);
}
ByteArray::ByteArray(const char* data, int length)
ByteArray::ByteArray(const char* data, int length):
_useMalloc(false)
{
if (length == -1)
length = strlen(data) + 1;
length = strlen(data);
initData((const unsigned char*)data, (unsigned int) length);
initData((unsigned char*)data, (unsigned int) length);
}
ByteArray::ByteArray(const std::string& str)
ByteArray::ByteArray(const std::string& str):
_useMalloc(false)
{
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)
{
_data = new unsigned char[length];
memcpy((void*)_data, data, length);
if (_useMalloc)
_data = (unsigned char*)malloc(length);
else
_data = new unsigned char[length];
if (data)
memcpy((void*)_data, data, length);
_length = length;
addRef();
@@ -58,6 +73,7 @@ namespace gourou
ByteArray::ByteArray(const ByteArray& other)
{
this->_useMalloc = other._useMalloc;
this->_data = other._data;
this->_length = other._length;
@@ -68,6 +84,7 @@ namespace gourou
{
delRef();
this->_useMalloc = other._useMalloc;
this->_data = other._data;
this->_length = other._length;
@@ -97,7 +114,10 @@ namespace gourou
if (refCounter[_data] == 1)
{
delete[] _data;
if (_useMalloc)
free(_data);
else
delete[] _data;
refCounter.erase(_data);
}
else
@@ -152,22 +172,44 @@ namespace gourou
void ByteArray::append(const unsigned char* data, unsigned int length)
{
const unsigned char* oldData = _data;
unsigned char* newData = new unsigned char[_length+length];
if (!length)
return;
memcpy(newData, oldData, _length);
unsigned int oldLength = _length;
delRef();
resize(_length+length, true);
memcpy(&newData[_length], data, length);
_length += length;
_data = newData;
addRef();
memcpy(&_data[oldLength], data, length);
}
void ByteArray::append(unsigned char c) { append(&c, 1);}
void ByteArray::append(const char* str) { append((const unsigned char*)str, strlen(str));}
void ByteArray::append(const std::string& str) { append((const unsigned char*)str.c_str(), str.length()); }
void ByteArray::resize(unsigned length, bool keepData)
{
if (length == _length)
return;
else if (length < _length)
_length = length ; // Don't touch data
else // New size >
{
unsigned char* newData;
if (_useMalloc)
newData = (unsigned char*)malloc(_length+length);
else
newData = new unsigned char[_length+length];
if (keepData)
memcpy(newData, _data, _length);
delRef();
_length = length;
_data = newData;
addRef();
}
}
}

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>
@@ -24,8 +24,10 @@
namespace gourou
{
FulfillmentItem::FulfillmentItem(pugi::xml_document& doc, User* user)
: fulfillDoc()
{
metadatas = doc.select_node("//metadata").node();
fulfillDoc.reset(doc); /* We must keep a copy */
metadatas = fulfillDoc.select_node("//metadata").node();
if (!metadatas)
EXCEPTION(FFI_INVALID_FULFILLMENT_DATA, "No metadata tag in document");
@@ -36,6 +38,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 +64,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 +98,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>
@@ -131,7 +134,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 +160,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 +232,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 +327,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 +455,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 +505,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,69 +528,134 @@ 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);
GOUROU_LOG(INFO, "Download into " << path);
std::string rightsStr = item->getRights();
ByteArray rightsStr(item->getRights());
void* handler = client->zipOpen(path);
client->zipWriteFile(handler, "META-INF/rights.xml", rightsStr);
client->zipClose(handler);
if (item->getMetadata("format").find("application/pdf") != std::string::npos)
res = PDF;
if (headers.count("Content-Type") &&
headers["Content-Type"].find("application/pdf") != std::string::npos)
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 +666,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 +801,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 +814,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);
@@ -599,10 +850,10 @@ namespace gourou
// Generate IV in front
client->randBytes(encrypted_data, 16);
client->AESEncrypt(CryptoInterface::CHAIN_CBC,
deviceKey, 16, encrypted_data, 16,
data, len,
encrypted_data+16, &outLen);
client->Encrypt(CryptoInterface::ALGO_AES, CryptoInterface::CHAIN_CBC,
deviceKey, 16, encrypted_data, 16,
data, len,
encrypted_data+16, &outLen);
ByteArray res(encrypted_data, outLen+16);
@@ -618,10 +869,10 @@ namespace gourou
const unsigned char* deviceKey = device->getDeviceKey();
unsigned char* decrypted_data = new unsigned char[len-16];
client->AESDecrypt(CryptoInterface::CHAIN_CBC,
deviceKey, 16, data, 16,
data+16, len-16,
decrypted_data, &outLen);
client->Decrypt(CryptoInterface::ALGO_AES, CryptoInterface::CHAIN_CBC,
deviceKey, 16, data, 16,
data+16, len-16,
decrypted_data, &outLen);
ByteArray res(decrypted_data, outLen);
@@ -658,6 +909,332 @@ 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;}
void DRMProcessor::decryptADEPTKey(const std::string& encryptedKey, unsigned char* decryptedKey)
{
if (encryptedKey.size() != 172)
EXCEPTION(DRM_INVALID_KEY_SIZE, "Invalid encrypted key size (" << encryptedKey.size() << "). DRM version not supported");
ByteArray arrayEncryptedKey = ByteArray::fromBase64(encryptedKey);
std::string privateKeyData = user->getPrivateLicenseKey();
ByteArray privateRSAKey = ByteArray::fromBase64(privateKeyData);
ByteArray deviceKey(device->getDeviceKey(), Device::DEVICE_KEY_SIZE);
std::string pkcs12 = user->getPKCS12();
client->RSAPrivateDecrypt(privateRSAKey.data(), privateRSAKey.length(),
RSAInterface::RSA_KEY_PKCS12, deviceKey.toBase64().data(),
arrayEncryptedKey.data(), arrayEncryptedKey.length(), decryptedKey);
if (decryptedKey[0] != 0x00 || decryptedKey[1] != 0x02 ||
decryptedKey[RSA_KEY_SIZE-16-1] != 0x00)
EXCEPTION(DRM_ERR_ENCRYPTION_KEY, "Unable to retrieve encryption key");
}
void DRMProcessor::removeEPubDRM(const std::string& filenameIn, const std::string& filenameOut)
{
ByteArray zipData;
bool removeEncryptionXML = true;
void* zipHandler = client->zipOpen(filenameOut);
client->zipReadFile(zipHandler, "META-INF/rights.xml", zipData);
pugi::xml_document rightsDoc;
rightsDoc.load_string((const char*)zipData.data());
std::string encryptedKey = extractTextElem(rightsDoc, "/adept:rights/licenseToken/encryptedKey");
unsigned char decryptedKey[RSA_KEY_SIZE];
decryptADEPTKey(encryptedKey, decryptedKey);
client->zipReadFile(zipHandler, "META-INF/encryption.xml", zipData);
pugi::xml_document encryptionDoc;
encryptionDoc.load_string((const char*)zipData.data());
pugi::xpath_node_set nodeSet = encryptionDoc.select_nodes("//EncryptedData");
for (pugi::xpath_node_set::const_iterator it = nodeSet.begin();
it != nodeSet.end(); ++it)
{
pugi::xml_node encryptionMethod = it->node().child("EncryptionMethod");
pugi::xml_node cipherReference = it->node().child("CipherData").child("CipherReference");
std::string encryptionType = encryptionMethod.attribute("Algorithm").value();
std::string encryptedFile = cipherReference.attribute("URI").value();
if (encryptionType == "")
{
EXCEPTION(DRM_MISSING_PARAMETER, "Missing Algorithm attribute in encryption.xml");
}
else if (encryptionType == "http://www.w3.org/2001/04/xmlenc#aes128-cbc")
{
if (encryptedFile == "")
{
EXCEPTION(DRM_MISSING_PARAMETER, "Missing URI attribute in encryption.xml");
}
GOUROU_LOG(DEBUG, "Encrypted file " << encryptedFile);
client->zipReadFile(zipHandler, encryptedFile, zipData, false);
unsigned char* _data = zipData.data();
ByteArray clearData(zipData.length()-16+1, true); /* Reserve 1 byte for 'Z' */
unsigned char* _clearData = clearData.data();
gourou::ByteArray inflateData(true);
unsigned int dataOutLength;
client->Decrypt(CryptoInterface::ALGO_AES, CryptoInterface::CHAIN_CBC,
decryptedKey+RSA_KEY_SIZE-16, 16, /* Key */
_data, 16, /* IV */
&_data[16], zipData.length()-16,
_clearData, &dataOutLength);
// Add 'Z' at the end, done in ineptepub.py
_clearData[dataOutLength] = 'Z';
clearData.resize(dataOutLength+1);
try
{
client->inflate(clearData, inflateData);
client->zipWriteFile(zipHandler, encryptedFile, inflateData);
}
catch(gourou::Exception& e)
{
if (e.getErrorCode() == CLIENT_ZIP_ERROR)
{
GOUROU_LOG(ERROR, e.what() << std::endl << "Skip file " << encryptedFile);
}
else
throw e;
}
it->node().parent().remove_child(it->node());
}
else
{
GOUROU_LOG(WARN, "Unsupported encryption algorithm " << encryptionType << ", for file " << encryptedFile);
removeEncryptionXML = false;
}
}
client->zipDeleteFile(zipHandler, "META-INF/rights.xml");
if (removeEncryptionXML)
client->zipDeleteFile(zipHandler, "META-INF/encryption.xml");
else
{
StringXMLWriter xmlWriter;
encryptionDoc.save(xmlWriter, " ");
std::string xmlStr = xmlWriter.getResult();
ByteArray ba(xmlStr);
client->zipWriteFile(zipHandler, "META-INF/encryption.xml", ba);
}
client->zipClose(zipHandler);
}
void DRMProcessor::generatePDFObjectKey(int version,
const unsigned char* masterKey, unsigned int masterKeyLength,
int objectId, int objectGenerationNumber,
unsigned char* keyOut)
{
switch(version)
{
case 4:
ByteArray toHash(masterKey, masterKeyLength);
uint32_t _objectId = objectId;
uint32_t _objectGenerationNumber = objectGenerationNumber;
toHash.append((const unsigned char*)&_objectId, 3); // Fill 3 bytes
toHash.append((const unsigned char*)&_objectGenerationNumber, 2); // Fill 2 bytes
client->digest("md5", toHash.data(), toHash.length(), keyOut);
break;
}
}
void DRMProcessor::removePDFDRM(const std::string& filenameIn, const std::string& filenameOut)
{
uPDFParser::Parser parser;
bool EBXHandlerFound = false;
if (filenameIn == filenameOut)
{
EXCEPTION(DRM_IN_OUT_EQUALS, "PDF IN must be different of PDF OUT");
}
try
{
GOUROU_LOG(DEBUG, "Parse PDF");
parser.parse(filenameIn);
}
catch(std::invalid_argument& e)
{
GOUROU_LOG(ERROR, "Invalid PDF");
return;
}
uPDFParser::Integer* ebxVersion;
std::vector<uPDFParser::Object*> objects = parser.objects();
std::vector<uPDFParser::Object*>::reverse_iterator it;
unsigned char decryptedKey[RSA_KEY_SIZE];
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;
ebxVersion = (uPDFParser::Integer*)(*ebx)["V"];
if (ebxVersion->value() != 4)
{
EXCEPTION(DRM_VERSION_NOT_SUPPORTED, "EBX encryption version not supported " << ebxVersion->value());
}
if (!(ebx->hasKey("ADEPT_LICENSE")))
{
EXCEPTION(DRM_ERR_ENCRYPTION_KEY, "No ADEPT_LICENSE found");
}
uPDFParser::String* licenseObject = (uPDFParser::String*)(*ebx)["ADEPT_LICENSE"];
ByteArray zippedData = ByteArray::fromBase64(licenseObject->value());
ByteArray rightsStr;
client->inflate(zippedData, rightsStr);
pugi::xml_document rightsDoc;
rightsDoc.load_string((const char*)rightsStr.data());
std::string encryptedKey = extractTextElem(rightsDoc, "/adept:rights/licenseToken/encryptedKey");
decryptADEPTKey(encryptedKey, decryptedKey);
break;
}
}
if (!EBXHandlerFound)
{
EXCEPTION(DRM_ERR_ENCRYPTION_KEY, "EBX_HANDLER not found");
}
std::vector<uPDFParser::XRefValue> xrefTable = parser.xrefTable();
std::vector<uPDFParser::XRefValue>::iterator xrefIt;
for(xrefIt = xrefTable.begin(); xrefIt != xrefTable.end(); xrefIt++)
{
GOUROU_LOG(DEBUG, "XREF obj " << (*xrefIt).objectId() << " used " << (*xrefIt).used());
if (!(*xrefIt).used())
continue;
uPDFParser::Object* object = (*xrefIt).object();
if (!object)
{
GOUROU_LOG(DEBUG, "No object");
continue;
}
unsigned char tmpKey[16];
generatePDFObjectKey(ebxVersion->value(),
decryptedKey+RSA_KEY_SIZE-16, 16,
object->objectId(), object->generationNumber(),
tmpKey);
uPDFParser::Dictionary& dictionary = object->dictionary();
std::map<std::string, uPDFParser::DataType*>& dictValues = dictionary.value();
std::map<std::string, uPDFParser::DataType*>::iterator dictIt;
std::map<std::string, uPDFParser::DataType*> decodedStrings;
std::string string;
/* Parse dictionary */
for (dictIt = dictValues.begin(); dictIt != dictValues.end(); dictIt++)
{
uPDFParser::DataType* dictData = dictIt->second;
if (dictData->type() == uPDFParser::DataType::STRING)
{
string = ((uPDFParser::String*) dictData)->unescapedValue();
unsigned char* encryptedData = (unsigned char*)string.c_str();
unsigned int dataLength = string.size();
unsigned char* clearData = new unsigned char[dataLength];
unsigned int dataOutLength;
GOUROU_LOG(DEBUG, "Decrypt string " << dictIt->first << " " << dataLength);
client->Decrypt(CryptoInterface::ALGO_RC4, CryptoInterface::CHAIN_ECB,
tmpKey, 16, /* Key */
NULL, 0, /* IV */
encryptedData, dataLength,
clearData, &dataOutLength);
decodedStrings[dictIt->first] = new uPDFParser::String(
std::string((const char*)clearData, dataOutLength));
delete[] clearData;
}
}
for (dictIt = decodedStrings.begin(); dictIt != decodedStrings.end(); dictIt++)
dictionary.replace(dictIt->first, dictIt->second);
std::vector<uPDFParser::DataType*>::iterator datasIt;
std::vector<uPDFParser::DataType*>& datas = (*xrefIt).object()->data();
uPDFParser::Stream* stream;
for (datasIt = datas.begin(); datasIt != datas.end(); datasIt++)
{
if ((*datasIt)->type() != uPDFParser::DataType::STREAM)
continue;
stream = (uPDFParser::Stream*) (*datasIt);
unsigned char* encryptedData = stream->data();
unsigned int dataLength = stream->dataLength();
unsigned char* clearData = new unsigned char[dataLength];
unsigned int dataOutLength;
GOUROU_LOG(DEBUG, "Decrypt stream id " << object->objectId() << ", size " << stream->dataLength());
client->Decrypt(CryptoInterface::ALGO_RC4, CryptoInterface::CHAIN_ECB,
tmpKey, 16, /* Key */
NULL, 0, /* IV */
encryptedData, dataLength,
clearData, &dataOutLength);
stream->setData(clearData, dataOutLength, true);
}
}
uPDFParser::Object& trailer = parser.getTrailer();
trailer.deleteKey("Encrypt");
parser.write(filenameOut);
}
void DRMProcessor::removeDRM(const std::string& filenameIn, const std::string& filenameOut,
ITEM_TYPE type)
{
if (type == PDF)
removePDFDRM(filenameIn, filenameOut);
else
removeEPubDRM(filenameIn, filenameOut);
}
}

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,10 +48,8 @@ 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);
pugi::xpath_node xpath_node = activationDoc.select_node("//adept:username");
if (xpath_node)
@@ -61,6 +59,20 @@ namespace gourou {
if (throwOnNull)
EXCEPTION(USER_INVALID_ACTIVATION_FILE, "Invalid activation file");
}
if (loginMethod == "anonymous")
username = "anonymous";
else
username = gourou::extractTextElem(activationDoc, "//adept:username", throwOnNull);
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 +86,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 +121,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 +211,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,15 @@
TARGETS=acsmdownloader activate
TARGETS=acsmdownloader adept_activate adept_remove
CXXFLAGS=-Wall `pkg-config --cflags Qt5Core Qt5Network` -fPIC -I$(ROOT)/include -I$(ROOT)/lib/pugixml/src/
STATIC_DEP=
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
STATIC_DEP = $(ROOT)/libgourou.a
else
LDFLAGS=`pkg-config --libs Qt5Core Qt5Network` -L$(ROOT) -lgourou -lcrypto -lzip
LDFLAGS += -lgourou
endif
ifneq ($(DEBUG),)
@@ -14,12 +18,17 @@ else
CXXFLAGS += -O2
endif
COMMON_DEPS = drmprocessorclientimpl.cpp $(STATIC_DEP)
all: $(TARGETS)
acsmdownloader: drmprocessorclientimpl.cpp acsmdownloader.cpp
acsmdownloader: acsmdownloader.cpp $(COMMON_DEPS)
$(CXX) $(CXXFLAGS) $^ $(LDFLAGS) -o $@
activate: drmprocessorclientimpl.cpp activate.cpp
adept_activate: adept_activate.cpp $(COMMON_DEPS)
$(CXX) $(CXXFLAGS) $^ $(LDFLAGS) -o $@
adept_remove: adept_remove.cpp $(COMMON_DEPS)
$(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,84 @@ 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
{
// Remove invalid characters
std::replace(filename.begin(), filename.end(), '/', '_');
}
}
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 +180,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 +217,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 +248,9 @@ int main(int argc, char** argv)
case 'o':
outputFile = optarg;
break;
case 'e':
exportPrivateKey = true;
break;
case 'v':
verbose++;
break;
@@ -219,7 +268,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 +279,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:
@@ -143,15 +144,16 @@ static void usage(const char* cmd)
{
std::cout << "Create new device files used by ADEPT DRM" << std::endl;
std::cout << "Usage: " << cmd << " (-u|--username) username [(-p|--password) password] [(-O|--output-dir) dir] [(-r|--random-serial)] [(-v|--verbose)] [(-h|--help)]" << std::endl << std::endl;
std::cout << "Usage: " << cmd << " (-a|--anonymous) | ( (-u|--username) username [(-p|--password) password] ) [(-O|--output-dir) dir] [(-r|--random-serial)] [(-v|--verbose)] [(-h|--help)]" << std::endl << std::endl;
std::cout << " " << "-a|--anonymous" << "\t" << "Anonymous account, no need for username/password (Use it only with a DRM removal software)" << std::endl;
std::cout << " " << "-u|--username" << "\t\t" << "AdobeID username (ie adobe.com email account)" << std::endl;
std::cout << " " << "-p|--password" << "\t\t" << "AdobeID password (asked if not set via command line) " << std::endl;
std::cout << " " << "-O|--output-dir" << "\t" << "Optional output directory were to put result (default ./.adept). This directory must not already exists" << std::endl;
std::cout << " " << "-H|--hobbes-version" << "\t"<< "Force RMSDK version to a specific value (default: version of current librmsdk)" << std::endl;
std::cout << " " << "-r|--random-serial" << "\t"<< "Generate a random device serial (if not set, it will be dependent of your current configuration)" << std::endl;
std::cout << " " << "-v|--verbose" << "\t\t" << "Increase verbosity, can be set multiple times" << std::endl;
std::cout << " " << "-V|--version" << "\t\t" << "Display libgourou version" << std::endl;
std::cout << " " << "-V|--version" << "\t\t" << "Display libgourou version" << std::endl;
std::cout << " " << "-h|--help" << "\t\t" << "This help" << std::endl;
std::cout << std::endl;
@@ -173,27 +175,32 @@ int main(int argc, char** argv)
int c, ret = -1;
const char* _outputDir = outputDir;
int verbose = gourou::DRMProcessor::getLogLevel();
bool anonymous = false;
while (1) {
int option_index = 0;
static struct option long_options[] = {
{"anonymous", no_argument , 0, 'a' },
{"username", required_argument, 0, 'u' },
{"password", required_argument, 0, 'p' },
{"output-dir", required_argument, 0, 'O' },
{"hobbes-version",required_argument, 0, 'H' },
{"random-serial", no_argument, 0, 'r' },
{"verbose", no_argument, 0, 'v' },
{"version", no_argument, 0, 'V' },
{"version", no_argument, 0, 'V' },
{"help", no_argument, 0, 'h' },
{0, 0, 0, 0 }
};
c = getopt_long(argc, argv, "u:p:O:H:rvVh",
c = getopt_long(argc, argv, "au:p:O:H:rvVh",
long_options, &option_index);
if (c == -1)
break;
switch (c) {
case 'a':
anonymous = true;
break;
case 'u':
username = optarg;
break;
@@ -226,15 +233,22 @@ int main(int argc, char** argv)
gourou::DRMProcessor::setLogLevel(verbose);
if (!username)
if ((!username && !anonymous) ||
(username && anonymous))
{
usage(argv[0]);
return -1;
}
if (anonymous)
{
username = "anonymous";
password = "";
}
if (!_outputDir || _outputDir[0] == 0)
{
outputDir = abspath(DEFAULT_ADEPT_DIR);
outputDir = strdup(abspath(DEFAULT_ADEPT_DIR));
}
else
{
@@ -244,29 +258,53 @@ int main(int argc, char** argv)
QFile file(_outputDir);
// realpath doesn't works if file/dir doesn't exists
if (file.exists())
outputDir = realpath(_outputDir, 0);
outputDir = strdup(realpath(_outputDir, 0));
else
outputDir = abspath(_outputDir);
outputDir = strdup(abspath(_outputDir));
}
else
outputDir = strdup(_outputDir);
}
QCoreApplication app(argc, argv);
QFile file(outputDir);
if (file.exists())
{
int key;
while (true)
{
std::cout << "!! Warning !! : " << outputDir << " already exists." << std::endl;
std::cout << "All your data will be overwrite. Would you like to continue ? [y/N] " << std::flush ;
key = getchar();
if (key == 'n' || key == 'N' || key == '\n' || key == '\r')
goto end;
if (key == 'y' || key == 'Y')
break;
}
// Clean STDIN buf
while ((key = getchar()) != '\n')
;
}
std::string pass;
if (!password)
{
char prompt[128];
std::snprintf(prompt, sizeof(prompt), "Enter password for <%s> : ", username);
std::string pass = getpass((const char*)prompt, false);
pass = getpass((const char*)prompt, false);
password = pass.c_str();
}
QCoreApplication app(argc, argv);
Activate activate(&app);
ADEPTActivate activate(&app);
QThreadPool::globalInstance()->start(&activate);
ret = app.exec();
end:
free((void*)outputDir);
return ret;
}

304
utils/adept_remove.cpp Normal file
View File

@@ -0,0 +1,304 @@
/*
Copyright (c) 2021, Grégory Soutadé
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* Neither the name of the copyright holder nor the
names of its contributors may be used to endorse or promote products
derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY
EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include <unistd.h>
#include <getopt.h>
#include <iostream>
#include <QFile>
#include <QDir>
#include <QCoreApplication>
#include <QRunnable>
#include <QThreadPool>
#include <QTemporaryFile>
#include <libgourou.h>
#include <libgourou_common.h>
#include "drmprocessorclientimpl.h"
#define ARRAY_SIZE(arr) (sizeof(arr)/sizeof(arr[0]))
static const char* deviceFile = "device.xml";
static const char* activationFile = "activation.xml";
static const char* devicekeyFile = "devicesalt";
static const char* inputFile = 0;
static const char* outputFile = 0;
static const char* outputDir = 0;
static const char* defaultDirs[] = {
".adept/",
"./adobe-digital-editions/",
"./.adobe-digital-editions/"
};
static inline bool endsWith(const std::string& s, const std::string& suffix)
{
return s.rfind(suffix) == std::abs((int)(s.size()-suffix.size()));
}
class ADEPTRemove: public QRunnable
{
public:
ADEPTRemove(QCoreApplication* app):
app(app)
{
setAutoDelete(false);
}
void run()
{
int ret = 0;
try
{
gourou::DRMProcessor::ITEM_TYPE type;
DRMProcessorClientImpl client;
gourou::DRMProcessor processor(&client, deviceFile, activationFile, devicekeyFile);
std::string filename;
if (!outputFile)
filename = std::string(inputFile);
else
filename = outputFile;
if (outputDir)
{
QDir dir(outputDir);
if (!dir.exists(outputDir))
dir.mkpath(outputDir);
filename = std::string(outputDir) + "/" + filename;
}
if (endsWith(filename, ".epub"))
type = gourou::DRMProcessor::ITEM_TYPE::EPUB;
else if (endsWith(filename, ".pdf"))
type = gourou::DRMProcessor::ITEM_TYPE::PDF;
else
{
EXCEPTION(gourou::DRM_FORMAT_NOT_SUPPORTED, "Unsupported file format of " << filename);
}
if (inputFile != filename)
{
QFile::remove(filename.c_str());
if (!QFile::copy(inputFile, filename.c_str()))
{
EXCEPTION(gourou::DRM_FILE_ERROR, "Unable to copy " << inputFile << " into " << filename);
}
processor.removeDRM(inputFile, filename, type);
std::cout << "DRM removed into new file " << filename << std::endl;
}
else
{
// Use temp file for PDF
if (type == gourou::DRMProcessor::ITEM_TYPE::PDF)
{
QTemporaryFile tempFile;
tempFile.open();
tempFile.setAutoRemove(false); // In case of failure
processor.removeDRM(inputFile, tempFile.fileName().toStdString(), type);
/* Original file must be removed before doing a copy... */
QFile origFile(inputFile);
origFile.remove();
if (!QFile::copy(tempFile.fileName(), filename.c_str()))
{
EXCEPTION(gourou::DRM_FILE_ERROR, "Unable to copy " << tempFile.fileName().toStdString() << " into " << filename);
}
tempFile.setAutoRemove(true);
}
else
processor.removeDRM(inputFile, filename, type);
std::cout << "DRM removed from " << filename << std::endl;
}
} catch(std::exception& e)
{
std::cout << e.what() << std::endl;
ret = 1;
}
this->app->exit(ret);
}
private:
QCoreApplication* app;
};
static const char* findFile(const char* filename, bool inDefaultDirs=true)
{
QFile file(filename);
if (file.exists())
return strdup(filename);
if (!inDefaultDirs) return 0;
for (int i=0; i<(int)ARRAY_SIZE(defaultDirs); i++)
{
QString path = QString(defaultDirs[i]) + QString(filename);
file.setFileName(path);
if (file.exists())
return strdup(path.toStdString().c_str());
}
return 0;
}
static void version(void)
{
std::cout << "Current libgourou version : " << gourou::DRMProcessor::VERSION << std::endl ;
}
static void usage(const char* cmd)
{
std::cout << "Remove ADEPT DRM (from Adobe) of EPUB/PDF file" << std::endl;
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|--input-file) file(.epub|pdf)" << 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 filename (default inplace DRM removal>)" << std::endl;
std::cout << " " << "-f|--input-file" << "\t" << "EPUB/PDF file to process" << std::endl;
std::cout << " " << "-v|--verbose" << "\t\t" << "Increase verbosity, can be set multiple times" << std::endl;
std::cout << " " << "-V|--version" << "\t\t" << "Display libgourou version" << std::endl;
std::cout << " " << "-h|--help" << "\t\t" << "This help" << std::endl;
std::cout << std::endl;
std::cout << "Device file, activation file and device key file are optionals. If not set, they are looked into :" << std::endl;
std::cout << " * Current directory" << std::endl;
std::cout << " * .adept" << std::endl;
std::cout << " * adobe-digital-editions directory" << std::endl;
std::cout << " * .adobe-digital-editions directory" << std::endl;
}
int main(int argc, char** argv)
{
int c, ret = -1;
const char** files[] = {&devicekeyFile, &deviceFile, &activationFile};
int verbose = gourou::DRMProcessor::getLogLevel();
while (1) {
int option_index = 0;
static struct option long_options[] = {
{"device-file", required_argument, 0, 'd' },
{"activation-file", required_argument, 0, 'a' },
{"device-key-file", required_argument, 0, 'k' },
{"output-dir", required_argument, 0, 'O' },
{"output-file", required_argument, 0, 'o' },
{"input-file", required_argument, 0, 'f' },
{"export-private-key",no_argument, 0, 'e' },
{"verbose", no_argument, 0, 'v' },
{"version", no_argument, 0, 'V' },
{"help", no_argument, 0, 'h' },
{0, 0, 0, 0 }
};
c = getopt_long(argc, argv, "d:a:k:O:o:f:evVh",
long_options, &option_index);
if (c == -1)
break;
switch (c) {
case 'd':
deviceFile = optarg;
break;
case 'a':
activationFile = optarg;
break;
case 'k':
devicekeyFile = optarg;
break;
case 'f':
inputFile = optarg;
break;
case 'O':
outputDir = optarg;
break;
case 'o':
outputFile = optarg;
break;
case 'v':
verbose++;
break;
case 'V':
version();
return 0;
case 'h':
usage(argv[0]);
return 0;
default:
usage(argv[0]);
return -1;
}
}
gourou::DRMProcessor::setLogLevel(verbose);
if (!inputFile || (outputDir && !outputDir[0]) ||
(outputFile && !outputFile[0]))
{
usage(argv[0]);
return -1;
}
QCoreApplication app(argc, argv);
ADEPTRemove remover(&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 : " << orig << " doesn't exists, did you activate your device ?" << std::endl;
ret = -1;
hasErrors = true;
}
}
if (hasErrors)
goto end;
QThreadPool::globalInstance()->start(&remover);
ret = app.exec();
end:
for (i=0; i<(int)ARRAY_SIZE(files); i++)
{
if (*files[i])
free((void*)*files[i]);
}
return ret;
}

View File

@@ -37,6 +37,7 @@
#include <QNetworkAccessManager>
#include <QFile>
#include <zlib.h>
#include <zip.h>
#include <libgourou_common.h>
@@ -46,23 +47,28 @@
/* Digest interface */
void* DRMProcessorClientImpl::createDigest(const std::string& digestName)
{
EVP_MD_CTX *sha_ctx = EVP_MD_CTX_new();
EVP_MD_CTX *md_ctx = EVP_MD_CTX_new();
const EVP_MD* md = EVP_get_digestbyname(digestName.c_str());
EVP_DigestInit(sha_ctx, md);
return sha_ctx;
if (EVP_DigestInit(md_ctx, md) != 1)
{
EVP_MD_CTX_free(md_ctx);
return 0;
}
return md_ctx;
}
int DRMProcessorClientImpl::digestUpdate(void* handler, unsigned char* data, unsigned int length)
{
return EVP_DigestUpdate((EVP_MD_CTX *)handler, data, length);
return (EVP_DigestUpdate((EVP_MD_CTX *)handler, data, length)) ? 0 : -1;
}
int DRMProcessorClientImpl::digestFinalize(void* handler, unsigned char* digestOut)
{
int res = EVP_DigestFinal((EVP_MD_CTX *)handler, digestOut, NULL);
EVP_MD_CTX_free((EVP_MD_CTX *)handler);
return res;
return (res == 1) ? 0 : -1;
}
int DRMProcessorClientImpl::digest(const std::string& digestName, unsigned char* data, unsigned int length, unsigned char* digestOut)
@@ -82,7 +88,21 @@ 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)
#define DISPLAY_THRESHOLD 10*1024 // Threshold to display download progression
static void downloadProgress(qint64 bytesReceived, qint64 bytesTotal) {
// For "big" files only
if (bytesTotal >= DISPLAY_THRESHOLD && gourou::logLevel >= gourou::WARN)
{
int percent = 0;
if (bytesTotal)
percent = (bytesReceived * 100) / bytesTotal;
std::cout << "\rDownload " << percent << "%" << std::flush;
}
}
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;
@@ -106,12 +126,36 @@ std::string DRMProcessorClientImpl::sendHTTPRequest(const std::string& URL, cons
else
reply = networkManager.get(request);
QCoreApplication* app = QCoreApplication::instance();
networkManager.moveToThread(app->thread());
while (!reply->isFinished())
app->processEvents();
QEventLoop loop;
QObject::connect(reply, &QNetworkReply::finished, &loop, &QEventLoop::quit);
// Handled just below
QObject::connect(reply, &QNetworkReply::errorOccurred, &loop, &QEventLoop::quit);
QObject::connect(reply, &QNetworkReply::downloadProgress, &loop, downloadProgress);
loop.exec();
QByteArray location = reply->rawHeader("Location");
if (location.size() != 0)
{
GOUROU_LOG(gourou::DEBUG, "New location");
return sendHTTPRequest(location.constData(), POSTData, contentType, responseHeaders);
}
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 (replyData.size() >= DISPLAY_THRESHOLD && gourou::logLevel >= gourou::WARN)
std::cout << std::endl;
if (reply->rawHeader("Content-Type") == "application/vnd.adobe.adept+xml")
{
GOUROU_LOG(gourou::DEBUG, ">>> " << std::endl << replyData.data());
@@ -144,8 +188,39 @@ void DRMProcessorClientImpl::RSAPrivateEncrypt(const unsigned char* RSAKey, unsi
if (gourou::logLevel >= gourou::DEBUG)
{
printf("Sig : ");
for(int i=0; i<(int)sizeof(res); i++)
printf("Encrypted : ");
for(int i=0; i<ret; i++)
printf("%02x ", res[i]);
printf("\n");
}
}
void DRMProcessorClientImpl::RSAPrivateDecrypt(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)
{
BIO* mem=BIO_new_mem_buf(RSAKey, RSAKeyLength);
PKCS8_PRIV_KEY_INFO* p8inf = d2i_PKCS8_PRIV_KEY_INFO_bio(mem, NULL);
if (!p8inf)
EXCEPTION(gourou::CLIENT_INVALID_PKCS8, ERR_error_string(ERR_get_error(), NULL));
EVP_PKEY* pkey = EVP_PKCS82PKEY(p8inf);
RSA * rsa;
int ret;
rsa = EVP_PKEY_get1_RSA(pkey);
ret = RSA_private_decrypt(dataLength, data, res, rsa, RSA_NO_PADDING);
if (ret < 0)
EXCEPTION(gourou::CLIENT_RSA_ERROR, ERR_error_string(ERR_get_error(), NULL));
if (gourou::logLevel >= gourou::DEBUG)
{
printf("Decrypted : ");
for(int i=0; i<ret; i++)
printf("%02x ", res[i]);
printf("\n");
}
@@ -213,84 +288,126 @@ 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,
const unsigned char* iv, unsigned int ivLength,
const unsigned char* dataIn, unsigned int dataInLength,
unsigned char* dataOut, unsigned int* dataOutLength)
void DRMProcessorClientImpl::Encrypt(CRYPTO_ALGO algo, 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)
{
void* handler = AESEncryptInit(chaining, key, keyLength, iv, ivLength);
AESEncryptUpdate(handler, dataIn, dataInLength, dataOut, dataOutLength);
AESEncryptFinalize(handler, dataOut+*dataOutLength, dataOutLength);
void* handler = EncryptInit(algo, chaining, key, keyLength, iv, ivLength);
EncryptUpdate(handler, dataIn, dataInLength, dataOut, dataOutLength);
EncryptFinalize(handler, dataOut+*dataOutLength, dataOutLength);
}
void* DRMProcessorClientImpl::AESEncryptInit(CHAINING_MODE chaining,
void* DRMProcessorClientImpl::EncryptInit(CRYPTO_ALGO algo, CHAINING_MODE chaining,
const unsigned char* key, unsigned int keyLength,
const unsigned char* iv, unsigned int ivLength)
{
EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new();
if (algo == ALGO_AES)
{
switch(keyLength)
{
case 16:
switch(chaining)
{
case CHAIN_ECB:
EVP_EncryptInit(ctx, EVP_aes_128_ecb(), key, iv);
break;
case CHAIN_CBC:
EVP_EncryptInit(ctx, EVP_aes_128_cbc(), key, iv);
break;
default:
EXCEPTION(gourou::CLIENT_BAD_CHAINING, "Unknown chaining mode " << chaining);
}
break;
default:
EVP_CIPHER_CTX_free(ctx);
EXCEPTION(gourou::CLIENT_BAD_KEY_SIZE, "Invalid key size " << keyLength);
}
}
else if (algo == ALGO_RC4)
{
if (keyLength != 16)
{
EVP_CIPHER_CTX_free(ctx);
EXCEPTION(gourou::CLIENT_BAD_KEY_SIZE, "Invalid key size " << keyLength);
}
EVP_DecryptInit(ctx, EVP_rc4(), key, iv);
}
return ctx;
}
void* DRMProcessorClientImpl::DecryptInit(CRYPTO_ALGO algo, CHAINING_MODE chaining,
const unsigned char* key, unsigned int keyLength,
const unsigned char* iv, unsigned int ivLength)
{
EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new();
switch(keyLength)
if (algo == ALGO_AES)
{
case 16:
switch(chaining)
switch(keyLength)
{
case CHAIN_ECB:
EVP_EncryptInit_ex(ctx, EVP_aes_128_ecb(), NULL, key, iv);
break;
case CHAIN_CBC:
EVP_EncryptInit_ex(ctx, EVP_aes_128_cbc(), NULL, key, iv);
case 16:
switch(chaining)
{
case CHAIN_ECB:
EVP_DecryptInit(ctx, EVP_aes_128_ecb(), key, iv);
break;
case CHAIN_CBC:
EVP_DecryptInit(ctx, EVP_aes_128_cbc(), key, iv);
break;
default:
EXCEPTION(gourou::CLIENT_BAD_CHAINING, "Unknown chaining mode " << chaining);
}
break;
default:
EXCEPTION(gourou::CLIENT_BAD_CHAINING, "Unknown chaining mode " << chaining);
break;
EVP_CIPHER_CTX_free(ctx);
EXCEPTION(gourou::CLIENT_BAD_KEY_SIZE, "Invalid key size " << keyLength);
}
default:
EVP_CIPHER_CTX_free(ctx);
EXCEPTION(gourou::CLIENT_BAD_KEY_SIZE, "Invalid key size " << keyLength);
}
else if (algo == ALGO_RC4)
{
if (keyLength != 16)
{
EVP_CIPHER_CTX_free(ctx);
EXCEPTION(gourou::CLIENT_BAD_KEY_SIZE, "Invalid key size " << keyLength);
}
EVP_DecryptInit(ctx, EVP_rc4(), key, iv);
}
return ctx;
}
void* DRMProcessorClientImpl::AESDecryptInit(CHAINING_MODE chaining,
const unsigned char* key, unsigned int keyLength,
const unsigned char* iv, unsigned int ivLength)
{
EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new();
switch(keyLength)
{
case 16:
switch(chaining)
{
case CHAIN_ECB:
EVP_DecryptInit_ex(ctx, EVP_aes_128_ecb(), NULL, key, iv);
break;
case CHAIN_CBC:
EVP_DecryptInit_ex(ctx, EVP_aes_128_cbc(), NULL, key, iv);
break;
default:
EXCEPTION(gourou::CLIENT_BAD_CHAINING, "Unknown chaining mode " << chaining);
}
break;
default:
EVP_CIPHER_CTX_free(ctx);
EXCEPTION(gourou::CLIENT_BAD_KEY_SIZE, "Invalid key size " << keyLength);
}
return ctx;
}
void DRMProcessorClientImpl::AESEncryptUpdate(void* handler, const unsigned char* dataIn, unsigned int dataInLength,
unsigned char* dataOut, unsigned int* dataOutLength)
void DRMProcessorClientImpl::EncryptUpdate(void* handler, const unsigned char* dataIn, unsigned int dataInLength,
unsigned char* dataOut, unsigned int* dataOutLength)
{
EVP_EncryptUpdate((EVP_CIPHER_CTX*)handler, dataOut, (int*)dataOutLength, dataIn, dataInLength);
}
void DRMProcessorClientImpl::AESEncryptFinalize(void* handler,
unsigned char* dataOut, unsigned int* dataOutLength)
void DRMProcessorClientImpl::EncryptFinalize(void* handler,
unsigned char* dataOut, unsigned int* dataOutLength)
{
int len;
EVP_EncryptFinal_ex((EVP_CIPHER_CTX*)handler, dataOut, &len);
@@ -298,24 +415,24 @@ void DRMProcessorClientImpl::AESEncryptFinalize(void* handler,
EVP_CIPHER_CTX_free((EVP_CIPHER_CTX*)handler);
}
void DRMProcessorClientImpl::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)
void DRMProcessorClientImpl::Decrypt(CRYPTO_ALGO algo, 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)
{
void* handler = AESDecryptInit(chaining, key, keyLength, iv, ivLength);
AESDecryptUpdate(handler, dataIn, dataInLength, dataOut, dataOutLength);
AESDecryptFinalize(handler, dataOut+*dataOutLength, dataOutLength);
void* handler = DecryptInit(algo, chaining, key, keyLength, iv, ivLength);
DecryptUpdate(handler, dataIn, dataInLength, dataOut, dataOutLength);
DecryptFinalize(handler, dataOut+*dataOutLength, dataOutLength);
}
void DRMProcessorClientImpl::AESDecryptUpdate(void* handler, const unsigned char* dataIn, unsigned int dataInLength,
unsigned char* dataOut, unsigned int* dataOutLength)
void DRMProcessorClientImpl::DecryptUpdate(void* handler, const unsigned char* dataIn, unsigned int dataInLength,
unsigned char* dataOut, unsigned int* dataOutLength)
{
EVP_DecryptUpdate((EVP_CIPHER_CTX*)handler, dataOut, (int*)dataOutLength, dataIn, dataInLength);
}
void DRMProcessorClientImpl::AESDecryptFinalize(void* handler, unsigned char* dataOut, unsigned int* dataOutLength)
void DRMProcessorClientImpl::DecryptFinalize(void* handler, unsigned char* dataOut, unsigned int* dataOutLength)
{
int len;
EVP_DecryptFinal_ex((EVP_CIPHER_CTX*)handler, dataOut, &len);
@@ -333,35 +450,39 @@ void* DRMProcessorClientImpl::zipOpen(const std::string& path)
return handler;
}
std::string DRMProcessorClientImpl::zipReadFile(void* handler, const std::string& path)
void DRMProcessorClientImpl::zipReadFile(void* handler, const std::string& path, gourou::ByteArray& result, bool decompress)
{
std::string res;
unsigned char* buffer;
zip_stat_t sb;
if (zip_stat((zip_t *)handler, path.c_str(), 0, &sb) < 0)
EXCEPTION(gourou::CLIENT_ZIP_ERROR, "Zip error " << zip_strerror((zip_t *)handler));
EXCEPTION(gourou::CLIENT_ZIP_ERROR, "Zip error, no file " << path << ", " << zip_strerror((zip_t *)handler));
if (!(sb.valid & (ZIP_STAT_INDEX|ZIP_STAT_SIZE)))
EXCEPTION(gourou::CLIENT_ZIP_ERROR, "Required fields missing");
buffer = new unsigned char[sb.size];
result.resize(sb.size);
zip_file_t *f = zip_fopen_index((zip_t *)handler, sb.index, ZIP_FL_COMPRESSED);
zip_fread(f, buffer, sb.size);
zip_file_t *f = zip_fopen_index((zip_t *)handler, sb.index, (decompress)?0:ZIP_FL_COMPRESSED);
zip_fread(f, result.data(), sb.size);
zip_fclose(f);
res = std::string((char*)buffer, sb.size);
delete[] buffer;
return res;
}
void DRMProcessorClientImpl::zipWriteFile(void* handler, const std::string& path, const std::string& content)
void DRMProcessorClientImpl::zipWriteFile(void* handler, const std::string& path, gourou::ByteArray& content)
{
zip_source_t* s = zip_source_buffer((zip_t*)handler, content.c_str(), content.length(), 0);
if (zip_file_add((zip_t*)handler, path.c_str(), s, ZIP_FL_OVERWRITE|ZIP_FL_ENC_UTF_8) < 0)
zip_int64_t ret;
zip_source_t* s = zip_source_buffer((zip_t*)handler, content.takeShadowData(), content.length(), 1);
zip_int64_t idx = zip_name_locate((zip_t*)handler, path.c_str(), 0);
// File doesn't exists
if (idx == -1)
ret = zip_file_add((zip_t*)handler, path.c_str(), s, 0);
else
ret = zip_file_replace((zip_t*)handler, idx, s, ZIP_FL_OVERWRITE);
if (ret < 0)
{
zip_source_free(s);
EXCEPTION(gourou::CLIENT_ZIP_ERROR, "Zip error " << zip_strerror((zip_t *)handler));
@@ -383,3 +504,93 @@ void DRMProcessorClientImpl::zipClose(void* handler)
{
zip_close((zip_t*)handler);
}
void DRMProcessorClientImpl::inflate(gourou::ByteArray& 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.data(); // input char array
infstream.avail_out = (uInt)dataSize; // size of output
infstream.next_out = (Bytef *)buffer; // output char array
int ret = inflateInit2(&infstream, wbits);
if (ret != Z_OK)
EXCEPTION(gourou::CLIENT_ZIP_ERROR, "Inflate error, code " << zError(ret) << ", msg " << infstream.msg);
ret = ::inflate(&infstream, Z_FINISH);
while (ret == Z_OK || ret == Z_STREAM_END || ret == Z_BUF_ERROR)
{
// Real error
if (ret == Z_BUF_ERROR && infstream.avail_out == (uInt)dataSize)
EXCEPTION(gourou::CLIENT_ZIP_ERROR, "Inflate error, code " << zError(ret) << ", msg " << infstream.msg);
result.append(buffer, dataSize-infstream.avail_out);
if ((ret == Z_OK && infstream.avail_out != 0) || 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_FINISH);
}
if (ret == Z_STREAM_END)
ret = inflateEnd(&infstream);
delete[] buffer;
if (ret != Z_OK && ret != Z_STREAM_END)
EXCEPTION(gourou::CLIENT_ZIP_ERROR, "Inflate error, code " << zError(ret) << ", msg " << infstream.msg);
}
void DRMProcessorClientImpl::deflate(gourou::ByteArray& 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)dataSize;
defstream.next_in = (Bytef *)data.data(); // 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);
if (ret != Z_OK)
EXCEPTION(gourou::CLIENT_ZIP_ERROR, "Deflate error, code " << zError(ret) << ", msg " << defstream.msg);
ret = ::deflate(&defstream, Z_FINISH);
while (ret == Z_OK || ret == Z_STREAM_END)
{
result.append(buffer, dataSize-defstream.avail_out);
if ((ret == Z_OK && defstream.avail_out != 0) || 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_FINISH);
}
if (ret == Z_STREAM_END)
ret = deflateEnd(&defstream);
delete[] buffer;
if (ret != Z_OK && ret != Z_STREAM_END)
EXCEPTION(gourou::CLIENT_ZIP_ERROR, "Deflate error, code " << zError(ret) << ", msg " << defstream.msg);
}

View File

@@ -35,7 +35,7 @@
class DRMProcessorClientImpl : public gourou::DRMProcessorClient
{
public:
public:
/* Digest interface */
virtual void* createDigest(const std::string& digestName);
virtual int digestUpdate(void* handler, unsigned char* data, unsigned int length);
@@ -46,13 +46,18 @@ 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,
const unsigned char* data, unsigned dataLength,
unsigned char* res);
virtual void RSAPrivateDecrypt(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);
virtual void RSAPublicEncrypt(const unsigned char* RSAKey, unsigned int RSAKeyLength,
const RSA_KEY_TYPE keyType,
const unsigned char* data, unsigned dataLength,
@@ -63,48 +68,56 @@ 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,
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);
virtual void Encrypt(CRYPTO_ALGO algo, 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);
virtual void* AESEncryptInit(CHAINING_MODE chaining,
const unsigned char* key, unsigned int keyLength,
const unsigned char* iv=0, unsigned int ivLength=0);
virtual void* EncryptInit(CRYPTO_ALGO algo, CHAINING_MODE chaining,
const unsigned char* key, unsigned int keyLength,
const unsigned char* iv=0, unsigned int ivLength=0);
virtual void AESEncryptUpdate(void* handler, const unsigned char* dataIn, unsigned int dataInLength,
virtual void EncryptUpdate(void* handler, const unsigned char* dataIn, unsigned int dataInLength,
unsigned char* dataOut, unsigned int* dataOutLength);
virtual void AESEncryptFinalize(void* handler, unsigned char* dataOut, unsigned int* dataOutLength);
virtual void EncryptFinalize(void* handler, unsigned char* dataOut, unsigned int* dataOutLength);
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);
virtual void Decrypt(CRYPTO_ALGO algo, 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);
virtual void* AESDecryptInit(CHAINING_MODE chaining,
const unsigned char* key, unsigned int keyLength,
const unsigned char* iv=0, unsigned int ivLength=0);
virtual void* DecryptInit(CRYPTO_ALGO algo, CHAINING_MODE chaining,
const unsigned char* key, unsigned int keyLength,
const unsigned char* iv=0, unsigned int ivLength=0);
virtual void AESDecryptUpdate(void* handler, const unsigned char* dataIn, unsigned int dataInLength,
unsigned char* dataOut, unsigned int* dataOutLength);
virtual void AESDecryptFinalize(void* handler, unsigned char* dataOut, unsigned int* dataOutLength);
virtual void DecryptUpdate(void* handler, const unsigned char* dataIn, unsigned int dataInLength,
unsigned char* dataOut, unsigned int* dataOutLength);
virtual void DecryptFinalize(void* handler, unsigned char* dataOut, unsigned int* dataOutLength);
/* ZIP Interface */
virtual void* zipOpen(const std::string& path);
virtual std::string zipReadFile(void* handler, const std::string& path);
virtual void zipReadFile(void* handler, const std::string& path, gourou::ByteArray& result, bool decompress=true);
virtual void zipWriteFile(void* handler, const std::string& path, const std::string& content);
virtual void zipWriteFile(void* handler, const std::string& path, gourou::ByteArray& content);
virtual void zipDeleteFile(void* handler, const std::string& path);
virtual void zipClose(void* handler);
virtual void inflate(gourou::ByteArray& data, gourou::ByteArray& result,
int wbits=-15);
virtual void deflate(gourou::ByteArray& data, gourou::ByteArray& result,
int wbits=-15, int compressionLevel=8);
};
#endif