forked from soutade/libgourou
Compare commits
18 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 6bff559806 | |||
| 8a86bafbdc | |||
| 28aefba6d6 | |||
| e0e2bc7430 | |||
| d3c90f03bb | |||
| 469d378f9a | |||
| 98b531a232 | |||
| 956bad3068 | |||
| d9a920b062 | |||
| 204500117d | |||
| ce2cf4192a | |||
| 68bf48df27 | |||
| 81faf1f9be | |||
| f60abf04d8 | |||
| 0d77cf55e1 | |||
| 86a79cc381 | |||
| 68bf982b6f | |||
| ef8c2644ca |
@@ -1,16 +1,16 @@
|
||||
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.
|
||||
libgourou is a free implementation of Adobe's ADEPT protocol used to add DRM on ePub/PDF files. It overcomes the lack of Adobe support for Linux platforms.
|
||||
|
||||
|
||||
Architecture
|
||||
------------
|
||||
|
||||
Like RMSDK, libgourou has a client/server scheme. All platform specific functions (crypto, network...) has to be implemented in a client class (that derives from DRMProcessorClient) while server implements ADEPT protocol.
|
||||
Like RMSDK, libgourou has a client/server scheme. All platform specific functions (crypto, network...) have to be implemented in a client class (that derives from DRMProcessorClient) while server implements ADEPT protocol.
|
||||
A reference implementation using cURL, OpenSSL and libzip is provided (in _utils_ directory).
|
||||
|
||||
Main fucntions to use from gourou::DRMProcessor are :
|
||||
Main functions to use from gourou::DRMProcessor are:
|
||||
|
||||
* Get an ePub from an ACSM file : _fulfill()_ and _download()_
|
||||
* Create a new device : _createDRMProcessor()_
|
||||
@@ -18,41 +18,43 @@ Main fucntions to use from gourou::DRMProcessor are :
|
||||
* Remove DRM : _removeDRM()_
|
||||
* Return loaned book : _returnLoan()_
|
||||
|
||||
You can import configuration from (at least) :
|
||||
You can import configuration from (at least):
|
||||
|
||||
* Kobo device : .adept/device.xml, .adept/devicesalt and .adept/activation.xml
|
||||
* Bookeen device : .adobe-digital-editions/device.xml, root/devkey.bin and .adobe-digital-editions/activation.xml
|
||||
|
||||
Or create a new one. Be careful : there is a limited number of devices that can be created bye one account.
|
||||
Or create a new one. Be careful: there is a limited number of devices that can be created by one account.
|
||||
|
||||
ePub are encrypted using a shared key : one account / multiple devices, so you can create and register a device into your computer and read downloaded (and encrypted) ePub file with your eReader configured using the same AdobeID account.
|
||||
ePub are encrypted using a shared key: one account / multiple devices, so you can create and register a device into your computer and read downloaded (and encrypted) ePub file with your eReader configured using the same AdobeID account.
|
||||
|
||||
For those who wants to remove DRM without adept_remove, you can export your private key and import it within [Calibre](https://calibre-ebook.com/) an its DeDRM plugin.
|
||||
For those who want 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
|
||||
------------
|
||||
|
||||
For libgourou :
|
||||
For libgourou:
|
||||
|
||||
_externals_ :
|
||||
|
||||
* libpugixml
|
||||
|
||||
_internals_ :
|
||||
_internals_:
|
||||
|
||||
* uPDFParser
|
||||
|
||||
For utils :
|
||||
For utils:
|
||||
|
||||
* libcurl
|
||||
* OpenSSL
|
||||
* openssl
|
||||
* libzip
|
||||
* libpugixml
|
||||
|
||||
|
||||
Internal libraries are automatically fetched and statically compiled during the first run.
|
||||
When you update libgourou's repository, **don't forget to update internal libraries** with :
|
||||
External & utils dependencies has to be installed by your package manager (_apt_ for example).
|
||||
Use _-dev_ flavours to get needed headers.
|
||||
Internal libraries are automatically fetched and statically compiled during the first compilation.
|
||||
When you update libgourou's repository, **don't forget to update internal libraries** with:
|
||||
|
||||
make update_lib
|
||||
|
||||
@@ -92,31 +94,31 @@ You can optionaly specify your .adept directory
|
||||
|
||||
export ADEPT_DIR=/home/XXX
|
||||
|
||||
Then, use utils as following :
|
||||
Then, use utils as following:
|
||||
|
||||
You can import configuration from your eReader or create a new one with _utils/adept\_activate_ :
|
||||
You can import configuration from your eReader or create a new one with _utils/adept\_activate_:
|
||||
|
||||
./utils/adept_activate -u <AdobeID USERNAME>
|
||||
|
||||
Then a _/home/<user>/.config/adept_ directory is created with all configuration file
|
||||
|
||||
To download an ePub/PDF :
|
||||
To download an ePub/PDF:
|
||||
|
||||
./utils/acsmdownloader <ACSM_FILE>
|
||||
|
||||
To export your private key (for DeDRM software) :
|
||||
To export your private key (for DeDRM software):
|
||||
|
||||
./utils/acsmdownloader --export-private-key [-o adobekey_1.der]
|
||||
|
||||
To remove ADEPT DRM :
|
||||
To remove ADEPT DRM:
|
||||
|
||||
./utils/adept_remove <encryptedFile>
|
||||
|
||||
To list loaned books :
|
||||
To list loaned books:
|
||||
|
||||
./utils/adept_loan_mgt [-l]
|
||||
|
||||
To return a loaned book :
|
||||
To return a loaned book:
|
||||
|
||||
./utils/adept_loan_mgt -r <id>
|
||||
|
||||
@@ -124,6 +126,12 @@ To return a loaned book :
|
||||
You can get utils full options description with -h or --help switch
|
||||
|
||||
|
||||
Binary packages
|
||||
---------------
|
||||
|
||||
Compiled version (and AppImage) of libgourou and utils are available in [Release page](https://forge.soutade.fr/soutade/libgourou/releases)
|
||||
|
||||
|
||||
Docker
|
||||
------
|
||||
|
||||
@@ -150,3 +158,18 @@ Special thanks
|
||||
* _Jens_ for all test samples and utils testing
|
||||
* _Milian_ for debug & code
|
||||
* _Berwyn H_ for all test samples, feedbacks, patches and kind donation
|
||||
|
||||
|
||||
Donation
|
||||
--------
|
||||
|
||||
https://www.paypal.com/donate/?hosted_button_id=JD3U6XMZCPHKN
|
||||
|
||||
|
||||
Donators
|
||||
--------
|
||||
|
||||
* _Berwyn H_
|
||||
* _bwitt_
|
||||
* _Ismail_
|
||||
* _Radon_
|
||||
|
||||
+1
-1
@@ -37,7 +37,7 @@
|
||||
#define ACS_SERVER "http://adeactivate.adobe.com/adept"
|
||||
#endif
|
||||
|
||||
#define LIBGOUROU_VERSION "0.8.3"
|
||||
#define LIBGOUROU_VERSION "0.8.7"
|
||||
|
||||
namespace gourou
|
||||
{
|
||||
|
||||
+1
-1
@@ -2,7 +2,7 @@
|
||||
|
||||
# uPDFParser
|
||||
if [ ! -d lib/updfparser ] ; then
|
||||
git clone git://soutade.fr/updfparser.git lib/updfparser
|
||||
git clone https://forge.soutade.fr/soutade/uPDFParser.git lib/updfparser
|
||||
pushd lib/updfparser
|
||||
make BUILD_STATIC=1 BUILD_SHARED=0
|
||||
popd
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
if [ ! -d lib/updfparser ] ; then
|
||||
echo "Some libraries are missing"
|
||||
echo "You must run this script at the top of libgourou working direcotry."
|
||||
echo "./lib/setup.sh must be called first (make all)"
|
||||
echo "./scripts/setup.sh must be called first (make all)"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
|
||||
+21
-10
@@ -859,14 +859,21 @@ namespace gourou
|
||||
std::string DRMProcessor::getDefaultAdeptDir(void)
|
||||
{
|
||||
#ifndef DEFAULT_ADEPT_DIR
|
||||
const char* user = getenv("USER");
|
||||
const char* home = getenv("HOME");
|
||||
|
||||
if (home)
|
||||
return home + std::string("/.config/adept/");
|
||||
else
|
||||
{
|
||||
const char* user = getenv("USER");
|
||||
|
||||
if (user && user[0])
|
||||
{
|
||||
return std::string("/home/") + user + std::string("/.config/adept/");
|
||||
}
|
||||
else
|
||||
return LOCAL_ADEPT_DIR;
|
||||
if (user && user[0])
|
||||
{
|
||||
return std::string("/home/") + user + std::string("/.config/adept/");
|
||||
}
|
||||
else
|
||||
return LOCAL_ADEPT_DIR;
|
||||
}
|
||||
#else
|
||||
return DEFAULT_ADEPT_DIR "/";
|
||||
#endif
|
||||
@@ -1317,6 +1324,7 @@ namespace gourou
|
||||
std::vector<uPDFParser::Object*> objects = parser.objects();
|
||||
std::vector<uPDFParser::Object*>::iterator it;
|
||||
std::vector<uPDFParser::Object*>::reverse_iterator rIt;
|
||||
std::vector<uPDFParser::Object*> ebxObjects;
|
||||
unsigned char decryptedKey[16];
|
||||
int ebxId;
|
||||
|
||||
@@ -1327,7 +1335,7 @@ namespace gourou
|
||||
{
|
||||
EBXHandlerFound = true;
|
||||
uPDFParser::Object* ebx = *rIt;
|
||||
|
||||
|
||||
ebxVersion = (uPDFParser::Integer*)(*ebx)["V"];
|
||||
if (ebxVersion->value() != 4)
|
||||
{
|
||||
@@ -1338,7 +1346,7 @@ namespace gourou
|
||||
{
|
||||
EXCEPTION(DRM_ERR_ENCRYPTION_KEY, "No ADEPT_LICENSE found");
|
||||
}
|
||||
|
||||
|
||||
uPDFParser::String* licenseObject = (uPDFParser::String*)(*ebx)["ADEPT_LICENSE"];
|
||||
|
||||
std::string value = licenseObject->value();
|
||||
@@ -1375,7 +1383,7 @@ namespace gourou
|
||||
|
||||
if (object->objectId() == ebxId)
|
||||
{
|
||||
// object->deleteKey("Filter");
|
||||
ebxObjects.push_back(object);
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -1485,6 +1493,9 @@ namespace gourou
|
||||
}
|
||||
}
|
||||
|
||||
for(it = ebxObjects.begin(); it != ebxObjects.end(); it++)
|
||||
parser.removeObject(*it);
|
||||
|
||||
uPDFParser::Object& trailer = parser.getTrailer();
|
||||
trailer.deleteKey("Encrypt");
|
||||
|
||||
|
||||
@@ -229,7 +229,12 @@ private:
|
||||
maxSizeBookName = loan->bookName.size();
|
||||
}
|
||||
|
||||
if (maxSizeBookName > MAX_SIZE_BOOK_NAME)
|
||||
/* Manage empty names */
|
||||
if (maxSizeBookName == 0)
|
||||
maxSizeBookName = sizeof("No name ")-1;
|
||||
else if (maxSizeBookName < 4)
|
||||
maxSizeBookName = 4;
|
||||
else if (maxSizeBookName > MAX_SIZE_BOOK_NAME)
|
||||
maxSizeBookName = MAX_SIZE_BOOK_NAME;
|
||||
else if ((maxSizeBookName % 2))
|
||||
maxSizeBookName++;
|
||||
@@ -276,7 +281,9 @@ private:
|
||||
std::cout << kv.first;
|
||||
std::cout << " ";
|
||||
|
||||
if (loan->bookName.size() > MAX_SIZE_BOOK_NAME)
|
||||
if (loan->bookName.size() == 0)
|
||||
bookName = std::string("No name ");
|
||||
else if (loan->bookName.size() > MAX_SIZE_BOOK_NAME)
|
||||
bookName = std::string(loan->bookName.c_str(), MAX_SIZE_BOOK_NAME);
|
||||
else
|
||||
bookName = loan->bookName;
|
||||
|
||||
@@ -68,7 +68,13 @@ DRMProcessorClientImpl::DRMProcessorClientImpl():
|
||||
strcpy(cookiejar, "/tmp/libgourou_cookie_jar_XXXXXX");
|
||||
#endif
|
||||
|
||||
mkstemp(cookiejar);
|
||||
int fd = mkstemp(cookiejar);
|
||||
if (fd >= 0)
|
||||
close(fd);
|
||||
else
|
||||
{
|
||||
EXCEPTION(gourou::CLIENT_FILE_ERROR, "mkstemp error");
|
||||
}
|
||||
}
|
||||
|
||||
DRMProcessorClientImpl::~DRMProcessorClientImpl()
|
||||
@@ -298,14 +304,15 @@ std::string DRMProcessorClientImpl::sendHTTPRequest(const std::string& URL, cons
|
||||
}
|
||||
|
||||
curl_slist_free_all(list);
|
||||
|
||||
long http_code = 400;
|
||||
curl_easy_getinfo (curl, CURLINFO_RESPONSE_CODE, &http_code);
|
||||
|
||||
curl_easy_cleanup(curl);
|
||||
|
||||
if (res != CURLE_OK)
|
||||
EXCEPTION(gourou::CLIENT_NETWORK_ERROR, "Error " << curl_easy_strerror(res));
|
||||
|
||||
long http_code = 400;
|
||||
curl_easy_getinfo (curl, CURLINFO_RESPONSE_CODE, &http_code);
|
||||
|
||||
if (http_code >= 400)
|
||||
EXCEPTION(gourou::CLIENT_HTTP_ERROR, "HTTP Error code " << http_code);
|
||||
|
||||
@@ -332,11 +339,33 @@ void DRMProcessorClientImpl::padWithPKCS1(unsigned char* out, unsigned int outLe
|
||||
0x00 0x01 0xff * n 0x00 dataIn
|
||||
*/
|
||||
|
||||
memset(out, 0xFF, outLength);
|
||||
memset(out, 0xFF, outLength - inLength - 1);
|
||||
|
||||
out[0] = 0x0;
|
||||
out[1] = 0x1;
|
||||
out[outLength - inLength - 1] = 0x00;
|
||||
|
||||
memcpy(&out[outLength - inLength], in, inLength);
|
||||
}
|
||||
|
||||
void DRMProcessorClientImpl::padWithPKCS1Type2(unsigned char* out, unsigned int outLength,
|
||||
const unsigned char* in, unsigned int inLength)
|
||||
{
|
||||
if (outLength < (inLength + 3))
|
||||
EXCEPTION(gourou::CLIENT_RSA_ERROR, "Not enough space for PKCS1 padding");
|
||||
|
||||
/*
|
||||
PKCS1v5 type 2 Padding is :
|
||||
0x00 0x02 0xXX * n 0x00 dataIn
|
||||
XX is random non zero data
|
||||
*/
|
||||
|
||||
RAND_bytes(&out[2], outLength - inLength - 1);
|
||||
|
||||
out[0] = 0x0;
|
||||
out[1] = 0x2;
|
||||
out[outLength - inLength - 1] = 0x00;
|
||||
|
||||
memcpy(&out[outLength - inLength], in, inLength);
|
||||
}
|
||||
|
||||
@@ -428,33 +457,45 @@ void DRMProcessorClientImpl::RSAPublicEncrypt(const unsigned char* RSAKey, unsig
|
||||
unsigned char* res)
|
||||
{
|
||||
size_t outlen;
|
||||
|
||||
unsigned char* tmp;
|
||||
|
||||
X509 * x509 = d2i_X509(0, &RSAKey, RSAKeyLength);
|
||||
if (!x509)
|
||||
EXCEPTION(gourou::CLIENT_INVALID_CERTIFICATE, "Invalid certificate");
|
||||
|
||||
EVP_PKEY_CTX *ctx;
|
||||
EVP_PKEY * evpKey = X509_get_pubkey(x509);
|
||||
EVP_PKEY * pkey = X509_get_pubkey(x509);
|
||||
|
||||
if (!evpKey)
|
||||
if (!pkey)
|
||||
EXCEPTION(gourou::CLIENT_NO_PUB_KEY, "No public key in certificate");
|
||||
|
||||
ctx = EVP_PKEY_CTX_new(evpKey, NULL);
|
||||
ctx = EVP_PKEY_CTX_new(pkey, NULL);
|
||||
|
||||
if (EVP_PKEY_encrypt_init(ctx) <= 0)
|
||||
EXCEPTION(gourou::CLIENT_RSA_ERROR, ERR_error_string(ERR_get_error(), NULL));
|
||||
|
||||
if (EVP_PKEY_CTX_set_rsa_padding(ctx, RSA_PKCS1_PADDING) <= 0)
|
||||
EXCEPTION(gourou::CLIENT_RSA_ERROR, ERR_error_string(ERR_get_error(), NULL));
|
||||
if (EVP_PKEY_CTX_set_rsa_padding(ctx, RSA_NO_PADDING) <= 0)
|
||||
EXCEPTION(gourou::CLIENT_RSA_ERROR, ERR_error_string(ERR_get_error(), NULL));
|
||||
|
||||
int ret = EVP_PKEY_encrypt(ctx, res, &outlen, data, dataLength);
|
||||
outlen = EVP_PKEY_get_size(pkey);
|
||||
|
||||
tmp = (unsigned char*)malloc(outlen);
|
||||
|
||||
/*
|
||||
PKCS1 functions are no more exported.
|
||||
Some OpenSSL libraries still use type 1
|
||||
*/
|
||||
padWithPKCS1Type2(tmp, outlen, data, dataLength);
|
||||
|
||||
int ret = EVP_PKEY_encrypt(ctx, res, &outlen, tmp, outlen);
|
||||
|
||||
EVP_PKEY_CTX_free(ctx);
|
||||
free(tmp);
|
||||
|
||||
EVP_PKEY_free(pkey);
|
||||
|
||||
if (ret < 0)
|
||||
EXCEPTION(gourou::CLIENT_RSA_ERROR, ERR_error_string(ERR_get_error(), NULL));
|
||||
|
||||
EVP_PKEY_free(evpKey);
|
||||
}
|
||||
|
||||
void* DRMProcessorClientImpl::generateRSAKey(int keyLengthBits)
|
||||
@@ -468,7 +509,6 @@ void* DRMProcessorClientImpl::generateRSAKey(int keyLengthBits)
|
||||
EVP_PKEY_keygen_init(ctx);
|
||||
|
||||
EVP_PKEY_CTX_set_rsa_keygen_bits(ctx, keyLengthBits);
|
||||
EVP_PKEY_CTX_set_rsa_padding(ctx, RSA_PKCS1_PADDING);
|
||||
EVP_PKEY_CTX_set1_rsa_keygen_pubexp(ctx, bn);
|
||||
EVP_PKEY_keygen(ctx, &key);
|
||||
|
||||
|
||||
@@ -130,6 +130,8 @@ private:
|
||||
|
||||
void padWithPKCS1(unsigned char* out, unsigned int outLength,
|
||||
const unsigned char* in, unsigned int inLength);
|
||||
void padWithPKCS1Type2(unsigned char* out, unsigned int outLength,
|
||||
const unsigned char* in, unsigned int inLength);
|
||||
|
||||
#if OPENSSL_VERSION_MAJOR >= 3
|
||||
OSSL_PROVIDER *legacy, *deflt;
|
||||
|
||||
Reference in New Issue
Block a user