Compare commits

..

28 commits

Author SHA1 Message Date
Radon Rosborough
d7bd98e719 Use -fmacro-prefix-map for utils
The __FILE__ macro is used in the EXCEPTION macro to report the file
name alongside error messages. This macro reports the file name
exactly as it is passed to the compiler. For most source files in
libgourou this is a nice relative path such as "src/libgourou.cpp".
However, for EXCEPTION instances in libgourou_common.h, the compiler
flag is "-I$(ROOT)/include", where $(ROOT) is an absolute path passed
from the higher Makefile. This results in an absolute path to the
build directory being hardcoded into the utils shared library and
binaries, and reported in error messages. Besides being less readable
than the more common relative paths, this triggers warnings from
packaging tools that detect inadvertent references to temporary build
directories that end up in compiled binaries and might indicate a bug.

There is a GCC feature -fmacro-prefix-map which allows to perform
substition on the values that are used for __FILE__. Use that option
to strip out the absolute path component, without changing any
functionality.

The feature was added to GCC on 2018-01-18 and released in GCC 8.1.0.

7365279fca
2026-03-14 13:51:40 +01:00
Grégory Soutadé
72cb22ad2a Refresh curl download percent only when updated 2026-02-17 20:07:57 +01:00
Grégory Soutadé
b44b988966 Fix bug in utils with -o and -O options 2026-02-01 08:56:27 +01:00
Grégory Soutadé
76cab18667 Delete all referenced objects deleted by libgourou during PDF DRM removal 2026-01-31 20:31:44 +01:00
Jake Waksbaum
2387dff2cb Add darwin support to Makefile 2026-01-25 10:15:35 +01:00
Jake Waksbaum
a7cfd3ef89 Replace sprintf with snprintf 2026-01-19 15:35:24 -05:00
Jake Waksbaum
772abdd2f9 Remove unused message field 2026-01-19 14:46:49 -05:00
Jake Waksbaum
1605656c73 Exception: fix constructor 2026-01-19 14:45:59 -05:00
Grégory Soutadé
8f0341d0bd Some code formating 2026-01-18 15:44:20 +01:00
Grégory Soutadé
8061681705 Add a warning when ACSM file is expired 2026-01-18 15:22:02 +01:00
Grégory Soutadé
724961566c Update version 2026-01-18 14:35:18 +01:00
Grégory Soutadé
98c511d0ca Fix mkstemp unused result warning 2026-01-18 14:31:52 +01:00
Grégory Soutadé
db3e2179a9 Add PKCS1 Type 2 padding for Signing request 2026-01-18 14:31:52 +01:00
Grégory Soutadé
28aefba6d6 Update README.md 2025-11-17 22:29:35 +01:00
Grégory Soutadé
e0e2bc7430 Update uPDFParser git address 2025-08-25 20:37:52 +02:00
Grégory Soutadé
d3c90f03bb Update README.md 2025-04-21 08:59:51 +02:00
Grégory Soutadé
469d378f9a Update version to 0.8.7 2025-02-16 21:32:51 +01:00
Philipp Pagel
98b531a232 Fix setup.sh
fix path for updfparser
2025-02-11 18:12:36 +01:00
Grégory Soutadé
956bad3068 Update README.md 2024-12-31 13:21:14 +01:00
Ismail Dönmez
d9a920b062 Use $HOME variable if it exists
Signed-off-by: Ismail Dönmez <ismail@i10z.com>
2024-09-22 10:55:54 +02:00
Grégory Soutadé
204500117d Typo fix 2024-09-19 08:41:15 +02:00
Grégory Soutadé
ce2cf4192a Update README.md 2024-08-31 08:27:53 +02:00
Grégory Soutadé
68bf48df27 Update README.md 2024-08-27 21:48:39 +02:00
Grégory Soutadé
81faf1f9be adept_loan_mgt: manage cases were name is empty 2024-04-15 07:37:28 +02:00
nicokosi
f60abf04d8 Fix typos in README.md 2024-04-11 06:05:23 +02:00
Grégory Soutadé
0d77cf55e1 Update version 2024-03-28 21:58:07 +01:00
Grégory Soutadé
86a79cc381 Remove whole EBX objects for PDF when removing DRM 2024-03-28 21:54:23 +01:00
Grégory Soutadé
68bf982b6f Fix use after free in DRMProcessorClientImpl::sendHTTPRequest() 2024-01-24 19:13:22 +01:00
30 changed files with 4172 additions and 3987 deletions

View file

@ -12,6 +12,8 @@ LDFLAGS = -lpugixml
VERSION := $(shell cat include/libgourou.h |grep LIBGOUROU_VERSION|cut -d '"' -f2)
UNAME := $(shell uname -s)
BUILD_STATIC ?= 0
BUILD_SHARED ?= 1
BUILD_UTILS ?= 1
@ -27,8 +29,13 @@ ifneq ($(BUILD_STATIC), 0)
STATIC_UTILS=1
endif
ifneq ($(BUILD_SHARED), 0)
ifeq ($(UNAME), Darwin)
TARGETS += libgourou.dylib
TARGET_LIBRARIES += libgourou.dylib libgourou.dylib.$(VERSION)
else
TARGETS += libgourou.so
TARGET_LIBRARIES += libgourou.so libgourou.so.$(VERSION)
endif
endif
ifneq ($(BUILD_UTILS), 0)
TARGETS += build_utils
@ -82,6 +89,12 @@ libgourou.so.$(VERSION): $(OBJECTS) $(UPDFPARSERLIB)
libgourou.so: libgourou.so.$(VERSION)
ln -f -s $^ $@
libgourou.dylib.$(VERSION): $(OBJECTS) $(UPDFPARSERLIB)
$(CXX) $^ $(LDFLAGS) -o $@ -shared
libgourou.dylib: libgourou.dylib.$(VERSION)
ln -f -s $^ $@
build_utils: $(TARGET_LIBRARIES)
$(MAKE) -C utils ROOT=$(PWD) CXX=$(CXX) AR=$(AR) DEBUG=$(DEBUG) STATIC_UTILS=$(STATIC_UTILS) DESTDIR=$(DESTDIR) PREFIX=$(PREFIX)

View file

@ -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_

View file

@ -30,7 +30,7 @@
namespace macaron {
class Base64 {
class Base64 {
public:
static std::string Encode(const std::string data) {
@ -126,7 +126,7 @@ class Base64 {
return "";
}
};
};
}

View file

@ -37,7 +37,7 @@
#define ACS_SERVER "http://adeactivate.adobe.com/adept"
#endif
#define LIBGOUROU_VERSION "0.8.4"
#define LIBGOUROU_VERSION "0.8.8"
namespace gourou
{

View file

@ -44,7 +44,7 @@ namespace gourou
* Some common utilities
*/
#define ADOBE_ADEPT_NS "http://ns.adobe.com/adept"
#define ADOBE_ADEPT_NS "http://ns.adobe.com/adept"
static const int SHA1_LEN = 20;
static const int RSA_KEY_SIZE = 128;
@ -135,15 +135,15 @@ namespace gourou
DRM_INVALID_USER
};
#ifndef _NOEXCEPT
#if __STDC_VERSION__ >= 201112L
# define _NOEXCEPT noexcept
# define _NOEXCEPT_(x) noexcept(x)
#else
# define _NOEXCEPT throw()
# define _NOEXCEPT_(x)
#endif
#endif /* !_NOEXCEPT */
#ifndef _NOEXCEPT
#if __STDC_VERSION__ >= 201112L
# define _NOEXCEPT noexcept
# define _NOEXCEPT_(x) noexcept(x)
#else
# define _NOEXCEPT throw()
# define _NOEXCEPT_(x)
#endif
#endif /* !_NOEXCEPT */
/**
* Generic exception class
@ -165,8 +165,8 @@ namespace gourou
Exception(const Exception& other)
{
this->code = other.code;
this->line = line;
this->file = file;
this->line = other.line;
this->file = other.file;
this->fullmessage = strdup(other.fullmessage);
}
@ -181,7 +181,7 @@ namespace gourou
private:
int code, line;
const char* message, *file;
const char* file;
char* fullmessage;
};

View file

@ -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

View file

@ -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

View file

@ -202,7 +202,7 @@ namespace gourou
char* tmp = new char[_length*2+1];
for(int i=0; i<(int)_length; i++)
sprintf(&tmp[i*2], "%02x", _data[i]);
snprintf(&tmp[i*2], (_length-i)*2+1, "%02x", _data[i]);
tmp[_length*2] = 0;

View file

@ -94,12 +94,12 @@ namespace gourou
std::string FulfillmentItem::getMetadata(std::string name)
{
// https://stackoverflow.com/questions/313970/how-to-convert-an-instance-of-stdstring-to-lower-case
#if __STDC_VERSION__ >= 201112L
#if __STDC_VERSION__ >= 201112L
std::transform(name.begin(), name.end(), name.begin(),
[](unsigned char c){ return std::tolower(c); });
#else
#else
std::transform(name.begin(), name.end(), name.begin(), tolower);
#endif
#endif
name = std::string("dc:") + name;
pugi::xpath_node path = metadatas.select_node(name.c_str());

View file

@ -21,6 +21,7 @@
#include <sys/time.h>
#include <time.h>
#include <vector>
#include <ctime>
#include <uPDFParser.h>
@ -70,6 +71,15 @@ namespace gourou
if (user) delete user;
}
// function to parse a date or time string.
// https://www.geeksforgeeks.org/cpp/date-and-time-parsing-in-cpp/
static time_t parseDateTime(const char* datetimeString, const char* format)
{
struct tm tmStruct;
strptime(datetimeString, format, &tmStruct);
return mktime(&tmStruct);
}
DRMProcessor* DRMProcessor::createDRMProcessor(DRMProcessorClient* client, bool randomSerial, std::string dirName,
const std::string& hobbes, const std::string& ACSServer)
{
@ -508,6 +518,16 @@ namespace gourou
GOUROU_LOG(INFO, "Fulfill " << ACSMFile);
std::string expiration = extractTextElem(rootNode, "expiration", false);
if (expiration != "")
{
time_t expirationTime = parseDateTime(expiration.c_str(), "%Y-%m-%dT%H:%M:%S");
if (time(NULL) > expirationTime)
GOUROU_LOG(WARN, "WARNING: ACSM file expired (" << expiration << "), It may not work.");
}
// Build req file
pugi::xml_document fulfillReq;
@ -859,6 +879,12 @@ namespace gourou
std::string DRMProcessor::getDefaultAdeptDir(void)
{
#ifndef DEFAULT_ADEPT_DIR
const char* home = getenv("HOME");
if (home)
return home + std::string("/.config/adept/");
else
{
const char* user = getenv("USER");
if (user && user[0])
@ -867,6 +893,7 @@ namespace gourou
}
else
return LOCAL_ADEPT_DIR;
}
#else
return DEFAULT_ADEPT_DIR "/";
#endif
@ -1315,8 +1342,9 @@ namespace gourou
uPDFParser::Integer* ebxVersion;
std::vector<uPDFParser::Object*> objects = parser.objects();
std::vector<uPDFParser::Object*>::iterator it;
std::vector<uPDFParser::Object*>::iterator it, ebxIt;
std::vector<uPDFParser::Object*>::reverse_iterator rIt;
std::vector<uPDFParser::Object*> ebxObjects;
unsigned char decryptedKey[16];
int ebxId;
@ -1375,7 +1403,7 @@ namespace gourou
if (object->objectId() == ebxId)
{
// object->deleteKey("Filter");
ebxObjects.push_back(object);
continue;
}
@ -1485,6 +1513,33 @@ namespace gourou
}
}
/* Delete objects that reference EBX objects, except in trailer */
for(it = objects.begin(); it != objects.end(); it++)
{
uPDFParser::Object* object = *it;
if (object->hasKey("Encrypt") && (*object)["Encrypt"]->type() == uPDFParser::DataType::REFERENCE)
{
uPDFParser::Reference* encrypt = (uPDFParser::Reference*)(*object)["Encrypt"];
/* Delete EBX objects */
for(ebxIt = ebxObjects.begin(); ebxIt != ebxObjects.end(); ebxIt++)
{
if (encrypt->value() == (*ebxIt)->objectId())
{
GOUROU_LOG(ERROR, "Delete stream id " << object->objectId());
parser.removeObject(object);
break;
}
}
}
}
/* Delete EBX objects */
for(it = ebxObjects.begin(); it != ebxObjects.end(); it++)
parser.removeObject(*it);
uPDFParser::Object& trailer = parser.getTrailer();
trailer.deleteKey("Encrypt");

View file

@ -6,9 +6,10 @@ TARGETS=$(TARGET_BINARIES) launcher
MAN_PAGES=acsmdownloader adept_activate adept_remove adept_loan_mgt
CXXFLAGS=-Wall -fPIC -I$(ROOT)/include
CXXFLAGS=-Wall -fPIC -I$(ROOT)/include -fmacro-prefix-map=$(ROOT)/= -fdata-sections -ffunction-sections
STATIC_DEP=
# LDFLAGS += -Wl,--gc-sections
LDFLAGS += -L$(ROOT) -lcrypto -lzip -lz -lcurl -lpugixml
ifneq ($(STATIC_UTILS),)
@ -31,7 +32,7 @@ COMMON_LIB = utils.a
all: $(TARGETS)
${COMMON_LIB}: $(COMMON_DEPS)
$(CXX) $(CXXFLAGS) $(COMMON_DEPS) $(LDFLAGS) -c
$(CXX) $(CXXFLAGS) $(COMMON_DEPS) -c
$(AR) crs $@ $(COMMON_OBJECTS)
%: %.cpp $(COMMON_LIB) $(STATIC_DEP)

View file

@ -64,19 +64,18 @@ public:
if (exportPrivateKey)
{
std::string filename;
if (!outputFile)
filename = std::string("Adobe_PrivateLicenseKey--") + user->getUsername() + ".der";
else
if (outputFile)
filename = outputFile;
else
{
filename = std::string("Adobe_PrivateLicenseKey--") + user->getUsername() + ".der";
if (outputDir)
{
if (!fileExists(outputDir))
mkpath(outputDir);
filename = std::string(outputDir) + "/" + filename;
}
createPath(filename.c_str());
processor.exportPrivateLicenseKey(filename);
std::cout << "Private license key exported to " << filename << std::endl;
@ -86,7 +85,9 @@ public:
gourou::FulfillmentItem* item = processor.fulfill(acsmFile, notify);
std::string filename;
if (!outputFile)
if (outputFile)
filename = outputFile;
else
{
filename = item->getMetadata("title");
if (filename == "")
@ -96,18 +97,13 @@ public:
// Remove invalid characters
std::replace(filename.begin(), filename.end(), '/', '_');
}
}
else
filename = outputFile;
if (outputDir)
{
if (!fileExists(outputDir))
mkpath(outputDir);
filename = std::string(outputDir) + "/" + filename;
}
createPath(filename.c_str());
gourou::DRMProcessor::ITEM_TYPE type = processor.download(item, filename, resume);
if (!outputFile)
@ -186,8 +182,8 @@ static void usage(const char* cmd)
std::cout << basename((char*)cmd) << " download EPUB file from ACSM request file" << std::endl << std::endl;
std::cout << "Usage: " << basename((char*)cmd) << " [OPTIONS] file.acsm" << std::endl << std::endl;
std::cout << "Global Options:" << 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 <title.(epub|pdf|der)>)" << std::endl;
std::cout << " " << "-O|--output-dir" << "\t" << "Optional output directory were to put result (default ./) (not compatible with -o)" << std::endl;
std::cout << " " << "-o|--output-file" << "\t" << "Optional output filename (default <title.(epub|pdf|der)>) (not compatible with -O)" << std::endl;
std::cout << " " << "-f|--acsm-file" << "\t" << "Backward compatibility: 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 << " " << "-r|--resume" << "\t\t" << "Try to resume download (in case of previous failure)" << std::endl;
@ -309,6 +305,12 @@ int main(int argc, char** argv)
return -1;
}
if (outputDir && outputFile)
{
std::cout << "Error : you cannot use both -o and -O" << std::endl;
return -1;
}
ACSMDownloader downloader;
int i;
@ -339,7 +341,7 @@ int main(int argc, char** argv)
}
else
{
if (!fileExists(acsmFile))
if (!pathExists(acsmFile))
{
std::cout << "Error : " << acsmFile << " doesn't exists" << std::endl;
ret = -1;

View file

@ -240,7 +240,7 @@ int main(int argc, char** argv)
if (_outputDir[0] == '.' || _outputDir[0] != '/')
{
// realpath doesn't works if file/dir doesn't exists
if (fileExists(_outputDir))
if (pathExists(_outputDir))
outputDir = strdup(realpath(_outputDir, 0));
else
outputDir = strdup(abspath(_outputDir));
@ -250,7 +250,7 @@ int main(int argc, char** argv)
}
std::string pass;
if (fileExists(outputDir))
if (pathExists(outputDir))
{
int key;

View file

@ -109,7 +109,7 @@ private:
std::string loanDir = std::string(adeptDir) + std::string("/") + LOANS_DIR;
if (!fileExists(loanDir.c_str()))
if (!pathExists(loanDir.c_str()))
return;
dp = opendir (loanDir.c_str());
@ -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;

View file

@ -81,16 +81,13 @@ public:
gourou::DRMProcessor processor(&client, deviceFile, activationFile, devicekeyFile);
std::string filename;
if (!outputFile)
filename = std::string(inputFile);
else
if (outputFile)
filename = outputFile;
else
{
filename = std::string(inputFile);
if (outputDir)
{
if (!fileExists(outputDir))
mkpath(outputDir);
filename = std::string(outputDir) + "/" + filename;
}
@ -103,6 +100,8 @@ public:
EXCEPTION(gourou::DRM_FORMAT_NOT_SUPPORTED, "Unsupported file format of " << filename);
}
createPath(filename.c_str());
if (inputFile != filename)
{
unlink(filename.c_str());
@ -147,8 +146,8 @@ static void usage(const char* cmd)
std::cout << "Usage: " << basename((char*)cmd) << " [OPTIONS] file(.epub|pdf)" << std::endl << std::endl;
std::cout << "Global Options:" << 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 << " " << "-O|--output-dir" << "\t" << "Optional output directory were to put result (default ./) (not compatible with -o)" << std::endl;
std::cout << " " << "-o|--output-file" << "\t" << "Optional output filename (default inplace DRM removal>) (not compatible with -O)" << std::endl;
std::cout << " " << "-f|--input-file" << "\t" << "Backward compatibility: 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;
@ -259,6 +258,12 @@ int main(int argc, char** argv)
return -1;
}
if (outputDir && outputFile)
{
std::cout << "Error : you cannot use both -o and -O" << std::endl;
return -1;
}
ADEPTRemove remover;
int i;

View file

@ -49,17 +49,27 @@
#include <libgourou_common.h>
#include "drmprocessorclientimpl.h"
static int error_cb(const char *str, size_t len, void *u)
{
std::cout << str << std::endl;
return 0;
}
DRMProcessorClientImpl::DRMProcessorClientImpl():
legacy(0), deflt(0)
{
#if OPENSSL_VERSION_MAJOR >= 3
legacy = OSSL_PROVIDER_load(NULL, "legacy");
if (!legacy)
{
ERR_print_errors_cb(error_cb, NULL);
EXCEPTION(gourou::CLIENT_OSSL_ERROR, "Error, OpenSSL legacy provider not available");
}
deflt = OSSL_PROVIDER_load(NULL, "default");
if (!deflt)
EXCEPTION(gourou::CLIENT_OSSL_ERROR, "Error, OpenSSL default provider not available");
OSSL_PROVIDER_load(NULL, "base");
#endif
#ifdef WIN32
@ -68,7 +78,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()
@ -131,6 +147,7 @@ void DRMProcessorClientImpl::randBytes(unsigned char* bytesOut, unsigned int len
#define HTTP_REQ_MAX_RETRY 5
#define DISPLAY_THRESHOLD 10*1024 // Threshold to display download progression
static unsigned downloadedBytes;
static int lastPercent = -1;
static int downloadProgress(void *clientp, curl_off_t dltotal, curl_off_t dlnow,
curl_off_t ultotal, curl_off_t ulnow)
@ -142,7 +159,11 @@ static int downloadProgress(void *clientp, curl_off_t dltotal, curl_off_t dlnow,
if (dltotal)
percent = (dlnow * 100) / dltotal;
if (lastPercent != percent)
{
std::cout << "\rDownload " << percent << "%" << std::flush;
lastPercent = percent;
}
}
return 0;
@ -263,6 +284,7 @@ std::string DRMProcessorClientImpl::sendHTTPRequest(const std::string& URL, cons
curl_easy_setopt(curl, CURLOPT_XFERINFOFUNCTION, downloadProgress);
curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 0);
lastPercent = -1;
for (int i=0; i<HTTP_REQ_MAX_RETRY; i++)
{
@ -298,14 +320,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 +355,39 @@ 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);
for(unsigned int i=2; i<outLength - inLength - 1; i++)
{
while (out[i] == 0)
RAND_bytes(&out[i], 1);
}
out[0] = 0x0;
out[1] = 0x2;
out[outLength - inLength - 1] = 0x00;
memcpy(&out[outLength - inLength], in, inLength);
}
@ -428,33 +479,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)
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 +531,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);

View file

@ -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;

View file

@ -23,7 +23,7 @@ int main(int argc, char** argv)
fullPath = std::string(mountPoint) + util;
if (std::string(util) == "launcher" || !fileExists(fullPath.c_str()))
if (std::string(util) == "launcher" || !pathExists(fullPath.c_str()))
fullPath = std::string(mountPoint) + DEFAULT_UTIL;
free(argv0);

View file

@ -33,6 +33,7 @@
#include <stdio.h>
#include <fcntl.h>
#include <limits.h>
#include <libgen.h>
#include <iostream>
@ -51,10 +52,10 @@ void version(void)
std::cout << "Current libgourou version : " << gourou::DRMProcessor::VERSION << std::endl ;
}
bool fileExists(const char* filename)
bool pathExists(const char* path)
{
struct stat _stat;
int ret = stat(filename, &_stat);
int ret = stat(path, &_stat);
return (ret == 0);
}
@ -67,15 +68,15 @@ const char* findFile(const char* filename, bool inDefaultDirs)
if (adeptDir && adeptDir[0])
{
path = adeptDir + std::string("/") + filename;
if (fileExists(path.c_str()))
if (pathExists(path.c_str()))
return strdup(path.c_str());
}
path = gourou::DRMProcessor::getDefaultAdeptDir() + filename;
if (fileExists(path.c_str()))
if (pathExists(path.c_str()))
return strdup(path.c_str());
if (fileExists(filename))
if (pathExists(filename))
return strdup(filename);
if (!inDefaultDirs) return 0;
@ -83,7 +84,7 @@ const char* findFile(const char* filename, bool inDefaultDirs)
for (int i=0; i<(int)ARRAY_SIZE(defaultDirs); i++)
{
path = std::string(defaultDirs[i]) + filename;
if (fileExists(path.c_str()))
if (pathExists(path.c_str()))
return strdup(path.c_str());
}
@ -152,3 +153,12 @@ void fileCopy(const char* in, const char* out)
close (fdIn);
close (fdOut);
}
void createPath(const char* filename)
{
char* basepath = strdup(filename);
char* outputDir = dirname(basepath);
if (outputDir && !pathExists(outputDir))
mkpath(outputDir);
free(basepath);
}

View file

@ -50,9 +50,9 @@ void version(void);
const char* findFile(const char* filename, bool inDefaultDirs=true);
/**
* @brief Does the file (or directory exists)
* @brief Does the file (or directory) exists
*/
bool fileExists(const char* filename);
bool pathExists(const char* path);
/**
* @brief Recursively created dir
@ -64,4 +64,9 @@ void mkpath(const char *dir);
*/
void fileCopy(const char* in, const char* out);
/**
* @brief Create intermediate directories if it does not exists
*/
void createPath(const char* filename);
#endif