38 Commits

Author SHA1 Message Date
Grégory Soutadé
7b6b1471fe Update version 2022-04-23 17:51:19 +02:00
Grégory Soutadé
4f9b2de5a5 Remove use of tempnam function and fix bug (bad check of rename return) 2022-04-23 17:41:54 +02:00
Grégory Soutadé
f568b5d3a8 Update README.md 2022-04-03 09:47:47 +02:00
Grégory Soutadé
8c413b4f34 Update .gitignore 2022-04-03 09:39:46 +02:00
Grégory Soutadé
8fe8ba2808 Add adept_loan_mgt util 2022-04-03 09:39:46 +02:00
Grégory Soutadé
570ad83747 Manage loan tokens 2022-04-03 09:39:46 +02:00
Grégory Soutadé
2e7e352e35 Utils: use trim functions from libgourou_common.h (avoid code duplication) 2022-04-03 09:29:40 +02:00
Grégory Soutadé
9556fe862f Optimization : Add signature node into signNode() instead of returing it and be added after 2022-04-03 09:28:19 +02:00
Grégory Soutadé
2f2e4e193e Add resume option to acsmdownloader 2022-03-23 21:05:56 +01:00
Grégory Soutadé
5d3112bc38 Fix bug in anonymous activation (need to set login method as "anonymous") 2022-03-19 15:19:27 +01:00
Grégory Soutadé
e149af9e17 Handle HTTP request connexion fails and download resuming on lost connection during transfer 2022-03-17 21:56:17 +01:00
Grégory Soutadé
1221b2a95a Add optional fd parameter to sendHTTPRequest() in order to directly write received data in a buffer and not in an intermediate buffer 2022-03-17 21:55:02 +01:00
Grégory Soutadé
2ce6142596 Remove QtCore and QtNetwork, replace them by libcurl + libc 2022-03-16 22:45:33 +01:00
Grégory Soutadé
0f475423c0 Add a private option into adept_remove to provide encryption key 2022-03-12 23:04:16 +01:00
Grégory Soutadé
9b946a62b4 ADEPT remove : Don't decrypt XRef stream if there is one 2022-03-12 23:02:55 +01:00
Grégory Soutadé
432eb6f6cb Add uPDFParser as dependency in Makefile 2022-03-12 22:59:27 +01:00
Grégory Soutadé
85b65f8d61 Pad ADEPT_LICENSE before trying to decode it 2022-03-03 21:07:25 +01:00
Grégory Soutadé
25f5049ab9 PDF DRM removing : Try to parse all objects wether they're or not in xref table 2022-03-02 20:38:31 +01:00
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
24 changed files with 2365 additions and 496 deletions

4
.gitignore vendored
View File

@@ -5,4 +5,6 @@ lib
*~ *~
utils/acsmdownloader utils/acsmdownloader
utils/adept_activate utils/adept_activate
.adept utils/adept_remove
utils/adept_loan_mgt
.adept*

View File

@@ -36,7 +36,7 @@ TARGETDIR := bin
SRCEXT := cpp SRCEXT := cpp
OBJEXT := o 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/loan_token.cpp src/bytearray.cpp src/pugixml.cpp
OBJECTS := $(patsubst $(SRCDIR)/%,$(BUILDDIR)/%,$(SOURCES:.$(SRCEXT)=.$(OBJEXT))) OBJECTS := $(patsubst $(SRCDIR)/%,$(BUILDDIR)/%,$(SOURCES:.$(SRCEXT)=.$(OBJEXT)))
all: lib obj $(TARGETS) all: lib obj $(TARGETS)
@@ -53,8 +53,8 @@ $(BUILDDIR)/%.$(OBJEXT): $(SRCDIR)/%.$(SRCEXT)
libgourou: libgourou.a libgourou.so libgourou: libgourou.a libgourou.so
libgourou.a: $(OBJECTS) libgourou.a: $(OBJECTS) $(UPDFPARSERLIB)
$(AR) crs $@ obj/*.o ./lib/updfparser/obj/*.o $(AR) crs $@ obj/*.o $(UPDFPARSERLIB)
libgourou.so: $(OBJECTS) $(UPDFPARSERLIB) libgourou.so: $(OBJECTS) $(UPDFPARSERLIB)
$(CXX) obj/*.o $(LDFLAGS) -o $@ -shared $(CXX) obj/*.o $(LDFLAGS) -o $@ -shared

View File

@@ -15,7 +15,8 @@ Main fucntions to use from gourou::DRMProcessor are :
* Get an ePub from an ACSM file : _fulfill()_ and _download()_ * Get an ePub from an ACSM file : _fulfill()_ and _download()_
* Create a new device : _createDRMProcessor()_ * Create a new device : _createDRMProcessor()_
* Register a new device : _signIn()_ and _activateDevice()_ * Register a new device : _signIn()_ and _activateDevice()_
* Remove DRM : _removeDRM()_
* Return loaned book : _returnLoan()_
You can import configuration from (at least) : You can import configuration from (at least) :
@@ -26,7 +27,7 @@ Or create a new one. Be careful : there is a limited number of devices that can
ePub are encrypted using a shared key : one account / multiple devices, so you can create and register a device into your computer and read downloaded (and encrypted) ePub file with your eReader configured using the same AdobeID account. ePub are encrypted using a shared key : one account / multiple devices, so you can create and register a device into your computer and read downloaded (and encrypted) ePub file with your eReader configured using the same AdobeID account.
For those who wants to remove DRM, you can export your private key and import it within [Calibre](https://calibre-ebook.com/) an its DeDRM plugin. For those who wants to remove DRM without adept_remove, you can export your private key and import it within [Calibre](https://calibre-ebook.com/) an its DeDRM plugin.
Dependencies Dependencies
@@ -38,8 +39,7 @@ For libgourou :
For utils : For utils :
* QT5Core * libcurl
* QT5Network
* OpenSSL * OpenSSL
* libzip * libzip
@@ -65,13 +65,14 @@ BUILD_SHARED build libgourou.so if 1, nothing if 0, can be combined with BUILD_S
* Default value * Default value
Utils 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 export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$PWD
./utils/activate -u <AdobeID USERNAME> ./utils/adept_activate -u <AdobeID USERNAME>
Then a _./.adept_ directory is created with all configuration file Then a _./.adept_ directory is created with all configuration file
@@ -80,11 +81,29 @@ To download an ePub/PDF :
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$PWD export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$PWD
./utils/acsmdownloader -f <ACSM_FILE> ./utils/acsmdownloader -f <ACSM_FILE>
To export your private key : To export your private key (for DeDRM software) :
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$PWD export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$PWD
./utils/acsmdownloader --export-private-key [-o adobekey_1.der] ./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>
To list loaned books :
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$PWD
./utils/adept_loan_mgt [-l]
To return a loaned book :
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$PWD
./utils/adept_loan_mgt -r <id>
You can get utils full options description with -h or --help switch
Copyright Copyright
--------- ---------
@@ -106,3 +125,4 @@ Special thanks
-------------- --------------
* _Jens_ for all test samples and utils testing * _Jens_ for all test samples and utils testing
* _Milian_ for debug & code

View File

@@ -1,72 +0,0 @@
Introduction
------------
libgourou is a free implementation of Adobe's ADEPT protocol used to add DRM on ePub/PDF files. It overcome the lacks of Adobe support for Linux platforms.
Dependencies
------------
For libgourou :
* None
For utils :
* QT5Core
* QT5Network
* OpenSSL
* libzip
Utils
-----
You can import configuration from your eReader or create a new one with utils/activate :
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$PWD
./activate -u <AdobeID USERNAME>
Then a _./.adept_ directory is created with all configuration file
To download an ePub/PDF :
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$PWD
./acsmdownloader -f <ACSM_FILE>
To export your private key :
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$PWD
./acsmdownloader --export-private-key [-o adobekey_1.der]
Sources
-------
http://indefero.soutade.fr/p/libgourou
Copyright
---------
Grégory Soutadé
License
-------
libgourou : LGPL v3 or later
utils : BSD
Special thanks
--------------
* _Jens_ for all test samples and utils testing

View File

@@ -32,6 +32,7 @@ namespace gourou
* *
* Data handled is first copied in a newly allocated buffer * Data handled is first copied in a newly allocated buffer
* and then shared between all copies until last object is destroyed * and then shared between all copies until last object is destroyed
* (internal reference counter == 0)
*/ */
class ByteArray class ByteArray
{ {
@@ -39,8 +40,18 @@ namespace gourou
/** /**
* @brief Create an empty byte array * @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 * @brief Initialize ByteArray with a copy of data
@@ -119,14 +130,38 @@ namespace gourou
void append(const std::string& str); 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 * @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); ByteArray& operator=(const ByteArray& other);
@@ -135,9 +170,10 @@ namespace gourou
void addRef(); void addRef();
void delRef(); void delRef();
const unsigned char* _data; bool _useMalloc;
unsigned char* _data;
unsigned int _length; unsigned int _length;
static std::map<const unsigned char*, int> refCounter; static std::map<unsigned char*, int> refCounter;
}; };
} }
#endif #endif

View File

@@ -35,7 +35,7 @@ namespace gourou
{ {
public: public:
/** /**
* @brief Create a digest handler (for now only SHA1 is used) * @brief Create a digest handler
* *
* @param digestName Digest name to instanciate * @param digestName Digest name to instanciate
*/ */
@@ -98,10 +98,12 @@ namespace gourou
* @param POSTData POST data if needed, if not set, a GET request is done * @param POSTData POST data if needed, if not set, a GET request is done
* @param contentType Optional content type of POST Data * @param contentType Optional content type of POST Data
* @param responseHeaders Optional Response headers of HTTP request * @param responseHeaders Optional Response headers of HTTP request
* @param fd Optional file descriptor to write request result
* @param resume false if target file should be truncated, true to try resume download (works only in combination with a valid fd)
* *
* @return data of HTTP response * @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(""), std::map<std::string, std::string>* responseHeaders=0) = 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, int fd=0, bool resume=false) = 0;
}; };
class RSAInterface class RSAInterface
@@ -128,6 +130,22 @@ namespace gourou
const unsigned char* data, unsigned dataLength, const unsigned char* data, unsigned dataLength,
unsigned char* res) = 0; 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 * @brief Encrypt data with RSA public key. Data is padded using PKCS1.5
* *
@@ -196,14 +214,20 @@ namespace gourou
class CryptoInterface class CryptoInterface
{ {
public: public:
enum CRYPTO_ALGO {
ALGO_AES=0,
ALGO_RC4
};
enum CHAINING_MODE { enum CHAINING_MODE {
CHAIN_ECB=0, CHAIN_ECB=0,
CHAIN_CBC 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 chaining Chaining mode
* @param key AES key * @param key AES key
* @param keyLength AES key length * @param keyLength AES key length
@@ -214,52 +238,53 @@ namespace gourou
* @param dataOut Encrypted data * @param dataOut Encrypted data
* @param dataOutLength Length of encrypted data * @param dataOutLength Length of encrypted data
*/ */
virtual void AESEncrypt(CHAINING_MODE chaining, virtual void Encrypt(CRYPTO_ALGO algo, CHAINING_MODE chaining,
const unsigned char* key, unsigned int keyLength, const unsigned char* key, unsigned int keyLength,
const unsigned char* iv, unsigned int ivLength, const unsigned char* iv, unsigned int ivLength,
const unsigned char* dataIn, unsigned int dataInLength, const unsigned char* dataIn, unsigned int dataInLength,
unsigned char* dataOut, unsigned int* dataOutLength) = 0; unsigned char* dataOut, unsigned int* dataOutLength) = 0;
/** /**
* @brief Init AES CBC encryption * @brief Init encryption
* *
* @param chaining Chaining mode * @param chaining Chaining mode
* @param key AES key * @param key Key
* @param keyLength AES key length * @param keyLength Key length
* @param iv IV key * @param iv Optional IV key
* @param ivLength IV key length * @param ivLength Optional IV key length
* *
* @return AES handler * @return AES handler
*/ */
virtual void* AESEncryptInit(CHAINING_MODE chaining, virtual void* EncryptInit(CRYPTO_ALGO algo, CHAINING_MODE chaining,
const unsigned char* key, unsigned int keyLength, const unsigned char* key, unsigned int keyLength,
const unsigned char* iv=0, unsigned int ivLength=0) = 0; const unsigned char* iv=0, unsigned int ivLength=0) = 0;
/** /**
* @brief Encrypt data * @brief Encrypt data
* *
* @param handler AES handler * @param handler Crypto handler
* @param dataIn Data to encrypt * @param dataIn Data to encrypt
* @param dataInLength Data length * @param dataInLength Data length
* @param dataOut Encrypted data * @param dataOut Encrypted data
* @param dataOutLength Length of encrypted data * @param dataOutLength Length of encrypted data
*/ */
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) = 0; 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 * Destroy handler at the end
* *
* @param handler AES handler * @param handler Crypto handler
* @param dataOut Last block of encrypted data * @param dataOut Last block of encrypted data
* @param dataOutLength Length 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 chaining Chaining mode
* @param key AES key * @param key AES key
* @param keyLength AES key length * @param keyLength AES key length
@@ -270,47 +295,47 @@ namespace gourou
* @param dataOut Encrypted data * @param dataOut Encrypted data
* @param dataOutLength Length of encrypted data * @param dataOutLength Length of encrypted data
*/ */
virtual void AESDecrypt(CHAINING_MODE chaining, virtual void Decrypt(CRYPTO_ALGO algo, CHAINING_MODE chaining,
const unsigned char* key, unsigned int keyLength, const unsigned char* key, unsigned int keyLength,
const unsigned char* iv, unsigned int ivLength, const unsigned char* iv, unsigned int ivLength,
const unsigned char* dataIn, unsigned int dataInLength, const unsigned char* dataIn, unsigned int dataInLength,
unsigned char* dataOut, unsigned int* dataOutLength) = 0; unsigned char* dataOut, unsigned int* dataOutLength) = 0;
/** /**
* @brief Init AES decryption * @brief Init decryption
* *
* @param chaining Chaining mode * @param chaining Chaining mode
* @param key AES key * @param key Key
* @param keyLength AES key length * @param keyLength Key length
* @param iv IV key * @param iv IV key
* @param ivLength IV key length * @param ivLength IV key length
* *
* @return AES handler * @return AES handler
*/ */
virtual void* AESDecryptInit(CHAINING_MODE chaining, virtual void* DecryptInit(CRYPTO_ALGO algo, CHAINING_MODE chaining,
const unsigned char* key, unsigned int keyLength, const unsigned char* key, unsigned int keyLength,
const unsigned char* iv=0, unsigned int ivLength=0) = 0; const unsigned char* iv=0, unsigned int ivLength=0) = 0;
/** /**
* @brief Decrypt data * @brief Decrypt data
* *
* @param handler AES handler * @param handler Crypto handler
* @param dataIn Data to decrypt * @param dataIn Data to decrypt
* @param dataInLength Data length * @param dataInLength Data length
* @param dataOut Decrypted data * @param dataOut Decrypted data
* @param dataOutLength Length of decrypted data * @param dataOutLength Length of decrypted data
*/ */
virtual void AESDecryptUpdate(void* handler, const unsigned char* dataIn, unsigned int dataInLength, virtual void DecryptUpdate(void* handler, const unsigned char* dataIn, unsigned int dataInLength,
unsigned char* dataOut, unsigned int* dataOutLength) = 0; 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 * Destroy handler at the end
* *
* @param handler AES handler * @param handler Crypto handler
* @param dataOut Last block decrypted data * @param dataOut Last block decrypted data
* @param dataOutLength Length of 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;
}; };
@@ -331,19 +356,19 @@ namespace gourou
* *
* @param handler ZIP file handler * @param handler ZIP file handler
* @param path Internal path inside zip file * @param path Internal path inside zip file
* * @param result Result buffer
* @return File content * @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 * @brief Write zip internal file
* *
* @param handler ZIP file handler * @param handler ZIP file handler
* @param path Internal path inside zip file * @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 * @brief Delete zip internal file
@@ -367,7 +392,7 @@ namespace gourou
* @param result Zipped data * @param result Zipped data
* @param wbits Window bits value for libz * @param wbits Window bits value for libz
*/ */
virtual void inflate(std::string data, gourou::ByteArray& result, virtual void inflate(gourou::ByteArray& data, gourou::ByteArray& result,
int wbits=-15) = 0; int wbits=-15) = 0;
/** /**
@@ -378,7 +403,7 @@ namespace gourou
* @param wbits Window bits value for libz * @param wbits Window bits value for libz
* @param compressionLevel Compression level for libz * @param compressionLevel Compression level for libz
*/ */
virtual void deflate(std::string data, gourou::ByteArray& result, virtual void deflate(gourou::ByteArray& data, gourou::ByteArray& result,
int wbits=-15, int compressionLevel=8) = 0; int wbits=-15, int compressionLevel=8) = 0;
}; };

View File

@@ -20,7 +20,7 @@
#ifndef _FULFILLMENT_ITEM_H_ #ifndef _FULFILLMENT_ITEM_H_
#define _FULFILLMENT_ITEM_H_ #define _FULFILLMENT_ITEM_H_
#include "bytearray.h" #include "loan_token.h"
#include <pugixml.hpp> #include <pugixml.hpp>
@@ -42,6 +42,8 @@ namespace gourou
*/ */
FulfillmentItem(pugi::xml_document& doc, User* user); FulfillmentItem(pugi::xml_document& doc, User* user);
~FulfillmentItem();
/** /**
* @brief Return metadata value from ACSM metadata section * @brief Return metadata value from ACSM metadata section
* *
@@ -64,12 +66,18 @@ namespace gourou
*/ */
std::string getResource(); std::string getResource();
/**
* @brief Return loan token if there is one
*/
LoanToken* getLoanToken();
private: private:
pugi::xml_document fulfillDoc; pugi::xml_document fulfillDoc;
pugi::xml_node metadatas; pugi::xml_node metadatas;
pugi::xml_document rights; pugi::xml_document rights;
std::string downloadURL; std::string downloadURL;
std::string resource; std::string resource;
LoanToken* loanToken;
void buildRights(const pugi::xml_node& licenseToken, User* user); void buildRights(const pugi::xml_node& licenseToken, User* user);
}; };

View File

@@ -40,7 +40,7 @@
#define ACS_SERVER "http://adeactivate.adobe.com/adept" #define ACS_SERVER "http://adeactivate.adobe.com/adept"
#endif #endif
#define LIBGOUROU_VERSION "0.4.4" #define LIBGOUROU_VERSION "0.7.1"
namespace gourou namespace gourou
{ {
@@ -81,10 +81,11 @@ namespace gourou
* *
* @param item Item from fulfill() method * @param item Item from fulfill() method
* @param path Output file path * @param path Output file path
* @param resume false if target file should be truncated, true to try resume download
* *
* @return Type of downloaded item * @return Type of downloaded item
*/ */
ITEM_TYPE download(FulfillmentItem* item, std::string path); ITEM_TYPE download(FulfillmentItem* item, std::string path, bool resume=false);
/** /**
* @brief SignIn into ACS Server (required to activate device) * @brief SignIn into ACS Server (required to activate device)
@@ -99,6 +100,14 @@ namespace gourou
*/ */
void activateDevice(); void activateDevice();
/**
* @brief Return loaned book to server
*
* @param loanID Loan ID received during fulfill
* @param operatorURL URL of operator that loans this book
*/
void returnLoan(const std::string& loanID, const std::string& operatorURL);
/** /**
* @brief Create a new ADEPT environment (device.xml, devicesalt and activation.xml). * @brief Create a new ADEPT environment (device.xml, devicesalt and activation.xml).
* *
@@ -134,10 +143,12 @@ namespace gourou
* @param POSTData POST data if needed, if not set, a GET request is done * @param POSTData POST data if needed, if not set, a GET request is done
* @param contentType Optional content type of POST Data * @param contentType Optional content type of POST Data
* @param responseHeaders Optional Response headers of HTTP request * @param responseHeaders Optional Response headers of HTTP request
* @param fd Optional File descriptor to write received data
* @param resume false if target file should be truncated, true to try resume download (works only in combination of a valid fd)
* *
* @return data of HTTP response * @return data of HTTP response
*/ */
ByteArray sendRequest(const std::string& URL, const std::string& POSTData=std::string(), const char* contentType=0, std::map<std::string, std::string>* responseHeaders=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, int fd=0, bool resume=false);
/** /**
* @brief Send HTTP POST request to URL with document as POSTData * @brief Send HTTP POST request to URL with document as POSTData
@@ -164,6 +175,9 @@ namespace gourou
*/ */
std::string serializeRSAPrivateKey(void* rsa); std::string serializeRSAPrivateKey(void* rsa);
/**
* @brief Export clear private license key into path
*/
void exportPrivateLicenseKey(std::string path); void exportPrivateLicenseKey(std::string path);
/** /**
@@ -181,6 +195,18 @@ namespace gourou
*/ */
DRMProcessorClient* getClient() { return client; } DRMProcessorClient* getClient() { return client; }
/**
* @brief Remove ADEPT DRM
* Warning: for PDF format, filenameIn must be different than filenameOut
*
* @param filenameIn Input file (with ADEPT DRM)
* @param filenameOut Output file (without ADEPT DRM)
* @param type Type of file (ePub or PDF)
* @param encryptionKey Optional encryption key, do not try to decrypt the one inside input file
* @param encryptionKeySize Size of encryption key (if provided)
*/
void removeDRM(const std::string& filenameIn, const std::string& filenameOut, ITEM_TYPE type, const unsigned char* encryptionKey=0, unsigned encryptionKeySize=0);
private: private:
gourou::DRMProcessorClient* client; gourou::DRMProcessorClient* client;
gourou::Device* device; gourou::Device* device;
@@ -192,7 +218,7 @@ namespace gourou
void pushTag(void* sha_ctx, uint8_t tag); 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, void *sha_ctx, std::map<std::string,std::string> nsHash);
void hashNode(const pugi::xml_node& root, unsigned char* sha_out); void hashNode(const pugi::xml_node& root, unsigned char* sha_out);
std::string signNode(const pugi::xml_node& rootNode); void signNode(pugi::xml_node& rootNode);
void addNonce(pugi::xml_node& root); void addNonce(pugi::xml_node& root);
void buildAuthRequest(pugi::xml_document& authReq); void buildAuthRequest(pugi::xml_document& authReq);
void buildInitLicenseServiceRequest(pugi::xml_document& initLicReq, std::string operatorURL); void buildInitLicenseServiceRequest(pugi::xml_document& initLicReq, std::string operatorURL);
@@ -200,10 +226,18 @@ namespace gourou
void operatorAuth(std::string operatorURL); void operatorAuth(std::string operatorURL);
void buildFulfillRequest(pugi::xml_document& acsmDoc, pugi::xml_document& fulfillReq); void buildFulfillRequest(pugi::xml_document& acsmDoc, pugi::xml_document& fulfillReq);
void buildActivateReq(pugi::xml_document& activateReq); void buildActivateReq(pugi::xml_document& activateReq);
void buildReturnReq(pugi::xml_document& returnReq, const std::string& loanID, const std::string& operatorURL);
ByteArray sendFulfillRequest(const pugi::xml_document& document, const std::string& url); ByteArray sendFulfillRequest(const pugi::xml_document& document, const std::string& url);
void buildSignInRequest(pugi::xml_document& signInRequest, const std::string& adobeID, const std::string& adobePassword, const std::string& authenticationCertificate); void buildSignInRequest(pugi::xml_document& signInRequest, const std::string& adobeID, const std::string& adobePassword, const std::string& authenticationCertificate);
void fetchLicenseServiceCertificate(const std::string& licenseURL, void fetchLicenseServiceCertificate(const std::string& licenseURL,
const std::string& operatorURL); const std::string& operatorURL);
void decryptADEPTKey(const std::string& encryptedKey, unsigned char* decryptedKey);
void removeEPubDRM(const std::string& filenameIn, const std::string& filenameOut, const unsigned char* encryptionKey, unsigned encryptionKeySize);
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, const unsigned char* encryptionKey, unsigned encryptionKeySize);
}; };
} }

View File

@@ -55,7 +55,8 @@ namespace gourou
GOUROU_INVALID_CLIENT, GOUROU_INVALID_CLIENT,
GOUROU_TAG_NOT_FOUND, GOUROU_TAG_NOT_FOUND,
GOUROU_ADEPT_ERROR, GOUROU_ADEPT_ERROR,
GOUROU_FILE_ERROR GOUROU_FILE_ERROR,
GOUROU_INVALID_PROPERTY
}; };
enum FULFILL_ERROR { enum FULFILL_ERROR {
@@ -92,10 +93,12 @@ namespace gourou
USER_INVALID_ACTIVATION_FILE, USER_INVALID_ACTIVATION_FILE,
USER_NO_AUTHENTICATION_URL, USER_NO_AUTHENTICATION_URL,
USER_NO_PROPERTY, USER_NO_PROPERTY,
USER_INVALID_INPUT,
}; };
enum FULFILL_ITEM_ERROR { enum FULFILL_ITEM_ERROR {
FFI_INVALID_FULFILLMENT_DATA = 0x4000 FFI_INVALID_FULFILLMENT_DATA = 0x4000,
FFI_INVALID_LOAN_TOKEN
}; };
enum CLIENT_ERROR { enum CLIENT_ERROR {
@@ -110,6 +113,18 @@ namespace gourou
CLIENT_ZIP_ERROR, CLIENT_ZIP_ERROR,
CLIENT_GENERIC_EXCEPTION, CLIENT_GENERIC_EXCEPTION,
CLIENT_NETWORK_ERROR, CLIENT_NETWORK_ERROR,
CLIENT_INVALID_PKCS8,
CLIENT_FILE_ERROR
};
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
}; };
/** /**
@@ -273,15 +288,33 @@ namespace gourou
node.append_child(pugi::node_pcdata).set_value(value.c_str()); node.append_child(pugi::node_pcdata).set_value(value.c_str());
} }
/**
* @brief Open a file descriptor on path. If it already exists and truncate == true, it's truncated
*
* @return Created fd, must be closed
*/
static inline int createNewFile(std::string path, bool truncate=true)
{
int options = O_CREAT|O_WRONLY;
if (truncate)
options |= O_TRUNC;
else
options |= O_APPEND;
int fd = open(path.c_str(), options, S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH);
if (fd <= 0)
EXCEPTION(GOUROU_FILE_ERROR, "Unable to create " << path);
return fd;
}
/** /**
* @brief Write data in a file. If it already exists, it's truncated * @brief Write data in a file. If it already exists, it's truncated
*/ */
static inline void writeFile(std::string path, const unsigned char* data, unsigned int length) static inline void writeFile(std::string path, const unsigned char* data, unsigned int length)
{ {
int fd = open(path.c_str(), O_CREAT|O_WRONLY|O_TRUNC, S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH); int fd = createNewFile(path);
if (fd <= 0)
EXCEPTION(GOUROU_FILE_ERROR, "Unable to create " << path);
if (write(fd, data, length) != length) if (write(fd, data, length) != length)
EXCEPTION(GOUROU_FILE_ERROR, "Write error for file " << path); EXCEPTION(GOUROU_FILE_ERROR, "Write error for file " << path);

54
include/loan_token.h Normal file
View File

@@ -0,0 +1,54 @@
/*
Copyright 2022 Grégory Soutadé
This file is part of libgourou.
libgourou is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
libgourou is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with libgourou. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef _LOAN_TOKEN_H_
#define _LOAN_TOKEN_H_
#include <map>
#include <pugixml.hpp>
namespace gourou
{
/**
* @brief This class is a container for a fulfillment object
*/
class LoanToken
{
public:
/**
* @brief Main constructor. Not to be called by user
*
* @param doc Fulfill reply
*/
LoanToken(pugi::xml_document& doc);
/**
* @brief Get a property (id, operatorURL, validity)
*/
std::string getProperty(const std::string& property, const std::string& _default=std::string(""));
std::string operator[](const std::string& property);
private:
std::map<std::string, std::string> properties;
};
}
#endif

View File

@@ -24,33 +24,48 @@
namespace gourou 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); initData(data, length);
} }
ByteArray::ByteArray(const char* data, int length) ByteArray::ByteArray(const char* data, int length):
_useMalloc(false)
{ {
if (length == -1) if (length == -1)
length = strlen(data); 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()); initData((unsigned char*)str.c_str(), (unsigned int)str.length());
} }
void ByteArray::initData(const unsigned char* data, unsigned int length) void ByteArray::initData(const unsigned char* data, unsigned int length)
{ {
if (_useMalloc)
_data = (unsigned char*)malloc(length);
else
_data = new unsigned char[length]; _data = new unsigned char[length];
if (data)
memcpy((void*)_data, data, length); memcpy((void*)_data, data, length);
_length = length; _length = length;
addRef(); addRef();
@@ -58,6 +73,7 @@ namespace gourou
ByteArray::ByteArray(const ByteArray& other) ByteArray::ByteArray(const ByteArray& other)
{ {
this->_useMalloc = other._useMalloc;
this->_data = other._data; this->_data = other._data;
this->_length = other._length; this->_length = other._length;
@@ -68,6 +84,7 @@ namespace gourou
{ {
delRef(); delRef();
this->_useMalloc = other._useMalloc;
this->_data = other._data; this->_data = other._data;
this->_length = other._length; this->_length = other._length;
@@ -97,6 +114,9 @@ namespace gourou
if (refCounter[_data] == 1) if (refCounter[_data] == 1)
{ {
if (_useMalloc)
free(_data);
else
delete[] _data; delete[] _data;
refCounter.erase(_data); refCounter.erase(_data);
} }
@@ -152,22 +172,44 @@ namespace gourou
void ByteArray::append(const unsigned char* data, unsigned int length) void ByteArray::append(const unsigned char* data, unsigned int length)
{ {
const unsigned char* oldData = _data; if (!length)
unsigned char* newData = new unsigned char[_length+length]; return;
memcpy(newData, oldData, _length); unsigned int oldLength = _length;
delRef(); resize(_length+length, true);
memcpy(&newData[_length], data, length); memcpy(&_data[oldLength], data, length);
_length += length;
_data = newData;
addRef();
} }
void ByteArray::append(unsigned char c) { append(&c, 1);} 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 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::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

@@ -24,7 +24,7 @@
namespace gourou namespace gourou
{ {
FulfillmentItem::FulfillmentItem(pugi::xml_document& doc, User* user) FulfillmentItem::FulfillmentItem(pugi::xml_document& doc, User* user)
: fulfillDoc() : fulfillDoc(), loanToken(0)
{ {
fulfillDoc.reset(doc); /* We must keep a copy */ fulfillDoc.reset(doc); /* We must keep a copy */
metadatas = fulfillDoc.select_node("//metadata").node(); metadatas = fulfillDoc.select_node("//metadata").node();
@@ -50,6 +50,23 @@ namespace gourou
EXCEPTION(FFI_INVALID_FULFILLMENT_DATA, "Any license token in document"); EXCEPTION(FFI_INVALID_FULFILLMENT_DATA, "Any license token in document");
buildRights(licenseToken, user); buildRights(licenseToken, user);
node = doc.select_node("/envelope/fulfillmentResult/returnable").node();
try
{
if (node && node.first_child().value() == std::string("true"))
loanToken = new LoanToken(doc);
}
catch(std::exception& e)
{
GOUROU_LOG(ERROR, "Book is returnable, but contains invalid loan token");
GOUROU_LOG(ERROR, e.what());
}
}
FulfillmentItem::~FulfillmentItem()
{
if (loanToken) delete loanToken;
} }
void FulfillmentItem::buildRights(const pugi::xml_node& licenseToken, User* user) void FulfillmentItem::buildRights(const pugi::xml_node& licenseToken, User* user)
@@ -103,4 +120,9 @@ namespace gourou
{ {
return resource; return resource;
} }
LoanToken* FulfillmentItem::getLoanToken()
{
return loanToken;
}
} }

View File

@@ -35,7 +35,6 @@
#define ASN_TEXT 0x04 #define ASN_TEXT 0x04
#define ASN_ATTRIBUTE 0x05 #define ASN_ATTRIBUTE 0x05
namespace gourou namespace gourou
{ {
GOUROU_LOG_LEVEL logLevel = WARN; GOUROU_LOG_LEVEL logLevel = WARN;
@@ -237,7 +236,7 @@ namespace gourou
} }
} }
std::string DRMProcessor::signNode(const pugi::xml_node& rootNode) void DRMProcessor::signNode(pugi::xml_node& rootNode)
{ {
// Compute hash // Compute hash
unsigned char sha_out[SHA1_LEN]; unsigned char sha_out[SHA1_LEN];
@@ -261,9 +260,8 @@ namespace gourou
printf("\n"); printf("\n");
} }
ByteArray signature(res, sizeof(res)); std::string signature = ByteArray(res, sizeof(res)).toBase64();
appendTextElem(rootNode, "adept:signature", signature);
return signature.toBase64();
} }
void DRMProcessor::addNonce(pugi::xml_node& root) void DRMProcessor::addNonce(pugi::xml_node& root)
@@ -301,11 +299,13 @@ namespace gourou
appendTextElem(root, "adept:expiration", buffer); appendTextElem(root, "adept:expiration", buffer);
} }
ByteArray DRMProcessor::sendRequest(const std::string& URL, const std::string& POSTdata, const char* contentType, std::map<std::string, std::string>* responseHeaders) ByteArray DRMProcessor::sendRequest(const std::string& URL, const std::string& POSTdata, const char* contentType, std::map<std::string, std::string>* responseHeaders, int fd, bool resume)
{ {
if (contentType == 0) if (contentType == 0)
contentType = ""; contentType = "";
std::string reply = client->sendHTTPRequest(URL, POSTdata, contentType, responseHeaders); std::string reply = client->sendHTTPRequest(URL, POSTdata, contentType, responseHeaders, fd, resume);
if (fd) return ByteArray();
pugi::xml_document replyDoc; pugi::xml_document replyDoc;
replyDoc.load_buffer(reply.c_str(), reply.length()); replyDoc.load_buffer(reply.c_str(), reply.length());
@@ -367,8 +367,7 @@ namespace gourou
addNonce(root); addNonce(root);
appendTextElem(root, "adept:user", user->getUUID()); appendTextElem(root, "adept:user", user->getUUID());
std::string signature = signNode(root); signNode(root);
appendTextElem(root, "adept:signature", signature);
} }
void DRMProcessor::doOperatorAuth(std::string operatorURL) void DRMProcessor::doOperatorAuth(std::string operatorURL)
@@ -529,13 +528,11 @@ namespace gourou
hmacParentNode.remove_child(hmacNode); hmacParentNode.remove_child(hmacNode);
std::string signature = signNode(rootNode); signNode(rootNode);
// Add removed HMAC // Add removed HMAC
appendTextElem(hmacParentNode, hmacNode.name(), hmacNode.first_child().value()); appendTextElem(hmacParentNode, hmacNode.name(), hmacNode.first_child().value());
appendTextElem(rootNode, "adept:signature", signature);
pugi::xpath_node node = acsmDoc.select_node("//operatorURL"); pugi::xpath_node node = acsmDoc.select_node("//operatorURL");
if (!node) if (!node)
EXCEPTION(FF_NO_OPERATOR_URL, "OperatorURL not found in ACSM document"); EXCEPTION(FF_NO_OPERATOR_URL, "OperatorURL not found in ACSM document");
@@ -582,7 +579,7 @@ namespace gourou
return new FulfillmentItem(fulfillReply, user); return new FulfillmentItem(fulfillReply, user);
} }
DRMProcessor::ITEM_TYPE DRMProcessor::download(FulfillmentItem* item, std::string path) DRMProcessor::ITEM_TYPE DRMProcessor::download(FulfillmentItem* item, std::string path, bool resume)
{ {
ITEM_TYPE res = EPUB; ITEM_TYPE res = EPUB;
@@ -591,13 +588,15 @@ namespace gourou
std::map<std::string, std::string> headers; std::map<std::string, std::string> headers;
ByteArray replyData = sendRequest(item->getDownloadURL(), "", 0, &headers); int fd = createNewFile(path, !resume);
writeFile(path, replyData); sendRequest(item->getDownloadURL(), "", 0, &headers, fd, resume);
close(fd);
GOUROU_LOG(INFO, "Download into " << path); GOUROU_LOG(INFO, "Download into " << path);
std::string rightsStr = item->getRights(); ByteArray rightsStr(item->getRights());
if (item->getMetadata("format").find("application/pdf") != std::string::npos) if (item->getMetadata("format").find("application/pdf") != std::string::npos)
res = PDF; res = PDF;
@@ -668,7 +667,10 @@ namespace gourou
pugi::xml_node signIn = signInRequest.append_child("adept:signIn"); pugi::xml_node signIn = signInRequest.append_child("adept:signIn");
signIn.append_attribute("xmlns:adept") = ADOBE_ADEPT_NS; signIn.append_attribute("xmlns:adept") = ADOBE_ADEPT_NS;
std::string loginMethod = user->getLoginMethod(); std::string loginMethod = user->getLoginMethod();
if (loginMethod.size())
if (adobeID == "anonymous")
signIn.append_attribute("method") = "anonymous";
else if (loginMethod.size())
signIn.append_attribute("method") = loginMethod.c_str(); signIn.append_attribute("method") = loginMethod.c_str();
else else
signIn.append_attribute("method") = "AdobeID"; signIn.append_attribute("method") = "AdobeID";
@@ -817,10 +819,7 @@ namespace gourou
pugi::xml_node root = activateReq.select_node("adept:activate").node(); pugi::xml_node root = activateReq.select_node("adept:activate").node();
std::string signature = signNode(root); signNode(root);
root = activateReq.select_node("adept:activate").node();
appendTextElem(root, "adept:signature", signature);
pugi::xml_document activationDoc; pugi::xml_document activationDoc;
user->readActivation(activationDoc); user->readActivation(activationDoc);
@@ -838,6 +837,33 @@ namespace gourou
user->updateActivationFile(activationDoc); user->updateActivationFile(activationDoc);
} }
void DRMProcessor::buildReturnReq(pugi::xml_document& returnReq, const std::string& loanID, const std::string& operatorURL)
{
pugi::xml_node decl = returnReq.append_child(pugi::node_declaration);
decl.append_attribute("version") = "1.0";
pugi::xml_node root = returnReq.append_child("adept:loanReturn");
root.append_attribute("xmlns:adept") = ADOBE_ADEPT_NS;
appendTextElem(root, "adept:user", user->getUUID());
appendTextElem(root, "adept:device", user->getDeviceUUID());
appendTextElem(root, "adept:loan", loanID);
addNonce(root);
signNode(root);
}
void DRMProcessor::returnLoan(const std::string& loanID, const std::string& operatorURL)
{
pugi::xml_document returnReq;
GOUROU_LOG(INFO, "Return loan " << loanID);
buildReturnReq(returnReq, loanID, operatorURL);
sendRequest(returnReq, operatorURL + "/LoanReturn");
}
ByteArray DRMProcessor::encryptWithDeviceKey(const unsigned char* data, unsigned int len) ByteArray DRMProcessor::encryptWithDeviceKey(const unsigned char* data, unsigned int len)
{ {
const unsigned char* deviceKey = device->getDeviceKey(); const unsigned char* deviceKey = device->getDeviceKey();
@@ -851,7 +877,7 @@ namespace gourou
// Generate IV in front // Generate IV in front
client->randBytes(encrypted_data, 16); client->randBytes(encrypted_data, 16);
client->AESEncrypt(CryptoInterface::CHAIN_CBC, client->Encrypt(CryptoInterface::ALGO_AES, CryptoInterface::CHAIN_CBC,
deviceKey, 16, encrypted_data, 16, deviceKey, 16, encrypted_data, 16,
data, len, data, len,
encrypted_data+16, &outLen); encrypted_data+16, &outLen);
@@ -870,7 +896,7 @@ namespace gourou
const unsigned char* deviceKey = device->getDeviceKey(); const unsigned char* deviceKey = device->getDeviceKey();
unsigned char* decrypted_data = new unsigned char[len-16]; unsigned char* decrypted_data = new unsigned char[len-16];
client->AESDecrypt(CryptoInterface::CHAIN_CBC, client->Decrypt(CryptoInterface::ALGO_AES, CryptoInterface::CHAIN_CBC,
deviceKey, 16, data, 16, deviceKey, 16, data, 16,
data+16, len-16, data+16, len-16,
decrypted_data, &outLen); decrypted_data, &outLen);
@@ -925,4 +951,353 @@ namespace gourou
int DRMProcessor::getLogLevel() {return (int)gourou::logLevel;} int DRMProcessor::getLogLevel() {return (int)gourou::logLevel;}
void DRMProcessor::setLogLevel(int logLevel) {gourou::logLevel = (GOUROU_LOG_LEVEL)logLevel;} void DRMProcessor::setLogLevel(int logLevel) {gourou::logLevel = (GOUROU_LOG_LEVEL)logLevel;}
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,
const unsigned char* encryptionKey, unsigned encryptionKeySize)
{
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];
if (!encryptionKey)
decryptADEPTKey(encryptedKey, decryptedKey);
else
{
GOUROU_LOG(DEBUG, "Use provided encryption key");
if (encryptionKeySize != 16)
EXCEPTION(DRM_ERR_ENCRYPTION_KEY, "Provided encryption key must be 16 bytes");
memcpy(&decryptedKey[sizeof(decryptedKey)-16], encryptionKey, encryptionKeySize);
}
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+sizeof(decryptedKey)-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,
const unsigned char* encryptionKey, unsigned encryptionKeySize)
{
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*>::iterator it;
std::vector<uPDFParser::Object*>::reverse_iterator rIt;
unsigned char decryptedKey[RSA_KEY_SIZE];
int ebxId;
for(rIt = objects.rbegin(); rIt != objects.rend(); rIt++)
{
// Update EBX_HANDLER with rights
if ((*rIt)->hasKey("Filter") && (**rIt)["Filter"]->str() == "/EBX_HANDLER")
{
EBXHandlerFound = true;
uPDFParser::Object* ebx = *rIt;
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"];
std::string value = licenseObject->value();
// Pad with '='
while ((value.size() % 4))
value += "=";
ByteArray zippedData = ByteArray::fromBase64(value);
if (zippedData.size() == 0)
EXCEPTION(DRM_ERR_ENCRYPTION_KEY, "Invalid ADEPT_LICENSE");
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");
if (!encryptionKey)
decryptADEPTKey(encryptedKey, decryptedKey);
else
{
GOUROU_LOG(DEBUG, "Use provided encryption key");
if (encryptionKeySize != 16)
EXCEPTION(DRM_ERR_ENCRYPTION_KEY, "Provided encryption key must be 16 bytes");
memcpy(&decryptedKey[sizeof(decryptedKey)-16], encryptionKey, encryptionKeySize);
}
ebxId = ebx->objectId();
break;
}
}
if (!EBXHandlerFound)
{
EXCEPTION(DRM_ERR_ENCRYPTION_KEY, "EBX_HANDLER not found");
}
for(it = objects.begin(); it != objects.end(); it++)
{
uPDFParser::Object* object = *it;
if (object->objectId() == ebxId)
{
// object->deleteKey("Filter");
continue;
}
// Should not decrypt XRef stream
if (object->hasKey("Type") && (*object)["Type"]->str() == "/XRef")
{
GOUROU_LOG(DEBUG, "XRef stream at " << object->offset());
continue;
}
GOUROU_LOG(DEBUG, "Obj " << object->objectId());
unsigned char tmpKey[16];
generatePDFObjectKey(ebxVersion->value(),
decryptedKey+sizeof(decryptedKey)-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 = 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);
if (dataOutLength != dataLength)
GOUROU_LOG(DEBUG, "New size " << dataOutLength);
}
}
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, const unsigned char* encryptionKey, unsigned encryptionKeySize)
{
if (type == PDF)
removePDFDRM(filenameIn, filenameOut, encryptionKey, encryptionKeySize);
else
removeEPubDRM(filenameIn, filenameOut, encryptionKey, encryptionKeySize);
}
} }

77
src/loan_token.cpp Normal file
View File

@@ -0,0 +1,77 @@
/*
Copyright 2022 Grégory Soutadé
This file is part of libgourou.
libgourou is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
libgourou is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with libgourou. If not, see <http://www.gnu.org/licenses/>.
*/
#include "libgourou_common.h"
#include "loan_token.h"
namespace gourou
{
LoanToken::LoanToken(pugi::xml_document& doc)
{
pugi::xml_node node = doc.select_node("/envelope/loanToken").node();
if (!node)
EXCEPTION(FFI_INVALID_LOAN_TOKEN, "No loanToken element in document");
node = doc.select_node("/envelope/loanToken/loan").node();
if (!node)
EXCEPTION(FFI_INVALID_LOAN_TOKEN, "No loanToken/loan element in document");
properties["id"] = node.first_child().value();
node = doc.select_node("/envelope/loanToken/operatorURL").node();
if (!node)
EXCEPTION(FFI_INVALID_LOAN_TOKEN, "No loanToken/operatorURL element in document");
properties["operatorURL"] = node.first_child().value();
node = doc.select_node("/envelope/fulfillmentResult/resourceItemInfo/licenseToken/permissions/display/until").node();
if (node)
properties["validity"] = node.first_child().value();
else
{
node = doc.select_node("/envelope/fulfillmentResult/resourceItemInfo/licenseToken/permissions/play/until").node();
if (node)
properties["validity"] = node.first_child().value();
else
EXCEPTION(FFI_INVALID_LOAN_TOKEN, "No loanToken/operatorURL element in document");
}
}
std::string LoanToken::getProperty(const std::string& property, const std::string& _default)
{
if (properties.find(property) == properties.end())
{
if (_default == "")
EXCEPTION(GOUROU_INVALID_PROPERTY, "Invalid property " << property);
return _default;
}
return properties[property];
}
std::string LoanToken::operator[](const std::string& property)
{
return getProperty(property);
}
}

View File

@@ -50,7 +50,6 @@ namespace gourou {
deviceFingerprint = gourou::extractTextElem(activationDoc, "//fingerprint", throwOnNull); deviceFingerprint = gourou::extractTextElem(activationDoc, "//fingerprint", throwOnNull);
authenticationCertificate = gourou::extractTextElem(activationDoc, "//adept:authenticationCertificate", throwOnNull); authenticationCertificate = gourou::extractTextElem(activationDoc, "//adept:authenticationCertificate", throwOnNull);
privateLicenseKey = gourou::extractTextElem(activationDoc, "//adept:privateLicenseKey", throwOnNull); privateLicenseKey = gourou::extractTextElem(activationDoc, "//adept:privateLicenseKey", throwOnNull);
username = gourou::extractTextElem(activationDoc, "//adept:username", throwOnNull);
pugi::xpath_node xpath_node = activationDoc.select_node("//adept:username"); pugi::xpath_node xpath_node = activationDoc.select_node("//adept:username");
if (xpath_node) if (xpath_node)
@@ -61,6 +60,11 @@ namespace gourou {
EXCEPTION(USER_INVALID_ACTIVATION_FILE, "Invalid activation file"); 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"); pugi::xpath_node_set nodeSet = activationDoc.select_nodes("//adept:licenseServices/adept:licenseServiceInfo");
for (pugi::xpath_node_set::const_iterator it = nodeSet.begin(); for (pugi::xpath_node_set::const_iterator it = nodeSet.begin();
it != nodeSet.end(); ++it) it != nodeSet.end(); ++it)

View File

@@ -1,10 +1,10 @@
TARGETS=acsmdownloader adept_activate TARGETS=acsmdownloader adept_activate adept_remove adept_loan_mgt
CXXFLAGS=-Wall `pkg-config --cflags Qt5Core Qt5Network` -fPIC -I$(ROOT)/include -I$(ROOT)/lib/pugixml/src/ CXXFLAGS=-Wall -fPIC -I$(ROOT)/include -I$(ROOT)/lib/pugixml/src/
STATIC_DEP= STATIC_DEP=
LDFLAGS=`pkg-config --libs Qt5Core Qt5Network` -L$(ROOT) -lcrypto -lzip -lz LDFLAGS=-L$(ROOT) -lcrypto -lzip -lz -lcurl
ifneq ($(STATIC_UTILS),) ifneq ($(STATIC_UTILS),)
STATIC_DEP = $(ROOT)/libgourou.a STATIC_DEP = $(ROOT)/libgourou.a
@@ -18,12 +18,26 @@ else
CXXFLAGS += -O2 CXXFLAGS += -O2
endif endif
COMMON_DEPS = drmprocessorclientimpl.cpp utils_common.cpp
COMMON_OBJECTS = $(COMMON_DEPS:.cpp=.o)
COMMON_LIB = utils.a
all: $(TARGETS) all: $(TARGETS)
acsmdownloader: drmprocessorclientimpl.cpp acsmdownloader.cpp $(STATIC_DEP) ${COMMON_LIB}: ${COMMON_DEPS} ${STATIC_DEP}
$(CXX) $(CXXFLAGS) ${COMMON_DEPS} $(LDFLAGS) -c
$(AR) crs $@ ${COMMON_OBJECTS} $(STATIC_DEP)
acsmdownloader: acsmdownloader.cpp ${COMMON_LIB}
$(CXX) $(CXXFLAGS) $^ $(LDFLAGS) -o $@ $(CXX) $(CXXFLAGS) $^ $(LDFLAGS) -o $@
adept_activate: drmprocessorclientimpl.cpp adept_activate.cpp $(STATIC_DEP) adept_activate: adept_activate.cpp ${COMMON_LIB}
$(CXX) $(CXXFLAGS) $^ $(LDFLAGS) -o $@
adept_remove: adept_remove.cpp ${COMMON_LIB}
$(CXX) $(CXXFLAGS) $^ $(LDFLAGS) -o $@
adept_loan_mgt: adept_loan_mgt.cpp ${COMMON_LIB}
$(CXX) $(CXXFLAGS) $^ $(LDFLAGS) -o $@ $(CXX) $(CXXFLAGS) $^ $(LDFLAGS) -o $@
clean: clean:

View File

@@ -26,21 +26,17 @@
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/ */
#include <unistd.h>
#include <getopt.h> #include <getopt.h>
#include <libgen.h>
#include <iostream> #include <iostream>
#include <algorithm>
#include <QFile>
#include <QDir>
#include <QCoreApplication>
#include <QRunnable>
#include <QThreadPool>
#include <libgourou.h> #include <libgourou.h>
#include "drmprocessorclientimpl.h" #include <libgourou_common.h>
#define ARRAY_SIZE(arr) (sizeof(arr)/sizeof(arr[0])) #include "drmprocessorclientimpl.h"
#include "utils_common.h"
static const char* deviceFile = "device.xml"; static const char* deviceFile = "device.xml";
static const char* activationFile = "activation.xml"; static const char* activationFile = "activation.xml";
@@ -49,28 +45,18 @@ static const char* acsmFile = 0;
static bool exportPrivateKey = false; static bool exportPrivateKey = false;
static const char* outputFile = 0; static const char* outputFile = 0;
static const char* outputDir = 0; static const char* outputDir = 0;
static const char* defaultDirs[] = { static bool resume = false;
".adept/",
"./adobe-digital-editions/",
"./.adobe-digital-editions/"
};
class ACSMDownloader: public QRunnable class ACSMDownloader
{ {
public: public:
ACSMDownloader(QCoreApplication* app):
app(app)
{
setAutoDelete(false);
}
void run() int run()
{ {
int ret = 0; int ret = 0;
try try
{ {
DRMProcessorClientImpl client;
gourou::DRMProcessor processor(&client, deviceFile, activationFile, devicekeyFile); gourou::DRMProcessor processor(&client, deviceFile, activationFile, devicekeyFile);
gourou::User* user = processor.getUser(); gourou::User* user = processor.getUser();
@@ -84,9 +70,8 @@ public:
if (outputDir) if (outputDir)
{ {
QDir dir(outputDir); if (!fileExists(outputDir))
if (!dir.exists(outputDir)) mkpath(outputDir);
dir.mkpath(outputDir);
filename = std::string(outputDir) + "/" + filename; filename = std::string(outputDir) + "/" + filename;
} }
@@ -116,14 +101,13 @@ public:
if (outputDir) if (outputDir)
{ {
QDir dir(outputDir); if (!fileExists(outputDir))
if (!dir.exists(outputDir)) mkpath(outputDir);
dir.mkpath(outputDir);
filename = std::string(outputDir) + "/" + filename; filename = std::string(outputDir) + "/" + filename;
} }
gourou::DRMProcessor::ITEM_TYPE type = processor.download(item, filename); gourou::DRMProcessor::ITEM_TYPE type = processor.download(item, filename, resume);
if (!outputFile) if (!outputFile)
{ {
@@ -132,11 +116,12 @@ public:
finalName += ".pdf"; finalName += ".pdf";
else else
finalName += ".epub"; finalName += ".epub";
QDir dir; rename(filename.c_str(), finalName.c_str());
dir.rename(filename.c_str(), finalName.c_str());
filename = finalName; filename = finalName;
} }
std::cout << "Created " << filename << std::endl; std::cout << "Created " << filename << std::endl;
serializeLoanToken(item);
} }
} catch(std::exception& e) } catch(std::exception& e)
{ {
@@ -144,43 +129,62 @@ public:
ret = 1; ret = 1;
} }
this->app->exit(ret); return ret;
}
void serializeLoanToken(gourou::FulfillmentItem* item)
{
gourou::LoanToken* token = item->getLoanToken();
// No loan token available
if (!token)
return;
pugi::xml_document doc;
pugi::xml_node decl = doc.append_child(pugi::node_declaration);
decl.append_attribute("version") = "1.0";
pugi::xml_node root = doc.append_child("loanToken");
gourou::appendTextElem(root, "id", (*token)["id"]);
gourou::appendTextElem(root, "operatorURL", (*token)["operatorURL"]);
gourou::appendTextElem(root, "validity", (*token)["validity"]);
gourou::appendTextElem(root, "name", item->getMetadata("title"));
char * activationDir = strdup(deviceFile);
activationDir = dirname(activationDir);
gourou::StringXMLWriter xmlWriter;
doc.save(xmlWriter, " ");
std::string xmlStr = xmlWriter.getResult();
// Use first bytes of SHA1(id) as filename
unsigned char sha1[gourou::SHA1_LEN];
client.digest("SHA1", (unsigned char*)(*token)["id"].c_str(), (*token)["id"].size(), sha1);
gourou::ByteArray tmp(sha1, sizeof(sha1));
std::string filenameHex = tmp.toHex();
std::string filename(filenameHex.c_str(), ID_HASH_SIZE);
std::string fullPath = std::string(activationDir);
fullPath += std::string ("/") + std::string(LOANS_DIR);
mkpath(fullPath.c_str());
fullPath += filename + std::string(".xml");
gourou::writeFile(fullPath, xmlStr);
std::cout << "Loan token serialized into " << fullPath << std::endl;
free(activationDir);
} }
private: private:
QCoreApplication* app; DRMProcessorClientImpl client;
}; };
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) static void usage(const char* cmd)
{ {
std::cout << "Download EPUB file from ACSM request file" << std::endl; std::cout << "Download EPUB file from ACSM request file" << std::endl;
std::cout << "Usage: " << cmd << " [(-d|--device-file) device.xml] [(-a|--activation-file) activation.xml] [(-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 << "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)] [(-r|--resume)] [(-v|--verbose)] [(-h|--help)] (-f|--acsm-file) file.acsm|(-e|--export-private-key)" << std::endl << std::endl;
std::cout << " " << "-d|--device-file" << "\t" << "device.xml file from eReader" << std::endl; std::cout << " " << "-d|--device-file" << "\t" << "device.xml file from eReader" << std::endl;
std::cout << " " << "-a|--activation-file" << "\t" << "activation.xml file from eReader" << std::endl; std::cout << " " << "-a|--activation-file" << "\t" << "activation.xml file from eReader" << std::endl;
@@ -189,6 +193,7 @@ static void usage(const char* cmd)
std::cout << " " << "-o|--output-file" << "\t" << "Optional output filename (default <title.(epub|pdf|der)>)" << std::endl; std::cout << " " << "-o|--output-file" << "\t" << "Optional output filename (default <title.(epub|pdf|der)>)" << std::endl;
std::cout << " " << "-f|--acsm-file" << "\t" << "ACSM request file for epub download" << std::endl; std::cout << " " << "-f|--acsm-file" << "\t" << "ACSM request file for epub download" << std::endl;
std::cout << " " << "-e|--export-private-key"<< "\t" << "Export private key in DER format" << std::endl; std::cout << " " << "-e|--export-private-key"<< "\t" << "Export private key in DER format" << std::endl;
std::cout << " " << "-r|--resume" << "\t\t" << "Try to resume download (in case of previous failure)" << std::endl;
std::cout << " " << "-v|--verbose" << "\t\t" << "Increase verbosity, can be set multiple times" << std::endl; std::cout << " " << "-v|--verbose" << "\t\t" << "Increase verbosity, can be set multiple times" << std::endl;
std::cout << " " << "-V|--version" << "\t\t" << "Display libgourou version" << std::endl; std::cout << " " << "-V|--version" << "\t\t" << "Display libgourou version" << std::endl;
std::cout << " " << "-h|--help" << "\t\t" << "This help" << std::endl; std::cout << " " << "-h|--help" << "\t\t" << "This help" << std::endl;
@@ -218,13 +223,14 @@ int main(int argc, char** argv)
{"output-file", required_argument, 0, 'o' }, {"output-file", required_argument, 0, 'o' },
{"acsm-file", required_argument, 0, 'f' }, {"acsm-file", required_argument, 0, 'f' },
{"export-private-key",no_argument, 0, 'e' }, {"export-private-key",no_argument, 0, 'e' },
{"resume", no_argument, 0, 'r' },
{"verbose", no_argument, 0, 'v' }, {"verbose", no_argument, 0, 'v' },
{"version", no_argument, 0, 'V' }, {"version", no_argument, 0, 'V' },
{"help", no_argument, 0, 'h' }, {"help", no_argument, 0, 'h' },
{0, 0, 0, 0 } {0, 0, 0, 0 }
}; };
c = getopt_long(argc, argv, "d:a:k:O:o:f:evVh", c = getopt_long(argc, argv, "d:a:k:O:o:f:ervVh",
long_options, &option_index); long_options, &option_index);
if (c == -1) if (c == -1)
break; break;
@@ -251,6 +257,9 @@ int main(int argc, char** argv)
case 'e': case 'e':
exportPrivateKey = true; exportPrivateKey = true;
break; break;
case 'r':
resume = true;
break;
case 'v': case 'v':
verbose++; verbose++;
break; break;
@@ -275,8 +284,7 @@ int main(int argc, char** argv)
return -1; return -1;
} }
QCoreApplication app(argc, argv); ACSMDownloader downloader;
ACSMDownloader downloader(&app);
int i; int i;
bool hasErrors = false; bool hasErrors = false;
@@ -306,8 +314,7 @@ int main(int argc, char** argv)
} }
else else
{ {
QFile file(acsmFile); if (!fileExists(acsmFile))
if (!file.exists())
{ {
std::cout << "Error : " << acsmFile << " doesn't exists" << std::endl; std::cout << "Error : " << acsmFile << " doesn't exists" << std::endl;
ret = -1; ret = -1;
@@ -315,9 +322,7 @@ int main(int argc, char** argv)
} }
} }
QThreadPool::globalInstance()->start(&downloader); ret = downloader.run();
ret = app.exec();
end: end:
for (i=0; i<(int)ARRAY_SIZE(files); i++) for (i=0; i<(int)ARRAY_SIZE(files); i++)

View File

@@ -28,22 +28,16 @@
#include <unistd.h> #include <unistd.h>
#include <getopt.h> #include <getopt.h>
#include <stdlib.h>
#include <termios.h> #include <termios.h>
#include <string.h>
#include <limits.h>
#include <iostream> #include <iostream>
#include <ostream> #include <ostream>
#include <QFile>
#include <QDir>
#include <QCoreApplication>
#include <QRunnable>
#include <QThreadPool>
#include <libgourou.h> #include <libgourou.h>
#include "drmprocessorclientimpl.h" #include "drmprocessorclientimpl.h"
#include "utils_common.h"
#define ARRAY_SIZE(arr) (sizeof(arr)/sizeof(arr[0]))
static const char* username = 0; static const char* username = 0;
static const char* password = 0; static const char* password = 0;
@@ -100,16 +94,11 @@ static std::string getpass(const char *prompt, bool show_asterisk=false)
} }
class ADEPTActivate: public QRunnable class ADEPTActivate
{ {
public: public:
ADEPTActivate(QCoreApplication* app):
app(app)
{
setAutoDelete(false);
}
void run() int run()
{ {
int ret = 0; int ret = 0;
try try
@@ -128,24 +117,18 @@ public:
ret = 1; ret = 1;
} }
this->app->exit(ret); return ret;
} }
private:
QCoreApplication* app;
}; };
static void version(void)
{
std::cout << "Current libgourou version : " << gourou::DRMProcessor::VERSION << std::endl ;
}
static void usage(const char* cmd) static void usage(const char* cmd)
{ {
std::cout << "Create new device files used by ADEPT DRM" << std::endl; 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 << " " << "-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 << " " << "-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 << " " << "-O|--output-dir" << "\t" << "Optional output directory were to put result (default ./.adept). This directory must not already exists" << std::endl;
@@ -161,8 +144,8 @@ static void usage(const char* cmd)
static const char* abspath(const char* filename) static const char* abspath(const char* filename)
{ {
const char* root = getcwd(0, PATH_MAX); const char* root = getcwd(0, PATH_MAX);
QString fullPath = QString(root) + QString("/") + QString(filename); std::string fullPath = std::string(root) + std::string("/") + filename;
const char* res = strdup(fullPath.toStdString().c_str()); const char* res = strdup(fullPath.c_str());
free((void*)root); free((void*)root);
@@ -174,10 +157,12 @@ int main(int argc, char** argv)
int c, ret = -1; int c, ret = -1;
const char* _outputDir = outputDir; const char* _outputDir = outputDir;
int verbose = gourou::DRMProcessor::getLogLevel(); int verbose = gourou::DRMProcessor::getLogLevel();
bool anonymous = false;
while (1) { while (1) {
int option_index = 0; int option_index = 0;
static struct option long_options[] = { static struct option long_options[] = {
{"anonymous", no_argument , 0, 'a' },
{"username", required_argument, 0, 'u' }, {"username", required_argument, 0, 'u' },
{"password", required_argument, 0, 'p' }, {"password", required_argument, 0, 'p' },
{"output-dir", required_argument, 0, 'O' }, {"output-dir", required_argument, 0, 'O' },
@@ -189,12 +174,15 @@ int main(int argc, char** argv)
{0, 0, 0, 0 } {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); long_options, &option_index);
if (c == -1) if (c == -1)
break; break;
switch (c) { switch (c) {
case 'a':
anonymous = true;
break;
case 'u': case 'u':
username = optarg; username = optarg;
break; break;
@@ -227,47 +215,72 @@ int main(int argc, char** argv)
gourou::DRMProcessor::setLogLevel(verbose); gourou::DRMProcessor::setLogLevel(verbose);
if (!username) if ((!username && !anonymous) ||
(username && anonymous))
{ {
usage(argv[0]); usage(argv[0]);
return -1; return -1;
} }
if (anonymous)
{
username = "anonymous";
password = "";
}
if (!_outputDir || _outputDir[0] == 0) if (!_outputDir || _outputDir[0] == 0)
{ {
outputDir = abspath(DEFAULT_ADEPT_DIR); outputDir = strdup(abspath(DEFAULT_ADEPT_DIR));
} }
else else
{ {
// Relative path // Relative path
if (_outputDir[0] == '.' || _outputDir[0] != '/') if (_outputDir[0] == '.' || _outputDir[0] != '/')
{ {
QFile file(_outputDir);
// realpath doesn't works if file/dir doesn't exists // realpath doesn't works if file/dir doesn't exists
if (file.exists()) if (fileExists(_outputDir))
outputDir = realpath(_outputDir, 0); outputDir = strdup(realpath(_outputDir, 0));
else else
outputDir = abspath(_outputDir); outputDir = strdup(abspath(_outputDir));
} }
else else
outputDir = strdup(_outputDir); outputDir = strdup(_outputDir);
} }
std::string pass;
if (fileExists(outputDir))
{
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')
;
}
if (!password) if (!password)
{ {
char prompt[128]; char prompt[128];
std::snprintf(prompt, sizeof(prompt), "Enter password for <%s> : ", username); 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(); password = pass.c_str();
} }
QCoreApplication app(argc, argv); ADEPTActivate activate;
ADEPTActivate activate(&app); ret = activate.run();
QThreadPool::globalInstance()->start(&activate);
ret = app.exec();
end:
free((void*)outputDir); free((void*)outputDir);
return ret; return ret;
} }

479
utils/adept_loan_mgt.cpp Normal file
View File

@@ -0,0 +1,479 @@
/*
Copyright (c) 2022, 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 <getopt.h>
#include <iostream>
#include <algorithm>
#define _XOPEN_SOURCE 700
#include <stdio.h>
#include <sys/types.h>
#include <dirent.h>
#include <libgen.h>
#include <time.h>
#include <libgourou.h>
#include <libgourou_common.h>
#include "drmprocessorclientimpl.h"
#include "utils_common.h"
#define MAX_SIZE_BOOK_NAME 30
static char* activationDir = 0;
static const char* deviceFile = "device.xml";
static const char* activationFile = "activation.xml";
static const char* devicekeyFile = "devicesalt";
static bool list = false;
static const char* returnID = 0;
static const char* deleteID = 0;
struct Loan
{
std::string id;
std::string operatorURL;
std::string validity;
std::string bookName;
std::string path;
};
class LoanMGT
{
public:
~LoanMGT()
{
for (const auto& kv : loanedBooks)
delete kv.second;
}
int run()
{
int ret = 0;
try
{
DRMProcessorClientImpl client;
gourou::DRMProcessor processor(&client, deviceFile, activationFile, devicekeyFile);
loadLoanedBooks();
if (list)
displayLoanList();
else if (returnID)
returnBook(processor);
else if (deleteID)
deleteLoan();
} catch(std::exception& e)
{
std::cout << e.what() << std::endl;
ret = 1;
}
return ret;
}
private:
void loadLoanedBooks()
{
DIR *dp;
struct dirent *ep;
int entryLen;
struct Loan* loan;
char * res;
std::string loanDir = std::string(activationDir) + std::string("/") + LOANS_DIR;
if (!fileExists(loanDir.c_str()))
return;
dp = opendir (loanDir.c_str());
if(!dp)
EXCEPTION(gourou::USER_INVALID_INPUT, "Cannot read directory " << loanDir);
while ((ep = readdir (dp)))
{
if (ep->d_type != DT_LNK &&
ep->d_type != DT_REG)
continue;
entryLen = strlen(ep->d_name);
if (entryLen <= 4 ||
ep->d_name[entryLen-4] != '.' ||
ep->d_name[entryLen-3] != 'x' ||
ep->d_name[entryLen-2] != 'm' ||
ep->d_name[entryLen-1] != 'l')
continue;
std::string id = std::string(ep->d_name, entryLen-4);
loan = new Loan;
loan->path = loanDir + std::string("/") + ep->d_name;
pugi::xml_document xmlDoc;
pugi::xml_node node;
if (!xmlDoc.load_file(loan->path.c_str(), pugi::parse_ws_pcdata_single|pugi::parse_escapes, pugi::encoding_utf8))
{
std::cout << "Invalid loan entry " << loan->path << std::endl;
goto error;
}
// id
node = xmlDoc.select_node("//id").node();
if (!node)
{
std::cout << "Invalid loan entry " << ep->d_name << ", no id element" << std::endl;
goto error;
}
loan->id = node.first_child().value();
// operatorURL
node = xmlDoc.select_node("//operatorURL").node();
if (!node)
{
std::cout << "Invalid loan entry " << ep->d_name << ", no operatorURL element" << std::endl;
goto error;
}
loan->operatorURL = node.first_child().value();
// validity
node = xmlDoc.select_node("//validity").node();
if (!node)
{
std::cout << "Invalid loan entry " << ep->d_name << ", no validity element" << std::endl;
goto error;
}
loan->validity = node.first_child().value();
// bookName
node = xmlDoc.select_node("//name").node();
if (!node)
{
std::cout << "Invalid loan entry " << ep->d_name << ", no name element" << std::endl;
goto error;
}
loan->bookName = node.first_child().value();
struct tm tm;
res = strptime(loan->validity.c_str(), "%Y-%m-%dT%H:%M:%S%Z", &tm);
if (*res == 0)
{
if (mktime(&tm) <= time(NULL))
loan->validity = " (Expired)";
}
else
{
std::cout << "Unable to parse validity timestamp :" << loan->validity << std::endl;
loan->validity = " (Unknown)";
}
loanedBooks[id] = loan;
continue;
error:
if (loan)
delete loan;
}
closedir (dp);
}
void displayLoanList()
{
if (!loanedBooks.size())
{
std::cout << "Any book loaned" << std::endl;
return;
}
struct Loan* loan;
unsigned int maxSizeBookName=0;
// Compute max size
for (const auto& kv : loanedBooks)
{
loan = kv.second;
if (loan->bookName.size() > maxSizeBookName)
maxSizeBookName = loan->bookName.size();
}
if (maxSizeBookName > MAX_SIZE_BOOK_NAME)
maxSizeBookName = MAX_SIZE_BOOK_NAME;
else if ((maxSizeBookName % 2))
maxSizeBookName++;
// std::cout << " ID Book Expiration" << std::endl;
// std::cout << "------------------------------" << std::endl;
int fillID, fillBookName, fillExpiration=(20 - 10)/2;
fillID = (ID_HASH_SIZE - 2) / 2;
fillBookName = (maxSizeBookName - 4) / 2;
std::cout.width (fillID);
std::cout << "";
std::cout << "ID" ;
std::cout.width (fillID);
std::cout << "";
std::cout << " " ;
std::cout.width (fillBookName);
std::cout << "";
std::cout << "Book" ;
std::cout.width (fillBookName);
std::cout << "";
std::cout << " " ;
std::cout.width (fillExpiration);
std::cout << "";
std::cout << "Exipration";
std::cout.width (fillExpiration);
std::cout << "" << std::endl;
std::cout.fill ('-');
std::cout.width (ID_HASH_SIZE + 4 + maxSizeBookName + 4 + 20);
std::cout << "" << std::endl;
std::cout.fill (' ');
std::string bookName;
for (const auto& kv : loanedBooks)
{
loan = kv.second;
std::cout << kv.first;
std::cout << " ";
if (loan->bookName.size() > MAX_SIZE_BOOK_NAME)
bookName = std::string(loan->bookName.c_str(), MAX_SIZE_BOOK_NAME);
else
bookName = loan->bookName;
std::cout << bookName;
std::cout.width (maxSizeBookName - bookName.size());
std::cout << "";
std::cout << " ";
std::cout << loan->validity << std::endl;
}
std::cout << std::endl;
}
void returnBook(gourou::DRMProcessor& processor)
{
struct Loan* loan = loanedBooks[std::string(returnID)];
if (!loan)
{
std::cout << "Error : Loan " << returnID << " doesn't exists" << std::endl;
return;
}
processor.returnLoan(loan->id, loan->operatorURL);
deleteID = returnID;
if (deleteLoan(false))
{
std::cout << "Loan " << returnID << " successfully returned" << std::endl;
}
}
bool deleteLoan(bool displayResult=true)
{
struct Loan* loan = loanedBooks[std::string(deleteID)];
if (!loan)
{
std::cout << "Error : Loan " << deleteID << " doesn't exists" << std::endl;
return false;
}
if (unlink(loan->path.c_str()))
{
std::cout << "Error : Cannot delete " << loan->path << std::endl;
return false;
}
else if (displayResult)
{
std::cout << "Loan " << deleteID << " deleted" << std::endl;
}
return true;
}
std::map<std::string, struct Loan*> loanedBooks;
};
static void usage(const char* cmd)
{
std::cout << "Manage loaned books" << std::endl;
std::cout << "Usage: " << cmd << " [(-d|--activation-dir) dir] (-l|--list)|(-D|--delete loanID)|(-R|--delete loanID) [(-v|--verbose)] [(-h|--help)]" << std::endl << std::endl;
std::cout << " " << "-d|--activation-dir" << "\t" << "Directory of device.xml/activation.xml and device key" << std::endl;
std::cout << " " << "-l|--list" << "\t\t" << "List all loaned books" << std::endl;
std::cout << " " << "-r|--return" << "\t\t" << "Return a loaned book" << std::endl;
std::cout << " " << "-D|--delete" << "\t\t" << "Delete a loan entry without returning it" << 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 << "Activation directory is optional. If not set, it's 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();
int actions = 0;
while (1) {
int option_index = 0;
static struct option long_options[] = {
{"activation-dir", required_argument, 0, 'd' },
{"list", no_argument, 0, 'l' },
{"return", no_argument, 0, 'r' },
{"delete", no_argument, 0, 'D' },
{"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:lr:D:vVh",
long_options, &option_index);
if (c == -1)
break;
switch (c) {
case 'd':
activationDir = optarg;
break;
case 'l':
list = true;
actions++;
break;
case 'r':
returnID = optarg;
actions++;
break;
case 'D':
deleteID = optarg;
actions++;
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);
// By default, simply list books loaned
if (actions == 0)
list = true;
else if (actions != 1)
{
usage(argv[0]);
return -1;
}
LoanMGT loanMGT;
int i;
bool hasErrors = false;
const char* orig;
char *filename;
for (i=0; i<(int)ARRAY_SIZE(files); i++)
{
orig = *files[i];
if (activationDir)
{
std::string path = std::string(activationDir) + std::string("/") + orig;
filename = strdup(path.c_str());
}
else
filename = strdup(orig);
*files[i] = findFile(filename);
free(filename);
if (!*files[i])
{
std::cout << "Error : " << orig << " doesn't exists, did you activate your device ?" << std::endl;
hasErrors = true;
}
}
if (hasErrors)
{
// In case of activation dir was provided by user
activationDir = 0;
goto end;
}
if (activationDir)
activationDir = strdup(activationDir); // For below free
else
{
activationDir = strdup(deviceFile);
activationDir = dirname(activationDir);
}
ret = loanMGT.run();
end:
for (i=0; i<(int)ARRAY_SIZE(files); i++)
{
if (*files[i])
free((void*)*files[i]);
}
if (activationDir)
free(activationDir);
return ret;
}

299
utils/adept_remove.cpp Normal file
View File

@@ -0,0 +1,299 @@
/*
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 <getopt.h>
#include <iostream>
#include <libgourou.h>
#include <libgourou_common.h>
#include "drmprocessorclientimpl.h"
#include "utils_common.h"
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 char* encryptionKeyUser = 0;
static unsigned char* encryptionKey = 0;
static unsigned encryptionKeySize = 0;
static inline unsigned char htoi(unsigned char c)
{
if (c >= '0' && c <= '9')
c -= '0';
else if (c >= 'a' && c <= 'f')
c -= 'a' - 10;
else if (c >= 'A' && c <= 'F')
c -= 'A' - 10;
else
EXCEPTION(gourou::USER_INVALID_INPUT, "Invalid character " << c << " in encryption key");
return c;
}
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:
int 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)
{
if (!fileExists(outputDir))
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)
{
unlink(filename.c_str());
fileCopy(inputFile, filename.c_str());
processor.removeDRM(inputFile, filename, type, encryptionKey, encryptionKeySize);
std::cout << "DRM removed into new file " << filename << std::endl;
}
else
{
// Use temp file for PDF
if (type == gourou::DRMProcessor::ITEM_TYPE::PDF)
{
std::string tempFile = filename + ".tmp";
/* Be sure there is not already a temp file */
unlink(tempFile.c_str());
processor.removeDRM(filename, tempFile, type, encryptionKey, encryptionKeySize);
/* Original file must be removed before doing a copy... */
unlink(filename.c_str());
if (rename(tempFile.c_str(), filename.c_str()))
{
EXCEPTION(gourou::DRM_FILE_ERROR, "Unable to copy " << tempFile << " into " << filename);
}
}
else
processor.removeDRM(inputFile, filename, type, encryptionKey, encryptionKeySize);
std::cout << "DRM removed from " << filename << std::endl;
}
} catch(std::exception& e)
{
std::cout << e.what() << std::endl;
ret = 1;
}
return ret;
}
};
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' },
{"encryption-key", required_argument, 0, 'K' }, // Private option
{"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:K:vVh",
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 'K':
encryptionKeyUser = 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;
}
ADEPTRemove remover;
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 (encryptionKeyUser)
{
int size = std::string(encryptionKeyUser).size();
if ((size % 2))
{
std::cout << "Error : Encryption key must be odd length" << std::endl;
goto end;
}
if (encryptionKeyUser[0] == '0' && encryptionKeyUser[1] == 'x')
{
encryptionKeyUser += 2;
size -= 2;
}
encryptionKey = new unsigned char[size/2];
for(i=0; i<size; i+=2)
{
encryptionKey[i/2] = htoi(encryptionKeyUser[i]) << 4;
encryptionKey[i/2] |= htoi(encryptionKeyUser[i+1]);
}
encryptionKeySize = size/2;
}
if (hasErrors)
goto end;
ret = remover.run();
end:
for (i=0; i<(int)ARRAY_SIZE(files); i++)
{
if (*files[i])
free((void*)*files[i]);
}
if (encryptionKey)
free(encryptionKey);
return ret;
}

View File

@@ -25,20 +25,21 @@
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/ */
#include <bytearray.h>
#include <algorithm>
#include <cctype>
#include <locale>
#include <openssl/rand.h> #include <openssl/rand.h>
#include <openssl/pkcs12.h> #include <openssl/pkcs12.h>
#include <openssl/evp.h> #include <openssl/evp.h>
#include <openssl/err.h> #include <openssl/err.h>
#include <QCoreApplication> #include <curl/curl.h>
#include <QNetworkReply>
#include <QNetworkRequest>
#include <QNetworkAccessManager>
#include <QFile>
#include <zip.h>
#include <zlib.h> #include <zlib.h>
#include <zip.h>
#include <libgourou_common.h> #include <libgourou_common.h>
#include <libgourou_log.h> #include <libgourou_log.h>
@@ -47,23 +48,28 @@
/* Digest interface */ /* Digest interface */
void* DRMProcessorClientImpl::createDigest(const std::string& digestName) 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()); 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) 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 DRMProcessorClientImpl::digestFinalize(void* handler, unsigned char* digestOut)
{ {
int res = EVP_DigestFinal((EVP_MD_CTX *)handler, digestOut, NULL); int res = EVP_DigestFinal((EVP_MD_CTX *)handler, digestOut, NULL);
EVP_MD_CTX_free((EVP_MD_CTX *)handler); 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) int DRMProcessorClientImpl::digest(const std::string& digestName, unsigned char* data, unsigned int length, unsigned char* digestOut)
@@ -83,11 +89,78 @@ void DRMProcessorClientImpl::randBytes(unsigned char* bytesOut, unsigned int len
} }
/* HTTP interface */ /* HTTP interface */
std::string DRMProcessorClientImpl::sendHTTPRequest(const std::string& URL, const std::string& POSTData, const std::string& contentType, std::map<std::string, std::string>* responseHeaders) #define HTTP_REQ_MAX_RETRY 5
#define DISPLAY_THRESHOLD 10*1024 // Threshold to display download progression
static unsigned downloadedBytes;
static int downloadProgress(void *clientp, curl_off_t dltotal, curl_off_t dlnow,
curl_off_t ultotal, curl_off_t ulnow)
{ {
QNetworkRequest request(QUrl(URL.c_str())); // For "big" files only
QNetworkAccessManager networkManager; if (dltotal >= DISPLAY_THRESHOLD && gourou::logLevel >= gourou::WARN)
QByteArray replyData; {
int percent = 0;
if (dltotal)
percent = (dlnow * 100) / dltotal;
std::cout << "\rDownload " << percent << "%" << std::flush;
}
return 0;
}
static size_t curlRead(void *data, size_t size, size_t nmemb, void *userp)
{
gourou::ByteArray* replyData = (gourou::ByteArray*) userp;
replyData->append((unsigned char*)data, size*nmemb);
return size*nmemb;
}
static size_t curlReadFd(void *data, size_t size, size_t nmemb, void *userp)
{
int fd = *(int*) userp;
size_t res = write(fd, data, size*nmemb);
downloadedBytes += res;
return res;
}
static size_t curlHeaders(char *buffer, size_t size, size_t nitems, void *userdata)
{
std::map<std::string, std::string>* responseHeaders = (std::map<std::string, std::string>*)userdata;
std::string::size_type pos = 0;
std::string buf(buffer, size*nitems);
pos = buf.find(":", pos);
if (pos != std::string::npos)
{
std::string key = std::string(buffer, pos);
std::string value = std::string(&buffer[pos+1], (size*nitems)-(pos+1));
key = gourou::trim(key);
value = gourou::trim(value);
(*responseHeaders)[key] = value;
if (gourou::logLevel >= gourou::DEBUG)
std::cout << key << " : " << value << std::endl;
}
return size*nitems;
}
std::string DRMProcessorClientImpl::sendHTTPRequest(const std::string& URL, const std::string& POSTData, const std::string& contentType, std::map<std::string, std::string>* responseHeaders, int fd, bool resume)
{
gourou::ByteArray replyData;
std::map<std::string, std::string> localHeaders;
if (!responseHeaders)
responseHeaders = &localHeaders;
GOUROU_LOG(gourou::INFO, "Send request to " << URL); GOUROU_LOG(gourou::INFO, "Send request to " << URL);
if (POSTData.size()) if (POSTData.size())
@@ -95,48 +168,111 @@ std::string DRMProcessorClientImpl::sendHTTPRequest(const std::string& URL, cons
GOUROU_LOG(gourou::DEBUG, "<<< " << std::endl << POSTData); GOUROU_LOG(gourou::DEBUG, "<<< " << std::endl << POSTData);
} }
request.setRawHeader("Accept", "*/*"); unsigned prevDownloadedBytes;
request.setRawHeader("User-Agent", "book2png"); downloadedBytes = 0;
if (contentType.size()) if (fd && resume)
request.setRawHeader("Content-Type", contentType.c_str()); {
struct stat _stat;
if (!fstat(fd, &_stat))
{
GOUROU_LOG(gourou::WARN, "Resume download @ " << _stat.st_size << " bytes");
downloadedBytes = _stat.st_size;
}
else
GOUROU_LOG(gourou::WARN, "Want to resume, but fstat failed");
}
QNetworkReply* reply; CURL *curl = curl_easy_init();
CURLcode res;
curl_easy_setopt(curl, CURLOPT_URL, URL.c_str());
curl_easy_setopt(curl, CURLOPT_USERAGENT, "book2png");
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1);
struct curl_slist *list = NULL;
list = curl_slist_append(list, "Accept: */*");
std::string _contentType;
if (contentType.size())
{
_contentType = "Content-Type: " + contentType;
list = curl_slist_append(list, _contentType.c_str());
}
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, list);
if (POSTData.size()) if (POSTData.size())
reply = networkManager.post(request, POSTData.c_str());
else
reply = networkManager.get(request);
QCoreApplication* app = QCoreApplication::instance();
networkManager.moveToThread(app->thread());
while (!reply->isFinished())
app->processEvents();
QByteArray location = reply->rawHeader("Location");
if (location.size() != 0)
{ {
GOUROU_LOG(gourou::DEBUG, "New location"); curl_easy_setopt(curl, CURLOPT_POST, 1L);
return sendHTTPRequest(location.constData(), POSTData, contentType, responseHeaders); curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, POSTData.size());
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, POSTData.data());
} }
if (reply->error() != QNetworkReply::NoError) if (fd)
EXCEPTION(gourou::CLIENT_NETWORK_ERROR, "Error " << reply->errorString().toStdString()); {
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, curlReadFd);
QList<QByteArray> headers = reply->rawHeaderList(); curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void*)&fd);
for (int i = 0; i < headers.size(); ++i) { }
if (gourou::logLevel >= gourou::DEBUG) else
std::cout << headers[i].constData() << " : " << reply->rawHeader(headers[i]).constData() << std::endl; {
if (responseHeaders) curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, curlRead);
(*responseHeaders)[headers[i].constData()] = reply->rawHeader(headers[i]).constData(); curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void*)&replyData);
} }
replyData = reply->readAll(); curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, curlHeaders);
if (reply->rawHeader("Content-Type") == "application/vnd.adobe.adept+xml") curl_easy_setopt(curl, CURLOPT_HEADERDATA, (void*)responseHeaders);
curl_easy_setopt(curl, CURLOPT_XFERINFOFUNCTION, downloadProgress);
curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 0);
for (int i=0; i<HTTP_REQ_MAX_RETRY; i++)
{
prevDownloadedBytes = downloadedBytes;
if (downloadedBytes)
curl_easy_setopt(curl, CURLOPT_RESUME_FROM, downloadedBytes);
res = curl_easy_perform(curl);
// Connexion failed, wait & retry
if (res == CURLE_COULDNT_CONNECT)
{
GOUROU_LOG(gourou::WARN, "\nConnection failed, attempt " << (i+1) << "/" << HTTP_REQ_MAX_RETRY);
}
// Transfer failed but some data has been received
// --> try again without incrementing tries
else if (res == CURLE_RECV_ERROR)
{
if (prevDownloadedBytes != downloadedBytes)
{
GOUROU_LOG(gourou::WARN, "\nConnection broken, but data received, try again");
i--;
}
else
GOUROU_LOG(gourou::WARN, "\nConnection broken and no data received, attempt " << (i+1) << "/" << HTTP_REQ_MAX_RETRY);
}
// Other error --> fail
else
break;
// Wait a little bit (250ms * i)
usleep((250 * 1000) * (i+1));
}
curl_slist_free_all(list);
curl_easy_cleanup(curl);
if (res != CURLE_OK)
EXCEPTION(gourou::CLIENT_NETWORK_ERROR, "Error " << curl_easy_strerror(res));
if ((downloadedBytes >= DISPLAY_THRESHOLD || replyData.size() >= DISPLAY_THRESHOLD) &&
gourou::logLevel >= gourou::WARN)
std::cout << std::endl;
if ((*responseHeaders)["Content-Type"] == "application/vnd.adobe.adept+xml")
{ {
GOUROU_LOG(gourou::DEBUG, ">>> " << std::endl << replyData.data()); GOUROU_LOG(gourou::DEBUG, ">>> " << std::endl << replyData.data());
} }
return std::string(replyData.data(), replyData.length()); return std::string((char*)replyData.data(), replyData.length());
} }
void DRMProcessorClientImpl::RSAPrivateEncrypt(const unsigned char* RSAKey, unsigned int RSAKeyLength, void DRMProcessorClientImpl::RSAPrivateEncrypt(const unsigned char* RSAKey, unsigned int RSAKeyLength,
@@ -163,8 +299,39 @@ void DRMProcessorClientImpl::RSAPrivateEncrypt(const unsigned char* RSAKey, unsi
if (gourou::logLevel >= gourou::DEBUG) if (gourou::logLevel >= gourou::DEBUG)
{ {
printf("Sig : "); printf("Encrypted : ");
for(int i=0; i<(int)sizeof(res); i++) 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("%02x ", res[i]);
printf("\n"); printf("\n");
} }
@@ -252,33 +419,35 @@ void DRMProcessorClientImpl::extractCertificate(const unsigned char* RSAKey, uns
} }
/* Crypto interface */ /* Crypto interface */
void DRMProcessorClientImpl::AESEncrypt(CHAINING_MODE chaining, void DRMProcessorClientImpl::Encrypt(CRYPTO_ALGO algo, CHAINING_MODE chaining,
const unsigned char* key, unsigned int keyLength, const unsigned char* key, unsigned int keyLength,
const unsigned char* iv, unsigned int ivLength, const unsigned char* iv, unsigned int ivLength,
const unsigned char* dataIn, unsigned int dataInLength, const unsigned char* dataIn, unsigned int dataInLength,
unsigned char* dataOut, unsigned int* dataOutLength) unsigned char* dataOut, unsigned int* dataOutLength)
{ {
void* handler = AESEncryptInit(chaining, key, keyLength, iv, ivLength); void* handler = EncryptInit(algo, chaining, key, keyLength, iv, ivLength);
AESEncryptUpdate(handler, dataIn, dataInLength, dataOut, dataOutLength); EncryptUpdate(handler, dataIn, dataInLength, dataOut, dataOutLength);
AESEncryptFinalize(handler, dataOut+*dataOutLength, 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* key, unsigned int keyLength,
const unsigned char* iv, unsigned int ivLength) const unsigned char* iv, unsigned int ivLength)
{ {
EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new(); EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new();
if (algo == ALGO_AES)
{
switch(keyLength) switch(keyLength)
{ {
case 16: case 16:
switch(chaining) switch(chaining)
{ {
case CHAIN_ECB: case CHAIN_ECB:
EVP_EncryptInit_ex(ctx, EVP_aes_128_ecb(), NULL, key, iv); EVP_EncryptInit(ctx, EVP_aes_128_ecb(), key, iv);
break; break;
case CHAIN_CBC: case CHAIN_CBC:
EVP_EncryptInit_ex(ctx, EVP_aes_128_cbc(), NULL, key, iv); EVP_EncryptInit(ctx, EVP_aes_128_cbc(), key, iv);
break; break;
default: default:
EXCEPTION(gourou::CLIENT_BAD_CHAINING, "Unknown chaining mode " << chaining); EXCEPTION(gourou::CLIENT_BAD_CHAINING, "Unknown chaining mode " << chaining);
@@ -288,26 +457,37 @@ void* DRMProcessorClientImpl::AESEncryptInit(CHAINING_MODE chaining,
EVP_CIPHER_CTX_free(ctx); EVP_CIPHER_CTX_free(ctx);
EXCEPTION(gourou::CLIENT_BAD_KEY_SIZE, "Invalid key size " << keyLength); 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; return ctx;
} }
void* DRMProcessorClientImpl::AESDecryptInit(CHAINING_MODE chaining, void* DRMProcessorClientImpl::DecryptInit(CRYPTO_ALGO algo, CHAINING_MODE chaining,
const unsigned char* key, unsigned int keyLength, const unsigned char* key, unsigned int keyLength,
const unsigned char* iv, unsigned int ivLength) const unsigned char* iv, unsigned int ivLength)
{ {
EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new(); EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new();
if (algo == ALGO_AES)
{
switch(keyLength) switch(keyLength)
{ {
case 16: case 16:
switch(chaining) switch(chaining)
{ {
case CHAIN_ECB: case CHAIN_ECB:
EVP_DecryptInit_ex(ctx, EVP_aes_128_ecb(), NULL, key, iv); EVP_DecryptInit(ctx, EVP_aes_128_ecb(), key, iv);
break; break;
case CHAIN_CBC: case CHAIN_CBC:
EVP_DecryptInit_ex(ctx, EVP_aes_128_cbc(), NULL, key, iv); EVP_DecryptInit(ctx, EVP_aes_128_cbc(), key, iv);
break; break;
default: default:
EXCEPTION(gourou::CLIENT_BAD_CHAINING, "Unknown chaining mode " << chaining); EXCEPTION(gourou::CLIENT_BAD_CHAINING, "Unknown chaining mode " << chaining);
@@ -317,17 +497,27 @@ void* DRMProcessorClientImpl::AESDecryptInit(CHAINING_MODE chaining,
EVP_CIPHER_CTX_free(ctx); EVP_CIPHER_CTX_free(ctx);
EXCEPTION(gourou::CLIENT_BAD_KEY_SIZE, "Invalid key size " << keyLength); 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; return ctx;
} }
void DRMProcessorClientImpl::AESEncryptUpdate(void* handler, const unsigned char* dataIn, unsigned int dataInLength, void DRMProcessorClientImpl::EncryptUpdate(void* handler, const unsigned char* dataIn, unsigned int dataInLength,
unsigned char* dataOut, unsigned int* dataOutLength) unsigned char* dataOut, unsigned int* dataOutLength)
{ {
EVP_EncryptUpdate((EVP_CIPHER_CTX*)handler, dataOut, (int*)dataOutLength, dataIn, dataInLength); EVP_EncryptUpdate((EVP_CIPHER_CTX*)handler, dataOut, (int*)dataOutLength, dataIn, dataInLength);
} }
void DRMProcessorClientImpl::AESEncryptFinalize(void* handler, void DRMProcessorClientImpl::EncryptFinalize(void* handler,
unsigned char* dataOut, unsigned int* dataOutLength) unsigned char* dataOut, unsigned int* dataOutLength)
{ {
int len; int len;
@@ -336,24 +526,24 @@ void DRMProcessorClientImpl::AESEncryptFinalize(void* handler,
EVP_CIPHER_CTX_free((EVP_CIPHER_CTX*)handler); EVP_CIPHER_CTX_free((EVP_CIPHER_CTX*)handler);
} }
void DRMProcessorClientImpl::AESDecrypt(CHAINING_MODE chaining, void DRMProcessorClientImpl::Decrypt(CRYPTO_ALGO algo, CHAINING_MODE chaining,
const unsigned char* key, unsigned int keyLength, const unsigned char* key, unsigned int keyLength,
const unsigned char* iv, unsigned int ivLength, const unsigned char* iv, unsigned int ivLength,
const unsigned char* dataIn, unsigned int dataInLength, const unsigned char* dataIn, unsigned int dataInLength,
unsigned char* dataOut, unsigned int* dataOutLength) unsigned char* dataOut, unsigned int* dataOutLength)
{ {
void* handler = AESDecryptInit(chaining, key, keyLength, iv, ivLength); void* handler = DecryptInit(algo, chaining, key, keyLength, iv, ivLength);
AESDecryptUpdate(handler, dataIn, dataInLength, dataOut, dataOutLength); DecryptUpdate(handler, dataIn, dataInLength, dataOut, dataOutLength);
AESDecryptFinalize(handler, dataOut+*dataOutLength, dataOutLength); DecryptFinalize(handler, dataOut+*dataOutLength, dataOutLength);
} }
void DRMProcessorClientImpl::AESDecryptUpdate(void* handler, const unsigned char* dataIn, unsigned int dataInLength, void DRMProcessorClientImpl::DecryptUpdate(void* handler, const unsigned char* dataIn, unsigned int dataInLength,
unsigned char* dataOut, unsigned int* dataOutLength) unsigned char* dataOut, unsigned int* dataOutLength)
{ {
EVP_DecryptUpdate((EVP_CIPHER_CTX*)handler, dataOut, (int*)dataOutLength, dataIn, dataInLength); 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; int len;
EVP_DecryptFinal_ex((EVP_CIPHER_CTX*)handler, dataOut, &len); EVP_DecryptFinal_ex((EVP_CIPHER_CTX*)handler, dataOut, &len);
@@ -371,35 +561,39 @@ void* DRMProcessorClientImpl::zipOpen(const std::string& path)
return handler; 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; std::string res;
unsigned char* buffer;
zip_stat_t sb; zip_stat_t sb;
if (zip_stat((zip_t *)handler, path.c_str(), 0, &sb) < 0) 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))) if (!(sb.valid & (ZIP_STAT_INDEX|ZIP_STAT_SIZE)))
EXCEPTION(gourou::CLIENT_ZIP_ERROR, "Required fields missing"); 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_file_t *f = zip_fopen_index((zip_t *)handler, sb.index, (decompress)?0:ZIP_FL_COMPRESSED);
zip_fread(f, result.data(), sb.size);
zip_fread(f, buffer, sb.size);
zip_fclose(f); 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); zip_int64_t ret;
if (zip_file_add((zip_t*)handler, path.c_str(), s, ZIP_FL_OVERWRITE|ZIP_FL_ENC_UTF_8) < 0)
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); zip_source_free(s);
EXCEPTION(gourou::CLIENT_ZIP_ERROR, "Zip error " << zip_strerror((zip_t *)handler)); EXCEPTION(gourou::CLIENT_ZIP_ERROR, "Zip error " << zip_strerror((zip_t *)handler));
@@ -422,7 +616,7 @@ void DRMProcessorClientImpl::zipClose(void* handler)
zip_close((zip_t*)handler); zip_close((zip_t*)handler);
} }
void DRMProcessorClientImpl::inflate(std::string data, gourou::ByteArray& result, void DRMProcessorClientImpl::inflate(gourou::ByteArray& data, gourou::ByteArray& result,
int wbits) int wbits)
{ {
unsigned int dataSize = data.size()*2; unsigned int dataSize = data.size()*2;
@@ -435,19 +629,24 @@ void DRMProcessorClientImpl::inflate(std::string data, gourou::ByteArray& result
infstream.opaque = Z_NULL; infstream.opaque = Z_NULL;
infstream.avail_in = (uInt)data.size(); infstream.avail_in = (uInt)data.size();
infstream.next_in = (Bytef *)data.c_str(); // input char array infstream.next_in = (Bytef *)data.data(); // input char array
infstream.avail_out = (uInt)dataSize; // size of output infstream.avail_out = (uInt)dataSize; // size of output
infstream.next_out = (Bytef *)buffer; // output char array infstream.next_out = (Bytef *)buffer; // output char array
int ret = inflateInit2(&infstream, wbits); int ret = inflateInit2(&infstream, wbits);
if (ret != Z_OK) if (ret != Z_OK)
EXCEPTION(gourou::CLIENT_ZIP_ERROR, infstream.msg); EXCEPTION(gourou::CLIENT_ZIP_ERROR, "Inflate error, code " << zError(ret) << ", msg " << infstream.msg);
ret = ::inflate(&infstream, Z_FINISH); ret = ::inflate(&infstream, Z_FINISH);
while (ret == Z_OK || ret == Z_STREAM_END) while (ret == Z_OK || ret == Z_STREAM_END || ret == Z_BUF_ERROR)
{ {
// Real error
if (ret == Z_BUF_ERROR && infstream.avail_out == (uInt)dataSize)
break;
result.append(buffer, dataSize-infstream.avail_out); result.append(buffer, dataSize-infstream.avail_out);
if ((ret == Z_OK && infstream.avail_out != 0) || ret == Z_STREAM_END) if ((ret == Z_OK && infstream.avail_out != 0) || ret == Z_STREAM_END)
break; break;
infstream.avail_out = (uInt)dataSize; // size of output infstream.avail_out = (uInt)dataSize; // size of output
@@ -456,15 +655,15 @@ void DRMProcessorClientImpl::inflate(std::string data, gourou::ByteArray& result
} }
if (ret == Z_STREAM_END) if (ret == Z_STREAM_END)
ret = deflateEnd(&infstream); ret = inflateEnd(&infstream);
delete[] buffer; delete[] buffer;
if (ret != Z_OK && ret != Z_STREAM_END) if (ret != Z_OK && ret != Z_STREAM_END)
EXCEPTION(gourou::CLIENT_ZIP_ERROR, zError(ret)); EXCEPTION(gourou::CLIENT_ZIP_ERROR, "Inflate error, code " << zError(ret) << ", msg " << infstream.msg);
} }
void DRMProcessorClientImpl::deflate(std::string data, gourou::ByteArray& result, void DRMProcessorClientImpl::deflate(gourou::ByteArray& data, gourou::ByteArray& result,
int wbits, int compressionLevel) int wbits, int compressionLevel)
{ {
unsigned int dataSize = data.size(); unsigned int dataSize = data.size();
@@ -476,8 +675,8 @@ void DRMProcessorClientImpl::deflate(std::string data, gourou::ByteArray& result
defstream.zfree = Z_NULL; defstream.zfree = Z_NULL;
defstream.opaque = Z_NULL; defstream.opaque = Z_NULL;
defstream.avail_in = (uInt)data.size(); defstream.avail_in = (uInt)dataSize;
defstream.next_in = (Bytef *)data.c_str(); // input char array defstream.next_in = (Bytef *)data.data(); // input char array
defstream.avail_out = (uInt)dataSize; // size of output defstream.avail_out = (uInt)dataSize; // size of output
defstream.next_out = (Bytef *)buffer; // output char array defstream.next_out = (Bytef *)buffer; // output char array
@@ -485,7 +684,7 @@ void DRMProcessorClientImpl::deflate(std::string data, gourou::ByteArray& result
compressionLevel, Z_DEFAULT_STRATEGY); compressionLevel, Z_DEFAULT_STRATEGY);
if (ret != Z_OK) if (ret != Z_OK)
EXCEPTION(gourou::CLIENT_ZIP_ERROR, defstream.msg); EXCEPTION(gourou::CLIENT_ZIP_ERROR, "Deflate error, code " << zError(ret) << ", msg " << defstream.msg);
ret = ::deflate(&defstream, Z_FINISH); ret = ::deflate(&defstream, Z_FINISH);
while (ret == Z_OK || ret == Z_STREAM_END) while (ret == Z_OK || ret == Z_STREAM_END)
@@ -504,5 +703,5 @@ void DRMProcessorClientImpl::deflate(std::string data, gourou::ByteArray& result
delete[] buffer; delete[] buffer;
if (ret != Z_OK && ret != Z_STREAM_END) if (ret != Z_OK && ret != Z_STREAM_END)
EXCEPTION(gourou::CLIENT_ZIP_ERROR, zError(ret)); EXCEPTION(gourou::CLIENT_ZIP_ERROR, "Deflate error, code " << zError(ret) << ", msg " << defstream.msg);
} }

View File

@@ -35,7 +35,7 @@
class DRMProcessorClientImpl : public gourou::DRMProcessorClient class DRMProcessorClientImpl : public gourou::DRMProcessorClient
{ {
public: public:
/* Digest interface */ /* Digest interface */
virtual void* createDigest(const std::string& digestName); virtual void* createDigest(const std::string& digestName);
virtual int digestUpdate(void* handler, unsigned char* data, unsigned int length); 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); virtual void randBytes(unsigned char* bytesOut, unsigned int length);
/* HTTP interface */ /* HTTP interface */
virtual std::string sendHTTPRequest(const std::string& URL, const std::string& POSTData=std::string(""), const std::string& contentType=std::string(""), std::map<std::string, std::string>* responseHeaders=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, int fd=0, bool resume=false);
virtual void RSAPrivateEncrypt(const unsigned char* RSAKey, unsigned int RSAKeyLength, virtual void RSAPrivateEncrypt(const unsigned char* RSAKey, unsigned int RSAKeyLength,
const RSA_KEY_TYPE keyType, const std::string& password, const RSA_KEY_TYPE keyType, const std::string& password,
const unsigned char* data, unsigned dataLength, const unsigned char* data, unsigned dataLength,
unsigned char* res); 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, virtual void RSAPublicEncrypt(const unsigned char* RSAKey, unsigned int RSAKeyLength,
const RSA_KEY_TYPE keyType, const RSA_KEY_TYPE keyType,
const unsigned char* data, unsigned dataLength, const unsigned char* data, unsigned dataLength,
@@ -68,49 +73,50 @@ class DRMProcessorClientImpl : public gourou::DRMProcessorClient
unsigned char** certOut, unsigned int* certOutLength); unsigned char** certOut, unsigned int* certOutLength);
/* Crypto interface */ /* Crypto interface */
virtual void AESEncrypt(CHAINING_MODE chaining, virtual void Encrypt(CRYPTO_ALGO algo, CHAINING_MODE chaining,
const unsigned char* key, unsigned int keyLength, const unsigned char* key, unsigned int keyLength,
const unsigned char* iv, unsigned int ivLength, const unsigned char* iv, unsigned int ivLength,
const unsigned char* dataIn, unsigned int dataInLength, const unsigned char* dataIn, unsigned int dataInLength,
unsigned char* dataOut, unsigned int* dataOutLength); unsigned char* dataOut, unsigned int* dataOutLength);
virtual void* AESEncryptInit(CHAINING_MODE chaining, virtual void* EncryptInit(CRYPTO_ALGO algo, CHAINING_MODE chaining,
const unsigned char* key, unsigned int keyLength, const unsigned char* key, unsigned int keyLength,
const unsigned char* iv=0, unsigned int ivLength=0); 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); 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, virtual void Decrypt(CRYPTO_ALGO algo, CHAINING_MODE chaining,
const unsigned char* key, unsigned int keyLength, const unsigned char* key, unsigned int keyLength,
const unsigned char* iv, unsigned int ivLength, const unsigned char* iv, unsigned int ivLength,
const unsigned char* dataIn, unsigned int dataInLength, const unsigned char* dataIn, unsigned int dataInLength,
unsigned char* dataOut, unsigned int* dataOutLength); unsigned char* dataOut, unsigned int* dataOutLength);
virtual void* AESDecryptInit(CHAINING_MODE chaining, virtual void* DecryptInit(CRYPTO_ALGO algo, CHAINING_MODE chaining,
const unsigned char* key, unsigned int keyLength, const unsigned char* key, unsigned int keyLength,
const unsigned char* iv=0, unsigned int ivLength=0); const unsigned char* iv=0, unsigned int ivLength=0);
virtual void AESDecryptUpdate(void* handler, const unsigned char* dataIn, unsigned int dataInLength, virtual void DecryptUpdate(void* handler, const unsigned char* dataIn, unsigned int dataInLength,
unsigned char* dataOut, unsigned int* dataOutLength); unsigned char* dataOut, unsigned int* dataOutLength);
virtual void AESDecryptFinalize(void* handler, unsigned char* dataOut, unsigned int* dataOutLength); virtual void DecryptFinalize(void* handler, unsigned char* dataOut, unsigned int* dataOutLength);
/* ZIP Interface */ /* ZIP Interface */
virtual void* zipOpen(const std::string& path); 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 zipDeleteFile(void* handler, const std::string& path);
virtual void zipClose(void* handler); virtual void zipClose(void* handler);
virtual void inflate(std::string data, gourou::ByteArray& result, int wbits=-15); virtual void inflate(gourou::ByteArray& data, gourou::ByteArray& result,
int wbits=-15);
virtual void deflate(std::string data, gourou::ByteArray& result, virtual void deflate(gourou::ByteArray& data, gourou::ByteArray& result,
int wbits=-15, int compressionLevel=8); int wbits=-15, int compressionLevel=8);
}; };

127
utils/utils_common.cpp Normal file
View File

@@ -0,0 +1,127 @@
/*
Copyright (c) 2022, 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 <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <string.h>
#include <stdio.h>
#include <fcntl.h>
#include <limits.h>
#include <iostream>
#include <libgourou.h>
#include <libgourou_common.h>
#include "utils_common.h"
static const char* defaultDirs[] = {
".adept/",
"./adobe-digital-editions/",
"./.adobe-digital-editions/"
};
void version(void)
{
std::cout << "Current libgourou version : " << gourou::DRMProcessor::VERSION << std::endl ;
}
bool fileExists(const char* filename)
{
struct stat _stat;
int ret = stat(filename, &_stat);
return (ret == 0);
}
const char* findFile(const char* filename, bool inDefaultDirs)
{
if (fileExists(filename))
return strdup(filename);
if (!inDefaultDirs) return 0;
for (int i=0; i<(int)ARRAY_SIZE(defaultDirs); i++)
{
std::string path = std::string(defaultDirs[i]) + filename;
if (fileExists(path.c_str()))
return strdup(path.c_str());
}
return 0;
}
// https://stackoverflow.com/questions/2336242/recursive-mkdir-system-call-on-unix
void mkpath(const char *dir)
{
char tmp[PATH_MAX];
char *p = NULL;
size_t len;
snprintf(tmp, sizeof(tmp),"%s",dir);
len = strlen(tmp);
if (tmp[len - 1] == '/')
tmp[len - 1] = 0;
for (p = tmp + 1; *p; p++)
if (*p == '/') {
*p = 0;
mkdir(tmp, S_IRWXU);
*p = '/';
}
mkdir(tmp, S_IRWXU);
}
void fileCopy(const char* in, const char* out)
{
char buffer[4096];
int ret, fdIn, fdOut;
fdIn = open(in, O_RDONLY);
if (!fdIn)
EXCEPTION(gourou::CLIENT_FILE_ERROR, "Unable to open " << in);
fdOut = gourou::createNewFile(out);
if (!fdOut)
{
close (fdIn);
EXCEPTION(gourou::CLIENT_FILE_ERROR, "Unable to open " << out);
}
while (true)
{
ret = ::read(fdIn, buffer, sizeof(buffer));
if (ret <= 0)
break;
::write(fdOut, buffer, ret);
}
close (fdIn);
close (fdOut);
}

67
utils/utils_common.h Normal file
View File

@@ -0,0 +1,67 @@
/*
Copyright (c) 2022, 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.
*/
#ifndef _UTILS_COMMON_H_
#define _UTILS_COMMON_H_
#define LOANS_DIR "loans/"
#define ID_HASH_SIZE 16
#define ARRAY_SIZE(arr) (sizeof(arr)/sizeof(arr[0]))
/**
* @brief Display libgourou version
*/
void version(void);
/**
* @brief Find a given filename in current directory and/or in default directories
*
* @param filename Filename to search
* @param inDefaultDirs Search is default directories or not
*
* @return A copy of full path
*/
const char* findFile(const char* filename, bool inDefaultDirs=true);
/**
* @brief Does the file (or directory exists)
*/
bool fileExists(const char* filename);
/**
* @brief Recursively created dir
*/
void mkpath(const char *dir);
/**
* @brief Copy file in into file out
*/
void fileCopy(const char* in, const char* out);
#endif