Compare commits

...

80 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
Grégory Soutadé
ef8c2644ca Update version 2024-01-16 11:09:31 +01:00
Grégory Soutadé
e05639c09d Support HTTP error codes != 200 (exception) and cookies in drmprocessorclientimpl 2024-01-06 09:25:11 +01:00
Grégory Soutadé
69865e005b Register operatorURL only when certificate from operator is fetched 2024-01-06 09:23:03 +01:00
Grégory Soutadé
fd38e84da6 Fix for Android for adept_loan_mgt.cpp (strptime format) 2024-01-06 09:22:11 +01:00
Grégory Soutadé
92a67312bd Update README.md 2023-09-06 21:21:43 +02:00
Grégory Soutadé
29d298b373 Update libgourou version 2023-09-06 21:17:06 +02:00
Grégory Soutadé
40dcb7a041 Add --no-notify option to utils 2023-09-06 21:17:06 +02:00
Grégory Soutadé
e0bb1bd4f8 Add notify server feature 2023-09-06 21:17:06 +02:00
Grégory Soutadé
9388d82138 Loan ID must be Fullfilment ID, not <loan> value(s) 2023-09-04 18:28:47 +02:00
Grégory Soutadé
c19279397f Update README.md 2023-08-09 20:58:06 +02:00
Grégory Soutadé
bb5349d710 Update version 2023-08-08 20:14:23 +02:00
Grégory Soutadé
9a75213b49 Fix misuse of DESTDIR and PREFIX in Makefile 2023-08-08 20:13:03 +02:00
Grégory Soutadé
e06d20a392 DRM removal: Forgot to decrypt HexaString objects 2023-08-05 14:43:48 +02:00
Grégory Soutadé
a0f6324999 Try to fix GCC 13 compilation errors 2023-05-03 21:15:31 +02:00
Grégory Soutadé
c259cbd5a3 Add missing libgen.h in utils for basename() call 2023-03-28 20:32:05 +02:00
Grégory Soutadé
46afe771c7 Remove old pugixml include in utils Makefile 2023-01-15 09:51:00 +01:00
Grégory Soutadé
cad2189fc2 Typo fix 2023-01-14 18:21:06 +01:00
Grégory Soutadé
50bc16079f Fix static build 2023-01-14 12:56:06 +01:00
Grégory Soutadé
1213b34250 Fix ADEPT path creation within adept_activate 2023-01-11 19:57:47 +01:00
Grégory Soutadé
a66dcb959c Work on Makefile 2023-01-11 19:57:47 +01:00
Grégory Soutadé
84b01a5de3 Use system version of pugixml, not a checkouted one 2023-01-08 21:15:33 +01:00
Grégory Soutadé
be78d24236 Add man pages for utils 2023-01-08 21:05:22 +01:00
Grégory Soutadé
8aec5be244 Update Makefile to be more GNU/Linux style 2023-01-08 21:05:04 +01:00
Grégory Soutadé
3a0ab4b438 Update --help for utils and README.md 2023-01-08 21:04:23 +01:00
Grégory Soutadé
891ed05926 We can now specify directly file path for acsmdownloader and adept_remove (-f stille keeped for compatibility) 2023-01-08 20:58:41 +01:00
Grégory Soutadé
fc839e671a Manage ACSM files that contains server internal error 2023-01-06 21:17:57 +01:00
Grégory Soutadé
ab5afa5003 Add new default ADEPT directories : /home/<user>/.config/adept and $ADEPT_DIR environment variable 2023-01-05 21:29:55 +01:00
Grégory Soutadé
937c27fc23 Fix some warnings 2023-01-05 21:27:50 +01:00
Grégory Soutadé
34216d1b6e Check for target user before trying to decrypt a file 2023-01-05 21:26:05 +01:00
Berwyn
ffd2004cbb Correct 'any book loaned' message 2023-01-04 20:23:57 +01:00
Grégory Soutadé
c41dd46ca7 Check for potential write error (or not buffer fully consumed) 2022-12-23 17:51:51 +01:00
Grégory Soutadé
e4bd73c03d Add global option -D to utils, allowing to specify .adept directory instead of every single files. WARNING : -D has been changed by -d in adept_loan_mgt ! 2022-12-21 21:23:42 +01:00
Grégory Soutadé
f65e8cd9eb Check for target user before trying to decrypt a file 2022-12-21 21:06:03 +01:00
Grégory Soutadé
24bae89095 Factorize decryptADEPTKey() for ePub and PDF 2022-12-21 20:58:49 +01:00
Grégory Soutadé
afab1c0012 Fix over encrypted RSA key decryption algorithm 2022-12-21 20:15:11 +01:00
Grégory Soutadé
7878f91cdd Add support for MacOS and old compilers (not supporting C++11). Main patch is from Samuel Marks. 2022-11-21 17:56:29 +01:00
Grégory Soutadé
6e3958f09e Compute over encrypted key also for PDF files 2022-09-04 09:25:06 +02:00
Grégory Soutadé
2dbd4cc343 Update version 2022-08-29 15:19:23 +02:00
Grégory Soutadé
e28dc39a68 Make DRMProcessorClient API more consistent 2022-08-29 15:19:23 +02:00
Grégory Soutadé
7b8c7acbad Compute first pass for encryptedKey if keyType attribute is set 2022-08-29 11:57:33 +02:00
Grégory Soutadé
56b3231f92 Add dumpBuffer() in libgourou_common 2022-08-29 11:56:47 +02:00
Grégory Soutadé
7084fb7025 Add fromHex() static function to ByteArray 2022-08-29 11:56:47 +02:00
Grégory Soutadé
7f5b787cb9 Add launcher util for AppImage 2022-08-29 11:56:47 +02:00
Grégory Soutadé
086e9b0610 Update version 2022-08-29 11:56:47 +02:00
Grégory Soutadé
600535d52c Utils: Migration to OpenSSL3 2022-08-29 11:56:47 +02:00
Grégory Soutadé
57c3a58994 Add STATIC_NONCE option for build (developper mode) 2022-08-10 21:34:30 +02:00
Grégory Soutadé
210b265693 Forward DEBUG flag in Makefile 2022-08-10 21:34:30 +02:00
Grégory Soutadé
33bb983283 Change log levels names to avoid collisions 2022-08-10 21:34:30 +02:00
Grégory Soutadé
3c73b8ccb3 Update .gitignore
Patch from Nguyễn Gia Phong
2022-07-03 09:22:06 +02:00
Grégory Soutadé
4acf401031 Don't clone base64 repository at first build, use a static version of Base64.h (not modified since many years)
Patch from Nguyễn Gia Phong
2022-07-03 09:20:05 +02:00
Grégory Soutadé
7666d2a241 Update README 2022-06-12 15:03:14 +02:00
Grégory Soutadé
201ec69b11 Add scripts/update_lib.sh 2022-06-12 15:03:14 +02:00
36 changed files with 5003 additions and 3774 deletions

1
.gitignore vendored
View file

@ -1,5 +1,6 @@
obj
lib
*.o
*.a
*.so
*~

View file

@ -1,22 +1,41 @@
PREFIX ?= /usr/local
LIBDIR ?= /lib
INCDIR ?= /include
AR ?= $(CROSS)ar
CXX ?= $(CROSS)g++
UPDFPARSERLIB = ./lib/updfparser/libupdfparser.a
CXXFLAGS=-Wall -fPIC -I./include -I./lib -I./lib/pugixml/src/ -I./lib/updfparser/include
LDFLAGS = $(UPDFPARSERLIB)
CXXFLAGS += -Wall -fPIC -I./include -I./usr/include/pugixml -I./lib/updfparser/include
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
TARGETS =
TARGET_LIBRARIES =
ifneq ($(STATIC_UTILS),)
BUILD_STATIC=1
endif
ifneq ($(BUILD_STATIC), 0)
TARGETS += libgourou.a
TARGET_LIBRARIES += libgourou.a
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
@ -24,48 +43,83 @@ endif
ifneq ($(DEBUG),)
CXXFLAGS += -ggdb -O0
CXXFLAGS += -ggdb -O0 -DDEBUG
else
CXXFLAGS += -O2
endif
ifneq ($(STATIC_NONCE),)
CXXFLAGS += -DSTATIC_NONCE=1
endif
SRCDIR := src
INCDIR := inc
BUILDDIR := obj
TARGETDIR := bin
SRCEXT := cpp
OBJEXT := o
SOURCES = src/libgourou.cpp src/user.cpp src/device.cpp src/fulfillment_item.cpp src/loan_token.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
OBJECTS := $(patsubst $(SRCDIR)/%,$(BUILDDIR)/%,$(SOURCES:.$(SRCEXT)=.$(OBJEXT)))
all: lib obj $(TARGETS)
all: version lib obj $(TARGETS)
version:
@echo "Building libgourou $(VERSION)"
lib:
mkdir lib
./scripts/setup.sh
update_lib:
./scripts/update_lib.sh
obj:
mkdir obj
$(BUILDDIR)/%.$(OBJEXT): $(SRCDIR)/%.$(SRCEXT)
$(CXX) $(CXXFLAGS) -c $^ -o $@
libgourou: libgourou.a libgourou.so
libgourou: $(TARGET_LIBRARIES)
libgourou.a: $(OBJECTS) $(UPDFPARSERLIB)
$(AR) crs $@ obj/*.o $(UPDFPARSERLIB)
$(AR) rcs --thin $@ $^
libgourou.so: $(OBJECTS) $(UPDFPARSERLIB)
$(CXX) obj/*.o $(LDFLAGS) -o $@ -shared
libgourou.so.$(VERSION): $(OBJECTS) $(UPDFPARSERLIB)
$(CXX) $^ -Wl,-soname,$@ $(LDFLAGS) -o $@ -shared
build_utils:
make -C utils ROOT=$(PWD) CXX=$(CXX) AR=$(AR) DEBUG=$(DEBUG) STATIC_UTILS=$(STATIC_UTILS) OPENSSL3=$(OPENSSL3)
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)
install: $(TARGET_LIBRARIES)
install -d $(DESTDIR)$(PREFIX)$(LIBDIR)
# Use cp to preserver symlinks
cp --no-dereference $(TARGET_LIBRARIES) $(DESTDIR)$(PREFIX)$(LIBDIR)
$(MAKE) -C utils ROOT=$(PWD) CXX=$(CXX) AR=$(AR) DEBUG=$(DEBUG) STATIC_UTILS=$(STATIC_UTILS) DESTDIR=$(DESTDIR) PREFIX=$(PREFIX) install
uninstall:
cd $(DESTDIR)$(PREFIX)/$(LIBDIR)
rm -f $(TARGET_LIBRARIES) libgourou.so.$(VERSION)
cd -
install_headers:
install -d $(DESTDIR)$(PREFIX)/$(INCDIR)/libgourou
cp --no-dereference include/*.h $(DESTDIR)$(PREFIX)/$(INCDIR)/libgourou
uninstall_headers:
rm -rf $(DESTDIR)$(PREFIX)/$(INCDIR)/libgourou
clean:
rm -rf libgourou.a libgourou.so obj
make -C utils clean
rm -rf libgourou.a libgourou.so libgourou.so.$(VERSION)* obj
$(MAKE) -C utils clean
ultraclean: clean
rm -rf lib
make -C utils ultraclean
$(MAKE) -C utils ultraclean

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.
A reference implementation using Qt, OpenSSL and libzip is provided (in _utils_ directory).
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()_
@ -23,11 +23,11 @@ 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.
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
@ -35,13 +35,28 @@ Dependencies
For libgourou:
* None
_externals_ :
* libpugixml
_internals_:
* uPDFParser
For utils:
* libcurl
* OpenSSL
* openssl
* libzip
* libpugixml
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
Compilation
@ -49,7 +64,7 @@ Compilation
Use _make_ command
make [CROSS=XXX] [DEBUG=(0*|1)] [STATIC_UTILS=(0*|1)] [BUILD_UTILS=(0|1*)] [BUILD_STATIC=(0*|1)] [BUILD_SHARED=(0|1*)]
make [CROSS=XXX] [DEBUG=(0*|1)] [STATIC_UTILS=(0*|1)] [BUILD_UTILS=(0|1*)] [BUILD_STATIC=(0*|1)] [BUILD_SHARED=(0|1*)] [all*|clean|ultraclean|build_utils|install|uninstall]
CROSS can define a cross compiler prefix (ie arm-linux-gnueabihf-)
@ -63,47 +78,60 @@ BUILD_STATIC build libgourou.a if 1, nothing if 0, can be combined with BUILD_SH
BUILD_SHARED build libgourou.so if 1, nothing if 0, can be combined with BUILD_STATIC
other variables are DESTDIR and PREFIX to handle destination install directory
* Default value
Utils
-----
You can import configuration from your eReader or create a new one with _utils/adept\_activate_ :
First, add libgourou.so to your LD_LIBRARY_PATH
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$PWD
You can optionaly specify your .adept directory
export ADEPT_DIR=/home/XXX
Then, use utils as following:
You can import configuration from your eReader or create a new one with _utils/adept\_activate_:
./utils/adept_activate -u <AdobeID USERNAME>
Then a _./.adept_ directory is created with all configuration file
Then a _/home/<user>/.config/adept_ directory is created with all configuration file
To download an ePub/PDF:
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$PWD
./utils/acsmdownloader -f <ACSM_FILE>
./utils/acsmdownloader <ACSM_FILE>
To export your private key (for DeDRM software):
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$PWD
./utils/acsmdownloader --export-private-key [-o adobekey_1.der]
To remove ADEPT DRM:
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$PWD
./utils/adept_remove -f <encryptedFile>
./utils/adept_remove <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
Binary packages
---------------
Compiled version (and AppImage) of libgourou and utils are available in [Release page](https://forge.soutade.fr/soutade/libgourou/releases)
Docker
------
@ -116,7 +144,6 @@ Copyright
Grégory Soutadé
License
-------
@ -125,9 +152,24 @@ libgourou : LGPL v3 or later
utils : BSD
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_

133
include/Base64.h Normal file
View file

@ -0,0 +1,133 @@
#ifndef _MACARON_BASE64_H_
#define _MACARON_BASE64_H_
/**
* The MIT License (MIT)
* Copyright (c) 2016 tomykaira
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#include <string>
#include <stdint.h>
namespace macaron {
class Base64 {
public:
static std::string Encode(const std::string data) {
static
#if __STDC_VERSION__ >= 201112L
constexpr
#endif /* __STDC_VERSION__ >= 201112L */
char sEncodingTable[] = {
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H',
'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X',
'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f',
'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n',
'o', 'p', 'q', 'r', 's', 't', 'u', 'v',
'w', 'x', 'y', 'z', '0', '1', '2', '3',
'4', '5', '6', '7', '8', '9', '+', '/'
};
size_t in_len = data.size();
size_t out_len = 4 * ((in_len + 2) / 3);
std::string ret(out_len, '\0');
size_t i;
char *p = const_cast<char*>(ret.c_str());
for (i = 0; i < in_len - 2; i += 3) {
*p++ = sEncodingTable[(data[i] >> 2) & 0x3F];
*p++ = sEncodingTable[((data[i] & 0x3) << 4) | ((int) (data[i + 1] & 0xF0) >> 4)];
*p++ = sEncodingTable[((data[i + 1] & 0xF) << 2) | ((int) (data[i + 2] & 0xC0) >> 6)];
*p++ = sEncodingTable[data[i + 2] & 0x3F];
}
if (i < in_len) {
*p++ = sEncodingTable[(data[i] >> 2) & 0x3F];
if (i == (in_len - 1)) {
*p++ = sEncodingTable[((data[i] & 0x3) << 4)];
*p++ = '=';
}
else {
*p++ = sEncodingTable[((data[i] & 0x3) << 4) | ((int) (data[i + 1] & 0xF0) >> 4)];
*p++ = sEncodingTable[((data[i + 1] & 0xF) << 2)];
}
*p++ = '=';
}
return ret;
}
static std::string Decode(const std::string& input, std::string& out) {
static
#if __STDC_VERSION__ >= 201112L
constexpr
#endif /* __STDC_VERSION__ >= 201112L */
unsigned char kDecodingTable[] = {
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 62, 64, 64, 64, 63,
52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 64, 64, 64, 64, 64, 64,
64, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 64, 64, 64, 64, 64,
64, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 64, 64, 64, 64, 64,
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64
};
size_t in_len = input.size();
if (in_len % 4 != 0) return "Input data size is not a multiple of 4";
size_t out_len = in_len / 4 * 3;
if (input[in_len - 1] == '=') out_len--;
if (input[in_len - 2] == '=') out_len--;
out.resize(out_len);
for (size_t i = 0, j = 0; i < in_len;) {
uint32_t a = input[i] == '=' ? 0 & i++ : kDecodingTable[static_cast<int>(input[i++])];
uint32_t b = input[i] == '=' ? 0 & i++ : kDecodingTable[static_cast<int>(input[i++])];
uint32_t c = input[i] == '=' ? 0 & i++ : kDecodingTable[static_cast<int>(input[i++])];
uint32_t d = input[i] == '=' ? 0 & i++ : kDecodingTable[static_cast<int>(input[i++])];
uint32_t triple = (a << 3 * 6) + (b << 2 * 6) + (c << 1 * 6) + (d << 0 * 6);
if (j < out_len) out[j++] = (triple >> 2 * 8) & 0xFF;
if (j < out_len) out[j++] = (triple >> 1 * 8) & 0xFF;
if (j < out_len) out[j++] = (triple >> 0 * 8) & 0xFF;
}
return "";
}
};
}
#endif /* _MACARON_BASE64_H_ */

View file

@ -104,6 +104,13 @@ namespace gourou
*/
std::string toBase64();
/**
* @brief Convert hex string into bytes
*
* @param str Hex string
*/
static ByteArray fromHex(const std::string& str);
/**
* @brief Return a string with human readable hex encoded internal data
*/
@ -130,7 +137,7 @@ namespace gourou
void append(const std::string& str);
/**
* @brief Get internal data. Must bot be freed
* @brief Get internal data. Must not be freed
*/
unsigned char* data() {return _data;}

View file

@ -47,20 +47,16 @@ namespace gourou
* @param handler Digest handler
* @param data Data to digest
* @param length Length of data
*
* @return OK/KO
*/
virtual int digestUpdate(void* handler, unsigned char* data, unsigned int length) = 0;
virtual void digestUpdate(void* handler, unsigned char* data, unsigned int length) = 0;
/**
* @brief Finalize digest with remained buffered data and destroy handler
*
* @param handler Digest handler
* @param digestOut Digest result (buffer must be pre allocated with right size)
*
* @return OK/KO
*/
virtual int digestFinalize(void* handler, unsigned char* digestOut) = 0;
virtual void digestFinalize(void* handler, unsigned char* digestOut) = 0;
/**
* @brief Global digest function
@ -69,10 +65,8 @@ namespace gourou
* @param data Data to digest
* @param length Length of data
* @param digestOut Digest result (buffer must be pre allocated with right size)
*
* @return OK/KO
*/
virtual int digest(const std::string& digestName, unsigned char* data, unsigned int length, unsigned char* digestOut) = 0;
virtual void digest(const std::string& digestName, unsigned char* data, unsigned int length, unsigned char* digestOut) = 0;
};
class RandomInterface
@ -111,6 +105,7 @@ namespace gourou
public:
enum RSA_KEY_TYPE {
RSA_KEY_PKCS12 = 0,
RSA_KEY_PKCS8,
RSA_KEY_X509
};
@ -238,7 +233,7 @@ namespace gourou
* @param dataOut Encrypted data
* @param dataOutLength Length of encrypted data
*/
virtual void Encrypt(CRYPTO_ALGO algo, CHAINING_MODE chaining,
virtual void encrypt(CRYPTO_ALGO algo, CHAINING_MODE chaining,
const unsigned char* key, unsigned int keyLength,
const unsigned char* iv, unsigned int ivLength,
const unsigned char* dataIn, unsigned int dataInLength,
@ -255,7 +250,7 @@ namespace gourou
*
* @return AES handler
*/
virtual void* EncryptInit(CRYPTO_ALGO algo, CHAINING_MODE chaining,
virtual void* encryptInit(CRYPTO_ALGO algo, CHAINING_MODE chaining,
const unsigned char* key, unsigned int keyLength,
const unsigned char* iv=0, unsigned int ivLength=0) = 0;
@ -268,7 +263,7 @@ namespace gourou
* @param dataOut Encrypted data
* @param dataOutLength Length of encrypted data
*/
virtual void EncryptUpdate(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;
/**
@ -279,7 +274,7 @@ namespace gourou
* @param dataOut Last block of encrypted data
* @param dataOutLength Length of encrypted data
*/
virtual void EncryptFinalize(void* handler, unsigned char* dataOut, unsigned int* dataOutLength) = 0;
virtual void encryptFinalize(void* handler, unsigned char* dataOut, unsigned int* dataOutLength) = 0;
/**
* @brief Do decryption. If length of data is not multiple of block size, PKCS#5 padding is done
@ -295,7 +290,7 @@ namespace gourou
* @param dataOut Encrypted data
* @param dataOutLength Length of encrypted data
*/
virtual void Decrypt(CRYPTO_ALGO algo, CHAINING_MODE chaining,
virtual void decrypt(CRYPTO_ALGO algo, CHAINING_MODE chaining,
const unsigned char* key, unsigned int keyLength,
const unsigned char* iv, unsigned int ivLength,
const unsigned char* dataIn, unsigned int dataInLength,
@ -312,7 +307,7 @@ namespace gourou
*
* @return AES handler
*/
virtual void* DecryptInit(CRYPTO_ALGO algo, CHAINING_MODE chaining,
virtual void* decryptInit(CRYPTO_ALGO algo, CHAINING_MODE chaining,
const unsigned char* key, unsigned int keyLength,
const unsigned char* iv=0, unsigned int ivLength=0) = 0;
@ -325,7 +320,7 @@ namespace gourou
* @param dataOut Decrypted data
* @param dataOutLength Length of decrypted data
*/
virtual void DecryptUpdate(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;
/**
* @brief Finalize decryption (decrypt last block and remove padding if it is set).
@ -335,7 +330,7 @@ namespace gourou
* @param dataOut Last block decrypted data
* @param dataOutLength Length of decrypted data
*/
virtual void DecryptFinalize(void* handler, unsigned char* dataOut, unsigned int* dataOutLength) = 0;
virtual void decryptFinalize(void* handler, unsigned char* dataOut, unsigned int* dataOutLength) = 0;
};

View file

@ -27,20 +27,17 @@
#include "drmprocessorclient.h"
#include <pugixml.hpp>
#include <stdint.h>
#ifndef HOBBES_DEFAULT_VERSION
#define HOBBES_DEFAULT_VERSION "10.0.4"
#endif
#ifndef DEFAULT_ADEPT_DIR
#define DEFAULT_ADEPT_DIR "./.adept"
#endif
#ifndef ACS_SERVER
#define ACS_SERVER "http://adeactivate.adobe.com/adept"
#endif
#define LIBGOUROU_VERSION "0.7.2"
#define LIBGOUROU_VERSION "0.8.8"
namespace gourou
{
@ -70,10 +67,11 @@ namespace gourou
* @brief Fulfill ACSM file to server in order to retrieve ePub fulfillment item
*
* @param ACSMFile Path of ACSMFile
* @param notify Notify server if requested by response
*
* @return a FulfillmentItem if all is OK
*/
FulfillmentItem* fulfill(const std::string& ACSMFile);
FulfillmentItem* fulfill(const std::string& ACSMFile, bool notify=true);
/**
* @brief Once fulfilled, ePub file needs to be downloaded.
@ -105,8 +103,14 @@ namespace gourou
*
* @param loanID Loan ID received during fulfill
* @param operatorURL URL of operator that loans this book
* @param notify Notify server if requested by response
*/
void returnLoan(const std::string& loanID, const std::string& operatorURL);
void returnLoan(const std::string& loanID, const std::string& operatorURL, bool notify=true);
/**
* @brief Return default ADEPT directory (ie /home/<user>/.config/adept)
*/
static std::string getDefaultAdeptDir(void);
/**
* @brief Create a new ADEPT environment (device.xml, devicesalt and activation.xml).
@ -118,7 +122,7 @@ namespace gourou
* @param ACSServer Override main ACS server (default adeactivate.adobe.com)
*/
static DRMProcessor* createDRMProcessor(DRMProcessorClient* client,
bool randomSerial=false, const std::string& dirName=std::string(DEFAULT_ADEPT_DIR),
bool randomSerial=false, std::string dirName=std::string(""),
const std::string& hobbes=std::string(HOBBES_DEFAULT_VERSION),
const std::string& ACSServer=ACS_SERVER);
@ -231,7 +235,11 @@ namespace gourou
void buildSignInRequest(pugi::xml_document& signInRequest, const std::string& adobeID, const std::string& adobePassword, const std::string& authenticationCertificate);
void fetchLicenseServiceCertificate(const std::string& licenseURL,
const std::string& operatorURL);
void decryptADEPTKey(const std::string& encryptedKey, unsigned char* decryptedKey);
void buildNotifyReq(pugi::xml_document& returnReq, pugi::xml_node& body);
void notifyServer(pugi::xml_node& notifyRoot);
void notifyServer(pugi::xml_document& fulfillReply);
std::string encryptedKeyFirstPass(pugi::xml_document& rightsDoc, const std::string& encryptedKey, const std::string& keyType);
void decryptADEPTKey(pugi::xml_document& rightsDoc, unsigned char* decryptedKey, const unsigned char* encryptionKey=0, unsigned encryptionKeySize=0);
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,

View file

@ -64,7 +64,8 @@ namespace gourou
FF_INVALID_ACSM_FILE,
FF_NO_HMAC_IN_ACSM_FILE,
FF_NOT_ACTIVATED,
FF_NO_OPERATOR_URL
FF_NO_OPERATOR_URL,
FF_SERVER_INTERNAL_ERROR
};
enum DOWNLOAD_ERROR {
@ -106,6 +107,7 @@ namespace gourou
CLIENT_INVALID_PKCS12,
CLIENT_INVALID_CERTIFICATE,
CLIENT_NO_PRIV_KEY,
CLIENT_NO_PUB_KEY,
CLIENT_RSA_ERROR,
CLIENT_BAD_CHAINING,
CLIENT_BAD_KEY_SIZE,
@ -116,6 +118,9 @@ namespace gourou
CLIENT_INVALID_PKCS8,
CLIENT_FILE_ERROR,
CLIENT_OSSL_ERROR,
CLIENT_CRYPT_ERROR,
CLIENT_DIGEST_ERROR,
CLIENT_HTTP_ERROR
};
enum DRM_REMOVAL_ERROR {
@ -125,9 +130,21 @@ namespace gourou
DRM_FORMAT_NOT_SUPPORTED,
DRM_IN_OUT_EQUALS,
DRM_MISSING_PARAMETER,
DRM_INVALID_KEY_SIZE
DRM_INVALID_KEY_SIZE,
DRM_ERR_ENCRYPTION_KEY_FP,
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 */
/**
* Generic exception class
*/
@ -140,7 +157,7 @@ namespace gourou
std::stringstream msg;
msg << "Exception code : 0x" << std::setbase(16) << code << std::endl;
msg << "Message : " << message << std::endl;
if (logLevel >= DEBUG)
if (logLevel >= LG_LOG_DEBUG)
msg << "File : " << file << ":" << std::setbase(10) << line << std::endl;
fullmessage = strdup(msg.str().c_str());
}
@ -148,12 +165,12 @@ 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);
}
~Exception()
~Exception() _NOEXCEPT
{
free(fullmessage);
}
@ -164,7 +181,7 @@ namespace gourou
private:
int code, line;
const char* message, *file;
const char* file;
char* fullmessage;
};
@ -219,24 +236,32 @@ namespace gourou
return ltrim(rtrim(s, t), t);
}
/**
* @brief Extract text node from tag in document
* It can throw an exception if tag does not exists
* or just return an empty value
*/
static inline std::string extractTextElem(const pugi::xml_document& doc, const char* tagName, bool throwOnNull=true)
static inline pugi::xml_node getNode(const pugi::xml_node& root, const char* tagName, bool throwOnNull=true)
{
pugi::xpath_node xpath_node = doc.select_node(tagName);
pugi::xpath_node xpath_node = root.select_node(tagName);
if (!xpath_node)
{
if (throwOnNull)
EXCEPTION(GOUROU_TAG_NOT_FOUND, "Tag " << tagName << " not found");
return "";
return pugi::xml_node();
}
pugi::xml_node node = xpath_node.node().first_child();
return xpath_node.node();
}
/**
* @brief Extract text node from tag in document
* It can throw an exception if tag does not exists
* or just return an empty value
*/
static inline std::string extractTextElem(const pugi::xml_node& root, const char* tagName, bool throwOnNull=true)
{
pugi::xml_node node = getNode(root, tagName, throwOnNull);
node = node.first_child();
if (!node)
{
@ -250,29 +275,50 @@ namespace gourou
return trim(res);
}
static inline std::string extractTextElem(const pugi::xml_node& doc, const char* tagName, bool throwOnNull=true)
/**
* @brief Set text node of a tag in document
* It can throw an exception if tag does not exists
*/
static inline void setTextElem(const pugi::xml_node& root, const char* tagName,
const std::string& value, bool throwOnNull=true)
{
pugi::xpath_node xpath_node = doc.select_node(tagName);
if (!xpath_node)
{
if (throwOnNull)
EXCEPTION(GOUROU_TAG_NOT_FOUND, "Tag " << tagName << " not found");
return "";
}
pugi::xml_node node = xpath_node.node().first_child();
pugi::xml_node node = getNode(root, tagName, throwOnNull);
if (!node)
{
if (throwOnNull)
EXCEPTION(GOUROU_TAG_NOT_FOUND, "Text element for tag " << tagName << " not found");
return;
}
node = node.first_child();
if (!node)
node.append_child(pugi::node_pcdata).set_value(value.c_str());
else
node.set_value(value.c_str());
}
/**
* @brief Extract text attribute from tag in document
* It can throw an exception if attribute does not exists
* or just return an empty value
*/
static inline std::string extractTextAttribute(const pugi::xml_node& root, const char* tagName, const char* attributeName, bool throwOnNull=true)
{
pugi::xml_node node = getNode(root, tagName, throwOnNull);
pugi::xml_attribute attr = node.attribute(attributeName);
if (!attr)
{
if (throwOnNull)
EXCEPTION(GOUROU_TAG_NOT_FOUND, "Attribute element " << attributeName << " for tag " << tagName << " not found");
return "";
}
std::string res = node.value();
std::string res = attr.value();
return trim(res);
}
@ -289,6 +335,29 @@ namespace gourou
node.append_child(pugi::node_pcdata).set_value(value.c_str());
}
/**
* Remove "urn:uuid:" prefix and all '-' from uuid
* urn:uuid:9cb786e8-586a-4950-8901-fff8d2ee6025
* ->
* 9cb786e8586a49508901fff8d2ee6025
*/
static inline std::string extractIdFromUUID(const std::string& uuid)
{
unsigned int i = 0;
std::string res;
if (uuid.find("urn:uuid:") == 0)
i = 9;
for(; i<uuid.size(); i++)
{
if (uuid[i] != '-')
res += uuid[i];
}
return res;
}
/**
* @brief Open a file descriptor on path. If it already exists and truncate == true, it's truncated
*
@ -414,6 +483,20 @@ namespace gourou
}
return 0;
}
static inline void dumpBuffer(GOUROU_LOG_LEVEL level, const char* title, const unsigned char* data, unsigned int len)
{
if (gourou::logLevel < level)
return;
printf("%s", title);
for(unsigned int i=0; i<len; i++)
{
if (i && !(i%16)) printf("\n");
printf("%02x ", data[i]);
}
printf("\n");
}
}
#endif

View file

@ -24,16 +24,16 @@
namespace gourou {
enum GOUROU_LOG_LEVEL {
ERROR,
WARN,
INFO,
DEBUG,
TRACE
LG_LOG_ERROR,
LG_LOG_WARN,
LG_LOG_INFO,
LG_LOG_DEBUG,
LG_LOG_TRACE
};
extern GOUROU_LOG_LEVEL logLevel;
#define GOUROU_LOG(__lvl, __msg) if (__lvl <= gourou::logLevel) {std::cout << __msg << std::endl << std::flush;}
#define GOUROU_LOG(__lvl, __msg) if (gourou::LG_LOG_##__lvl <= gourou::logLevel) {std::cout << __msg << std::endl << std::flush;}
#define GOUROU_LOG_FUNC() GOUROU_LOG(TRACE, __FUNCTION__ << "() @ " << __FILE__ << ":" << __LINE__)
/**

View file

@ -1,21 +1,8 @@
#!/bin/bash
# Pugixml
if [ ! -d lib/pugixml ] ; then
git clone https://github.com/zeux/pugixml.git lib/pugixml
pushd lib/pugixml
git checkout latest
popd
fi
# Base64
if [ ! -d lib/base64 ] ; then
git clone https://gist.github.com/f0fd86b6c73063283afe550bc5d77594.git lib/base64
fi
# uPDFParser
if [ ! -d lib/updfparser ] ; then
git clone git://soutade.fr/updfparser.git lib/updfparser
git clone https://forge.soutade.fr/soutade/uPDFParser.git lib/updfparser
pushd lib/updfparser
make BUILD_STATIC=1 BUILD_SHARED=0
popd

13
scripts/update_lib.sh Executable file
View file

@ -0,0 +1,13 @@
#!/bin/bash
if [ ! -d lib/updfparser ] ; then
echo "Some libraries are missing"
echo "You must run this script at the top of libgourou working direcotry."
echo "./scripts/setup.sh must be called first (make all)"
exit 1
fi
# uPDFParser
pushd lib/updfparser
git pull origin master
make clean all BUILD_STATIC=1 BUILD_SHARED=0

View file

@ -17,8 +17,9 @@
along with libgourou. If not, see <http://www.gnu.org/licenses/>.
*/
#include <string.h>
#include <stdexcept>
#include <base64/Base64.h>
#include <Base64.h>
#include <bytearray.h>
@ -155,12 +156,53 @@ namespace gourou
return macaron::Base64::Encode(std::string((char*)_data, _length));
}
ByteArray ByteArray::fromHex(const std::string& str)
{
if (str.size() % 2)
throw std::invalid_argument("Size of hex string not multiple of 2");
ByteArray res((unsigned int)(str.size()/2));
unsigned int i;
unsigned char* data = res.data();
unsigned char cur, tmp;
for (i=0; i<str.size(); i+=2)
{
cur = 0;
tmp = str[i];
if (tmp >= 'a' && tmp <= 'f')
cur = (tmp - 'a' + 10) << 4;
else if (tmp >= 'A' && tmp <= 'F')
cur = (tmp - 'A' + 10) << 4;
else if (tmp >= '0' && tmp <= '9')
cur = (tmp - '0') << 4;
else
throw std::invalid_argument("Invalid character in hex string");
tmp = str[i+1];
if (tmp >= 'a' && tmp <= 'f')
cur += tmp - 'a' + 10;
else if (tmp >= 'A' && tmp <= 'F')
cur += tmp - 'A' + 10;
else if (tmp >= '0' && tmp <= '9')
cur += tmp - '0';
else
throw std::invalid_argument("Invalid character in hex string");
data[i/2] = cur;
}
return res;
}
std::string ByteArray::toHex()
{
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

@ -29,13 +29,23 @@
#include <libgourou_log.h>
#include <device.h>
// From https://stackoverflow.com/questions/1779715/how-to-get-mac-address-of-your-machine-using-a-c-program/35242525
#include <string.h>
#if defined(__linux__) || defined(linux) || defined(__linux)
#include <sys/ioctl.h>
#include <net/if.h>
#include <unistd.h>
#include <netinet/in.h>
#include <string.h>
#elif (defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__) \
|| defined(__bsdi__) || defined(__DragonFly__) || defined(__APPLE__))
#include <ifaddrs.h>
#include <sys/socket.h>
#include <net/if_dl.h>
#define BSD_HEADERS 1
#endif
#if defined(__linux__) || defined(linux) || defined(__linux)
// From https://stackoverflow.com/questions/1779715/how-to-get-mac-address-of-your-machine-using-a-c-program/35242525
int get_mac_address(unsigned char* mac_address)
{
struct ifreq ifr;
@ -74,6 +84,43 @@ int get_mac_address(unsigned char* mac_address)
return 1;
}
#elif BSD_HEADERS
// https://stackoverflow.com/a/3978293
int get_mac_address(unsigned char* mac_address, const char* if_name = "en0")
{
ifaddrs* iflist;
int found = 0;
if (getifaddrs(&iflist) == 0) {
for (ifaddrs* cur = iflist; cur; cur = cur->ifa_next) {
if ((cur->ifa_addr->sa_family == AF_LINK) &&
(strcmp(cur->ifa_name, if_name) == 0) &&
cur->ifa_addr) {
sockaddr_dl* sdl = (sockaddr_dl*)cur->ifa_addr;
memcpy(mac_address, LLADDR(sdl), sdl->sdl_alen);
found = 1;
break;
}
}
freeifaddrs(iflist);
}
return found;
}
#else
int get_mac_address(unsigned char* mac_address)
{
GOUROU_LOG(INFO, "get_mac_address() not implemented for your platform, using a static address");
mac_address[0] = 0x8D;
mac_address[1] = 0x70;
mac_address[2] = 0x13;
mac_address[3] = 0x8D;
mac_address[4] = 0x43;
mac_address[5] = 0x27;
return 1;
}
#endif /* defined(__linux__) || defined(linux) || defined(__linux) */
namespace gourou

View file

@ -17,6 +17,7 @@
along with libgourou. If not, see <http://www.gnu.org/licenses/>.
*/
#include <cctype>
#include <fulfillment_item.h>
#include <libgourou_common.h>
#include "user.h"
@ -93,8 +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
std::transform(name.begin(), name.end(), name.begin(),
[](unsigned char c){ return std::tolower(c); });
#else
std::transform(name.begin(), name.end(), name.begin(), tolower);
#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>
@ -28,6 +29,8 @@
#include <libgourou_common.h>
#include <libgourou_log.h>
#define LOCAL_ADEPT_DIR "./.adept"
#define ASN_NONE 0x00
#define ASN_NS_TAG 0x01
#define ASN_CHILD 0x02
@ -37,7 +40,7 @@
namespace gourou
{
GOUROU_LOG_LEVEL logLevel = WARN;
GOUROU_LOG_LEVEL logLevel = LG_LOG_WARN;
const std::string DRMProcessor::VERSION = LIBGOUROU_VERSION;
DRMProcessor::DRMProcessor(DRMProcessorClient* client):client(client), device(0), user(0)
@ -68,11 +71,23 @@ namespace gourou
if (user) delete user;
}
DRMProcessor* DRMProcessor::createDRMProcessor(DRMProcessorClient* client, bool randomSerial, const std::string& dirName,
// 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)
{
DRMProcessor* processor = new DRMProcessor(client);
if (dirName == "")
dirName = getDefaultAdeptDir();
Device* device = Device::createDevice(processor, dirName, hobbes, randomSerial);
processor->device = device;
@ -89,7 +104,7 @@ namespace gourou
uint16_t nlength = htons(length);
char c;
if (logLevel >= TRACE)
if (logLevel >= LG_LOG_TRACE)
printf("%02x %02x ", ((uint8_t*)&nlength)[0], ((uint8_t*)&nlength)[1]);
client->digestUpdate(sha_ctx, (unsigned char*)&nlength, sizeof(nlength));
@ -98,17 +113,17 @@ namespace gourou
{
c = string[i];
client->digestUpdate(sha_ctx, (unsigned char*)&c, 1);
if (logLevel >= TRACE)
if (logLevel >= LG_LOG_TRACE)
printf("%c", c);
}
if (logLevel >= TRACE)
if (logLevel >= LG_LOG_TRACE)
printf("\n");
}
void DRMProcessor::pushTag(void* sha_ctx, uint8_t tag)
{
client->digestUpdate(sha_ctx, &tag, sizeof(tag));
if (logLevel >= TRACE)
if (logLevel >= LG_LOG_TRACE)
printf("%02x ", tag);
}
@ -226,14 +241,7 @@ namespace gourou
client->digestFinalize(sha_ctx, sha_out);
if (logLevel >= DEBUG)
{
printf("\nSHA OUT : ");
for(int i=0; i<(int)SHA1_LEN; i++)
printf("%02x ", sha_out[i]);
printf("\n");
}
dumpBuffer(gourou::LG_LOG_DEBUG, "\nSHA OUT : ", sha_out, SHA1_LEN);
}
void DRMProcessor::signNode(pugi::xml_node& rootNode)
@ -252,13 +260,8 @@ namespace gourou
client->RSAPrivateEncrypt(privateRSAKey.data(), privateRSAKey.length(),
RSAInterface::RSA_KEY_PKCS12, deviceKey.toBase64().data(),
sha_out, sizeof(sha_out), res);
if (logLevel >= DEBUG)
{
printf("Sig : ");
for(int i=0; i<(int)sizeof(res); i++)
printf("%02x ", res[i]);
printf("\n");
}
dumpBuffer(gourou::LG_LOG_DEBUG, "Sig : ", res, sizeof(res));
std::string signature = ByteArray(res, sizeof(res)).toBase64();
appendTextElem(rootNode, "adept:signature", signature);
@ -282,7 +285,11 @@ namespace gourou
struct timeval tv;
gettimeofday(&tv, 0);
uint32_t nonce32[2] = {0x6f046000, 0x388a};
#ifdef STATIC_NONCE
uint64_t bigtime = 0xAA001122BBCCAAULL;
#else
uint64_t bigtime = tv.tv_sec*1000;
#endif
nonce32[0] += (bigtime & 0xFFFFFFFF) + (tv.tv_usec/1000);
nonce32[1] += ((bigtime >> 32) & 0xFFFFFFFF);
@ -402,30 +409,6 @@ namespace gourou
}
doOperatorAuth(operatorURL);
// Add new operatorURL to list
pugi::xml_document activationDoc;
user->readActivation(activationDoc);
pugi::xml_node root;
pugi::xpath_node xpathRes = activationDoc.select_node("//adept:operatorURLList");
// Create adept:operatorURLList if it doesn't exists
if (!xpathRes)
{
xpathRes = activationDoc.select_node("/activationInfo");
root = xpathRes.node();
root = root.append_child("adept:operatorURLList");
root.append_attribute("xmlns:adept") = ADOBE_ADEPT_NS;
appendTextElem(root, "adept:user", user->getUUID());
}
else
root = xpathRes.node();
appendTextElem(root, "adept:operatorURL", operatorURL);
user->updateActivationFile(activationDoc);
}
void DRMProcessor::buildFulfillRequest(pugi::xml_document& acsmDoc, pugi::xml_document& fulfillReq)
@ -495,10 +478,28 @@ namespace gourou
appendTextElem(root, "adept:licenseURL", licenseURL);
appendTextElem(root, "adept:certificate", certificate);
// Add new operatorURL to list
xpathRes = activationDoc.select_node("//adept:operatorURLList");
// Create adept:operatorURLList if it doesn't exists
if (!xpathRes)
{
xpathRes = activationDoc.select_node("/activationInfo");
root = xpathRes.node();
root = root.append_child("adept:operatorURLList");
root.append_attribute("xmlns:adept") = ADOBE_ADEPT_NS;
appendTextElem(root, "adept:user", user->getUUID());
}
else
root = xpathRes.node();
appendTextElem(root, "adept:operatorURL", operatorURL);
user->updateActivationFile(activationDoc);
}
FulfillmentItem* DRMProcessor::fulfill(const std::string& ACSMFile)
FulfillmentItem* DRMProcessor::fulfill(const std::string& ACSMFile, bool notify)
{
if (!user->getPKCS12().length())
EXCEPTION(FF_NOT_ACTIVATED, "Device not activated");
@ -508,14 +509,31 @@ namespace gourou
if (!acsmDoc.load_file(ACSMFile.c_str(), pugi::parse_ws_pcdata_single|pugi::parse_escapes, pugi::encoding_utf8))
EXCEPTION(FF_INVALID_ACSM_FILE, "Invalid ACSM file " << ACSMFile);
// Could be an server internal error
pugi::xml_node rootNode = acsmDoc.first_child();
if (std::string(rootNode.name()) == "error")
{
EXCEPTION(FF_SERVER_INTERNAL_ERROR, rootNode.attribute("data").value());
}
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;
buildFulfillRequest(acsmDoc, fulfillReq);
pugi::xpath_node root = fulfillReq.select_node("//adept:fulfill");
pugi::xml_node rootNode = root.node();
rootNode = root.node();
// Remove HMAC
pugi::xpath_node xpathRes = fulfillReq.select_node("//hmac");
@ -576,7 +594,12 @@ namespace gourou
fetchLicenseServiceCertificate(licenseURL, operatorURL);
return new FulfillmentItem(fulfillReply, user);
FulfillmentItem* item = new FulfillmentItem(fulfillReply, user);
if (notify)
notifyServer(fulfillReply);
return item;
}
DRMProcessor::ITEM_TYPE DRMProcessor::download(FulfillmentItem* item, std::string path, bool resume)
@ -853,7 +876,31 @@ namespace gourou
signNode(root);
}
void DRMProcessor::returnLoan(const std::string& loanID, const std::string& operatorURL)
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])
{
return std::string("/home/") + user + std::string("/.config/adept/");
}
else
return LOCAL_ADEPT_DIR;
}
#else
return DEFAULT_ADEPT_DIR "/";
#endif
}
void DRMProcessor::returnLoan(const std::string& loanID, const std::string& operatorURL,
bool notify)
{
pugi::xml_document returnReq;
@ -861,7 +908,71 @@ namespace gourou
buildReturnReq(returnReq, loanID, operatorURL);
sendRequest(returnReq, operatorURL + "/LoanReturn");
ByteArray replyData = sendRequest(returnReq, operatorURL + "/LoanReturn");
pugi::xml_document fulfillReply;
fulfillReply.load_string((const char*)replyData.data());
if (notify)
notifyServer(fulfillReply);
}
void DRMProcessor::buildNotifyReq(pugi::xml_document& returnReq, pugi::xml_node& body)
{
pugi::xml_node decl = returnReq.append_child(pugi::node_declaration);
decl.append_attribute("version") = "1.0";
pugi::xml_node root = returnReq.append_child("adept:notification");
root.append_attribute("xmlns:adept") = ADOBE_ADEPT_NS;
appendTextElem(root, "adept:user", user->getUUID());
appendTextElem(root, "adept:device", user->getDeviceUUID());
body = root.append_copy(body);
body.append_attribute("xmlns") = ADOBE_ADEPT_NS;
addNonce(root);
signNode(root);
}
void DRMProcessor::notifyServer(pugi::xml_node& notifyRoot)
{
std::string notifyUrl = extractTextElem(notifyRoot, "//notifyURL", false);
pugi::xml_node notifyBody = getNode(notifyRoot, "//body", false);
if (notifyUrl == "")
{
GOUROU_LOG(INFO, "No notify URL");
return;
}
if (!notifyBody)
{
GOUROU_LOG(INFO, "No notify body");
return;
}
pugi::xml_document notifyReq;
buildNotifyReq(notifyReq, notifyBody);
sendRequest(notifyReq, notifyUrl);
}
void DRMProcessor::notifyServer(pugi::xml_document& fulfillReply)
{
pugi::xpath_node_set notifySet = fulfillReply.select_nodes("//notify");
if (notifySet.empty())
{
GOUROU_LOG(DEBUG, "No notify request");
return;
}
for (pugi::xpath_node_set::const_iterator it = notifySet.begin(); it != notifySet.end(); ++it)
{
pugi::xml_node notifyRoot = it->node();
notifyServer(notifyRoot);
}
}
ByteArray DRMProcessor::encryptWithDeviceKey(const unsigned char* data, unsigned int len)
@ -877,7 +988,7 @@ namespace gourou
// Generate IV in front
client->randBytes(encrypted_data, 16);
client->Encrypt(CryptoInterface::ALGO_AES, CryptoInterface::CHAIN_CBC,
client->encrypt(CryptoInterface::ALGO_AES, CryptoInterface::CHAIN_CBC,
deviceKey, 16, encrypted_data, 16,
data, len,
encrypted_data+16, &outLen);
@ -896,7 +1007,7 @@ namespace gourou
const unsigned char* deviceKey = device->getDeviceKey();
unsigned char* decrypted_data = new unsigned char[len-16];
client->Decrypt(CryptoInterface::ALGO_AES, CryptoInterface::CHAIN_CBC,
client->decrypt(CryptoInterface::ALGO_AES, CryptoInterface::CHAIN_CBC,
deviceKey, 16, data, 16,
data+16, len-16,
decrypted_data, &outLen);
@ -939,42 +1050,154 @@ namespace gourou
void DRMProcessor::exportPrivateLicenseKey(std::string path)
{
int fd = open(path.c_str(), O_CREAT|O_TRUNC|O_WRONLY, S_IRWXU);
int ret;
if (fd <= 0)
EXCEPTION(GOUROU_FILE_ERROR, "Unable to open " << path);
ByteArray privateLicenseKey = ByteArray::fromBase64(user->getPrivateLicenseKey());
/* In adobekey.py, we get base64 decoded data [26:] */
write(fd, privateLicenseKey.data()+26, privateLicenseKey.length()-26);
ret = write(fd, privateLicenseKey.data()+26, privateLicenseKey.length()-26);
close(fd);
if (ret != (int)(privateLicenseKey.length()-26))
{
EXCEPTION(gourou::GOUROU_FILE_ERROR, "Error writing " << path);
}
}
int DRMProcessor::getLogLevel() {return (int)gourou::logLevel;}
void DRMProcessor::setLogLevel(int logLevel) {gourou::logLevel = (GOUROU_LOG_LEVEL)logLevel;}
void DRMProcessor::decryptADEPTKey(const std::string& encryptedKey, unsigned char* decryptedKey)
/**
* RSA Key can be over encrypted with AES128-CBC if keyType attribute is set
* remainder = keyType % 16
* Key = SHA256(keyType)[remainder*2:remainder*2+(16-remainder)] || SHA256(keyType)[16-remainder:16]
* IV = DeviceID ^ FulfillmentId ^ VoucherId
*
* @return Base64 encoded decrypted key
*/
std::string DRMProcessor::encryptedKeyFirstPass(pugi::xml_document& rightsDoc, const std::string& encryptedKey, const std::string& keyType)
{
unsigned char digest[32], key[16], iv[16];
unsigned int dataOutLength;
std::string id;
client->digest("SHA256", (unsigned char*)keyType.c_str(), keyType.size(), digest);
dumpBuffer(gourou::LG_LOG_DEBUG, "SHA of KeyType : ", digest, sizeof(digest));
long nonce = std::stol(keyType);
int remainder = nonce % 16;
memcpy(key, &digest[remainder*2], 16-remainder);
memcpy(&key[16-remainder], &digest[remainder], remainder);
id = extractTextElem(rightsDoc, "/adept:rights/licenseToken/device");
if (id == "")
EXCEPTION(DRM_ERR_ENCRYPTION_KEY_FP, "Device id not found in rights.xml");
ByteArray deviceId = ByteArray::fromHex(extractIdFromUUID(id));
unsigned char* _deviceId = deviceId.data();
id = extractTextElem(rightsDoc, "/adept:rights/licenseToken/fulfillment");
if (id == "")
EXCEPTION(DRM_ERR_ENCRYPTION_KEY_FP, "Fulfillment id not found in rights.xml");
ByteArray fulfillmentId = ByteArray::fromHex(extractIdFromUUID(id));
unsigned char* _fulfillmentId = fulfillmentId.data();
id = extractTextElem(rightsDoc, "/adept:rights/licenseToken/voucher");
if (id == "")
EXCEPTION(DRM_ERR_ENCRYPTION_KEY_FP, "Voucher id not found in rights.xml");
ByteArray voucherId = ByteArray::fromHex(extractIdFromUUID(id));
unsigned char* _voucherId = voucherId.data();
if (deviceId.size() < sizeof(iv) || fulfillmentId.size() < sizeof(iv) || voucherId.size() < sizeof(iv))
EXCEPTION(DRM_ERR_ENCRYPTION_KEY_FP, "One id has a bad length");
for(unsigned int i=0; i<sizeof(iv); i++)
iv[i] = _deviceId[i] ^ _fulfillmentId[i] ^ _voucherId[i];
dumpBuffer(gourou::LG_LOG_DEBUG, "First pass key : ", key, sizeof(key));
dumpBuffer(gourou::LG_LOG_DEBUG, "First pass IV : ", iv, sizeof(iv));
ByteArray arrayEncryptedKey = ByteArray::fromBase64(encryptedKey);
unsigned char* clearRSAKey = new unsigned char[arrayEncryptedKey.size()];
client->decrypt(CryptoInterface::ALGO_AES, CryptoInterface::CHAIN_CBC,
(const unsigned char*)key, (unsigned int)sizeof(key),
(const unsigned char*)iv, (unsigned int)sizeof(iv),
(const unsigned char*)arrayEncryptedKey.data(), arrayEncryptedKey.size(),
(unsigned char*)clearRSAKey, &dataOutLength);
dumpBuffer(gourou::LG_LOG_DEBUG, "\nDecrypted key : ", clearRSAKey, dataOutLength);
/* Last block could be 0x10*16 which is OpenSSL padding, remove it if it's the case */
bool skipLastLine = true;
for(unsigned int i=dataOutLength-16; i<dataOutLength; i++)
{
if (clearRSAKey[i] != 0x10)
{
skipLastLine = false;
break;
}
}
ByteArray res(clearRSAKey, (skipLastLine)?dataOutLength-16:dataOutLength);
delete[] clearRSAKey;
return res.toBase64();
}
void DRMProcessor::decryptADEPTKey(pugi::xml_document& rightsDoc, unsigned char* decryptedKey, const unsigned char* encryptionKey, unsigned encryptionKeySize)
{
unsigned char rsaKey[RSA_KEY_SIZE];
std::string user = extractTextElem(rightsDoc, "/adept:rights/licenseToken/user");
if (!encryptionKey)
{
if (this->user->getUUID() != user)
{
EXCEPTION(DRM_INVALID_USER, "This book has been downloaded for another user (" << user << ")");
}
std::string encryptedKey = extractTextElem(rightsDoc, "/adept:rights/licenseToken/encryptedKey");
std::string keyType = extractTextAttribute(rightsDoc, "/adept:rights/licenseToken/encryptedKey", "keyType", false);
if (keyType != "")
encryptedKey = encryptedKeyFirstPass(rightsDoc, encryptedKey, keyType);
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();
std::string privateKeyData = this->user->getPrivateLicenseKey();
ByteArray privateRSAKey = ByteArray::fromBase64(privateKeyData);
ByteArray deviceKey(device->getDeviceKey(), Device::DEVICE_KEY_SIZE);
std::string pkcs12 = user->getPKCS12();
dumpBuffer(gourou::LG_LOG_DEBUG, "To decrypt : ", arrayEncryptedKey.data(), arrayEncryptedKey.length());
client->RSAPrivateDecrypt(privateRSAKey.data(), privateRSAKey.length(),
RSAInterface::RSA_KEY_PKCS12, deviceKey.toBase64().data(),
arrayEncryptedKey.data(), arrayEncryptedKey.length(), decryptedKey);
RSAInterface::RSA_KEY_PKCS8, "",
arrayEncryptedKey.data(), arrayEncryptedKey.length(), rsaKey);
if (decryptedKey[0] != 0x00 || decryptedKey[1] != 0x02 ||
decryptedKey[RSA_KEY_SIZE-16-1] != 0x00)
dumpBuffer(gourou::LG_LOG_DEBUG, "Decrypted : ", rsaKey, sizeof(rsaKey));
if (rsaKey[0] != 0x00 || rsaKey[1] != 0x02 ||
rsaKey[RSA_KEY_SIZE-16-1] != 0x00)
EXCEPTION(DRM_ERR_ENCRYPTION_KEY, "Unable to retrieve encryption key");
}
memcpy(decryptedKey, &rsaKey[sizeof(rsaKey)-16], 16);
}
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, encryptionKey, encryptionKeySize);
}
}
void DRMProcessor::removeEPubDRM(const std::string& filenameIn, const std::string& filenameOut,
const unsigned char* encryptionKey, unsigned encryptionKeySize)
@ -987,19 +1210,9 @@ namespace gourou
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];
unsigned char decryptedKey[16];
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);
}
decryptADEPTKey(rightsDoc, decryptedKey, encryptionKey, encryptionKeySize);
client->zipReadFile(zipHandler, "META-INF/encryption.xml", zipData);
pugi::xml_document encryptionDoc;
@ -1037,8 +1250,8 @@ namespace gourou
gourou::ByteArray inflateData(true);
unsigned int dataOutLength;
client->Decrypt(CryptoInterface::ALGO_AES, CryptoInterface::CHAIN_CBC,
decryptedKey+sizeof(decryptedKey)-16, 16, /* Key */
client->decrypt(CryptoInterface::ALGO_AES, CryptoInterface::CHAIN_CBC,
decryptedKey, sizeof(decryptedKey), /* Key */
_data, 16, /* IV */
&_data[16], zipData.length()-16,
_clearData, &dataOutLength);
@ -1129,9 +1342,10 @@ 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;
unsigned char decryptedKey[RSA_KEY_SIZE];
std::vector<uPDFParser::Object*> ebxObjects;
unsigned char decryptedKey[16];
int ebxId;
for(rIt = objects.rbegin(); rIt != objects.rend(); rIt++)
@ -1170,18 +1384,7 @@ namespace gourou
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);
}
decryptADEPTKey(rightsDoc, decryptedKey, encryptionKey, encryptionKeySize);
ebxId = ebx->objectId();
@ -1200,7 +1403,7 @@ namespace gourou
if (object->objectId() == ebxId)
{
// object->deleteKey("Filter");
ebxObjects.push_back(object);
continue;
}
@ -1213,10 +1416,10 @@ namespace gourou
GOUROU_LOG(DEBUG, "Obj " << object->objectId());
unsigned char tmpKey[16];
unsigned char tmpKey[sizeof(decryptedKey)];
generatePDFObjectKey(ebxVersion->value(),
decryptedKey+sizeof(decryptedKey)-16, 16,
decryptedKey, sizeof(decryptedKey),
object->objectId(), object->generationNumber(),
tmpKey);
@ -1241,8 +1444,8 @@ namespace gourou
GOUROU_LOG(DEBUG, "Decrypt string " << dictIt->first << " " << dataLength);
client->Decrypt(CryptoInterface::ALGO_RC4, CryptoInterface::CHAIN_ECB,
tmpKey, 16, /* Key */
client->decrypt(CryptoInterface::ALGO_RC4, CryptoInterface::CHAIN_ECB,
tmpKey, sizeof(tmpKey), /* Key */
NULL, 0, /* IV */
encryptedData, dataLength,
clearData, &dataOutLength);
@ -1252,6 +1455,30 @@ namespace gourou
delete[] clearData;
}
else if (dictData->type() == uPDFParser::DataType::HEXASTRING)
{
string = ((uPDFParser::HexaString*) dictData)->value();
ByteArray hexStr = ByteArray::fromHex(string);
unsigned char* encryptedData = hexStr.data();
unsigned int dataLength = hexStr.size();
unsigned char* clearData = new unsigned char[dataLength];
unsigned int dataOutLength;
GOUROU_LOG(DEBUG, "Decrypt hexa string " << dictIt->first << " " << dataLength);
client->decrypt(CryptoInterface::ALGO_RC4, CryptoInterface::CHAIN_ECB,
tmpKey, sizeof(tmpKey), /* Key */
NULL, 0, /* IV */
encryptedData, dataLength,
clearData, &dataOutLength);
ByteArray clearHexStr = ByteArray(clearData, dataOutLength);
decodedStrings[dictIt->first] = new uPDFParser::HexaString(
clearHexStr.toHex());
delete[] clearData;
}
}
for (dictIt = decodedStrings.begin(); dictIt != decodedStrings.end(); dictIt++)
@ -1274,8 +1501,8 @@ namespace gourou
GOUROU_LOG(DEBUG, "Decrypt stream id " << object->objectId() << ", size " << stream->dataLength());
client->Decrypt(CryptoInterface::ALGO_RC4, CryptoInterface::CHAIN_ECB,
tmpKey, 16, /* Key */
client->decrypt(CryptoInterface::ALGO_RC4, CryptoInterface::CHAIN_ECB,
tmpKey, sizeof(tmpKey), /* Key */
NULL, 0, /* IV */
encryptedData, dataLength,
clearData, &dataOutLength);
@ -1286,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

@ -29,24 +29,13 @@ namespace gourou
if (!node)
EXCEPTION(FFI_INVALID_LOAN_TOKEN, "No loanToken element in document");
node = doc.select_node("/envelope/loanToken/loan").node();
node = doc.select_node("/envelope/fulfillmentResult/fulfillment").node();
if (node)
properties["id"] = node.first_child().value();
else
{
node = doc.select_node("/envelope/fulfillmentResult/resourceItemInfo/licenseToken/permissions/display/loan").node();
if (node)
properties["id"] = node.first_child().value();
else
{
node = doc.select_node("/envelope/fulfillmentResult/resourceItemInfo/licenseToken/permissions/play/loan").node();
if (node)
properties["id"] = node.first_child().value();
else
EXCEPTION(FFI_INVALID_LOAN_TOKEN, "No loanToken/loan element in document");
}
EXCEPTION(FFI_INVALID_LOAN_TOKEN, "No fulfillment element in document");
}
node = doc.select_node("/envelope/loanToken/operatorURL").node();

View file

@ -1 +0,0 @@
../lib/pugixml/src/pugixml.cpp

View file

@ -1,20 +1,16 @@
BINDIR ?= /bin
MANDIR ?= /share/man
TARGETS=acsmdownloader adept_activate adept_remove adept_loan_mgt
TARGET_BINARIES=acsmdownloader adept_activate adept_remove adept_loan_mgt
TARGETS=$(TARGET_BINARIES) launcher
CXXFLAGS=-Wall -fPIC -I$(ROOT)/include -I$(ROOT)/lib/pugixml/src/
LDFLAGS=
ifneq ($(OPENSSL3),)
# OpenSSL 1.1.0 compat
CXXFLAGS += -DOPENSSL_API_COMPAT=0x10100000L
CXXFLAGS += -I/tmp/openssl3/usr/include/ -I/tmp/openssl3/usr/include/x86_64-linux-gnu
LDFLAGS += -L/tmp/openssl3/usr/lib/x86_64-linux-gnu -L/tmp/openssl3/usr/lib/x86_64-linux-gnu/ossl-modules
endif
MAN_PAGES=acsmdownloader adept_activate adept_remove adept_loan_mgt
CXXFLAGS=-Wall -fPIC -I$(ROOT)/include -fmacro-prefix-map=$(ROOT)/= -fdata-sections -ffunction-sections
STATIC_DEP=
LDFLAGS += -L$(ROOT) -lcrypto -lzip -lz -lcurl
# LDFLAGS += -Wl,--gc-sections
LDFLAGS += -L$(ROOT) -lcrypto -lzip -lz -lcurl -lpugixml
ifneq ($(STATIC_UTILS),)
STATIC_DEP = $(ROOT)/libgourou.a
@ -23,7 +19,7 @@ LDFLAGS += -lgourou
endif
ifneq ($(DEBUG),)
CXXFLAGS += -ggdb -O0
CXXFLAGS += -ggdb -O0 -DDEBUG
else
CXXFLAGS += -O2
endif
@ -35,23 +31,27 @@ COMMON_LIB = utils.a
all: $(TARGETS)
${COMMON_LIB}: ${COMMON_DEPS} ${STATIC_DEP}
$(CXX) $(CXXFLAGS) ${COMMON_DEPS} $(LDFLAGS) -c
$(AR) crs $@ ${COMMON_OBJECTS} $(STATIC_DEP)
${COMMON_LIB}: $(COMMON_DEPS)
$(CXX) $(CXXFLAGS) $(COMMON_DEPS) -c
$(AR) crs $@ $(COMMON_OBJECTS)
acsmdownloader: acsmdownloader.cpp ${COMMON_LIB}
$(CXX) $(CXXFLAGS) $^ $(LDFLAGS) -o $@
%: %.cpp $(COMMON_LIB) $(STATIC_DEP)
$(CXX) $(CXXFLAGS) $^ $(STATIC_DEP) $(LDFLAGS) -o $@
adept_activate: adept_activate.cpp ${COMMON_LIB}
$(CXX) $(CXXFLAGS) $^ $(LDFLAGS) -o $@
install: $(TARGET_BINARIES)
install -d $(DESTDIR)$(PREFIX)/$(BINDIR)
install -m 755 $(TARGET_BINARIES) $(DESTDIR)$(PREFIX)/$(BINDIR)
install -d $(DESTDIR)$(PREFIX)/$(MANDIR)/man1
install -m 644 man/*.1 $(DESTDIR)$(PREFIX)/$(MANDIR)/man1
adept_remove: adept_remove.cpp ${COMMON_LIB}
$(CXX) $(CXXFLAGS) $^ $(LDFLAGS) -o $@
adept_loan_mgt: adept_loan_mgt.cpp ${COMMON_LIB}
$(CXX) $(CXXFLAGS) $^ $(LDFLAGS) -o $@
uninstall:
cd $(DESTDIR)$(PREFIX)/$(BINDIR)
rm -f $(TARGET_BINARIES)
cd -
cd $(DESTDIR)$(PREFIX)/$(MANDIR)/man1
rm -f $(addsuffix .1,$(TARGET_BINARIES)
clean:
rm -f $(TARGETS)
rm -f $(TARGETS) $(COMMON_LIB)
ultraclean: clean

View file

@ -46,6 +46,7 @@ static bool exportPrivateKey = false;
static const char* outputFile = 0;
static const char* outputDir = 0;
static bool resume = false;
static bool notify = true;
class ACSMDownloader
@ -63,29 +64,30 @@ 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;
}
else
{
gourou::FulfillmentItem* item = processor.fulfill(acsmFile);
gourou::FulfillmentItem* item = processor.fulfill(acsmFile, notify);
std::string filename;
if (!outputFile)
if (outputFile)
filename = outputFile;
else
{
filename = item->getMetadata("title");
if (filename == "")
@ -95,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)
@ -182,24 +179,31 @@ private:
static void usage(const char* cmd)
{
std::cout << "Download EPUB file from ACSM request file" << std::endl;
std::cout << "Usage: " << cmd << " [(-d|--device-file) device.xml] [(-a|--activation-file) activation.xml] [(-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 << " " << "-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 <title.(epub|pdf|der)>)" << std::endl;
std::cout << " " << "-f|--acsm-file" << "\t" << "ACSM request file for epub download" << std::endl;
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 ./) (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;
std::cout << " " << "-N|--no-notify" << "\t\t" << "Don't notify server, even if requested" << 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 << "ADEPT Options:" << std::endl;
std::cout << " " << "-D|--adept-directory" << "\t" << ".adept directory that must contains device.xml, activation.xml and devicesalt" << 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 << 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 << "Environment:" << std::endl;
std::cout << "Device file, activation file and device key file are optionals. If not set, they are looked into :" << std::endl << std::endl;
std::cout << " * $ADEPT_DIR environment variable" << std::endl;
std::cout << " * /home/<user>/.config/adept" << std::endl;
std::cout << " * Current directory" << std::endl;
std::cout << " * .adept" << std::endl;
std::cout << " * adobe-digital-editions directory" << std::endl;
@ -209,6 +213,7 @@ static void usage(const char* cmd)
int main(int argc, char** argv)
{
int c, ret = -1;
std::string _deviceFile, _activationFile, _devicekeyFile;
const char** files[] = {&devicekeyFile, &deviceFile, &activationFile};
int verbose = gourou::DRMProcessor::getLogLevel();
@ -216,6 +221,7 @@ int main(int argc, char** argv)
while (1) {
int option_index = 0;
static struct option long_options[] = {
{"adept-directory", required_argument, 0, 'D' },
{"device-file", required_argument, 0, 'd' },
{"activation-file", required_argument, 0, 'a' },
{"device-key-file", required_argument, 0, 'k' },
@ -224,18 +230,27 @@ int main(int argc, char** argv)
{"acsm-file", required_argument, 0, 'f' },
{"export-private-key",no_argument, 0, 'e' },
{"resume", no_argument, 0, 'r' },
{"no-notify", no_argument, 0, 'N' },
{"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:ervVh",
c = getopt_long(argc, argv, "D:d:a:k:O:o:f:erNvVh",
long_options, &option_index);
if (c == -1)
break;
switch (c) {
case 'D':
_deviceFile = std::string(optarg) + "/device.xml";
_activationFile = std::string(optarg) + "/activation.xml";
_devicekeyFile = std::string(optarg) + "/devicesalt";
deviceFile = _deviceFile.c_str();
activationFile = _activationFile.c_str();
devicekeyFile = _devicekeyFile.c_str();
break;
case 'd':
deviceFile = optarg;
break;
@ -260,6 +275,9 @@ int main(int argc, char** argv)
case 'r':
resume = true;
break;
case 'N':
notify = false;
break;
case 'v':
verbose++;
break;
@ -277,6 +295,9 @@ int main(int argc, char** argv)
gourou::DRMProcessor::setLogLevel(verbose);
if (optind == argc-1)
acsmFile = argv[optind];
if ((!acsmFile && !exportPrivateKey) || (outputDir && !outputDir[0]) ||
(outputFile && !outputFile[0]))
{
@ -284,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;
@ -314,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

@ -31,6 +31,7 @@
#include <termios.h>
#include <string.h>
#include <limits.h>
#include <libgen.h>
#include <iostream>
#include <ostream>
@ -124,10 +125,11 @@ public:
static void usage(const char* cmd)
{
std::cout << "Create new device files used by ADEPT DRM" << std::endl;
std::cout << basename((char*)cmd) << " create new device files used by ADEPT DRM" << 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 << "Usage: " << basename((char*)cmd) << " OPTIONS" << std::endl << std::endl;
std::cout << "Global Options:" << std::endl;
std::cout << " " << "-a|--anonymous" << "\t" << "Anonymous account, no need for username/password (Use it only with a DRM removal software)" << std::endl;
std::cout << " " << "-u|--username" << "\t\t" << "AdobeID username (ie adobe.com email account)" << std::endl;
std::cout << " " << "-p|--password" << "\t\t" << "AdobeID password (asked if not set via command line) " << std::endl;
@ -230,7 +232,7 @@ int main(int argc, char** argv)
if (!_outputDir || _outputDir[0] == 0)
{
outputDir = strdup(abspath(DEFAULT_ADEPT_DIR));
outputDir = strdup(gourou::DRMProcessor::getDefaultAdeptDir().c_str());
}
else
{
@ -238,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));
@ -248,7 +250,7 @@ int main(int argc, char** argv)
}
std::string pass;
if (fileExists(outputDir))
if (pathExists(outputDir))
{
int key;

View file

@ -45,13 +45,14 @@
#define MAX_SIZE_BOOK_NAME 30
static char* activationDir = 0;
static char* adeptDir = 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;
static bool notify = true;
struct Loan
{
@ -106,9 +107,9 @@ private:
struct Loan* loan;
char * res;
std::string loanDir = std::string(activationDir) + std::string("/") + LOANS_DIR;
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());
@ -182,8 +183,13 @@ private:
loan->bookName = node.first_child().value();
struct tm tm;
#ifdef __ANDROID__
res = strptime(loan->validity.c_str(), "%Y-%m-%dT%H:%M:%S%z", &tm);
#else
res = strptime(loan->validity.c_str(), "%Y-%m-%dT%H:%M:%S%Z", &tm);
if (*res == 0)
#endif
if (res != NULL && *res == 0)
{
if (mktime(&tm) <= time(NULL))
loan->validity = " (Expired)";
@ -209,7 +215,7 @@ private:
{
if (!loanedBooks.size())
{
std::cout << "Any book loaned" << std::endl;
std::cout << "No books loaned" << std::endl;
return;
}
@ -223,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++;
@ -252,7 +263,7 @@ private:
std::cout.width (fillExpiration);
std::cout << "";
std::cout << "Exipration";
std::cout << "Expiration";
std::cout.width (fillExpiration);
std::cout << "" << std::endl;
@ -270,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;
@ -296,7 +309,7 @@ private:
return;
}
processor.returnLoan(loan->id, loan->operatorURL);
processor.returnLoan(loan->id, loan->operatorURL, notify);
deleteID = returnID;
if (deleteLoan(false))
@ -334,20 +347,27 @@ private:
static void usage(const char* cmd)
{
std::cout << "Manage loaned books" << std::endl;
std::cout << basename((char*)cmd) << " manage loaned books" << std::endl << 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 << "Usage: " << basename((char*)cmd) << " [OPTIONS]" << std::endl << std::endl;
std::cout << " " << "-d|--activation-dir" << "\t" << "Directory of device.xml/activation.xml and device key" << std::endl;
std::cout << "Global Options:" << 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 << " " << "-d|--delete" << "\t\t" << "Delete a loan entry without returning it" << std::endl;
std::cout << " " << "-N|--no-notify" << "\t\t" << "Don't notify server, even if requested" << 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 << "ADEPT Options:" << std::endl;
std::cout << " " << "-D|--adept-directory" << "\t" << ".adept directory that must contains device.xml, activation.xml and devicesalt" << std::endl;
std::cout << std::endl;
std::cout << "Activation directory is optional. If not set, it's looked into :" << std::endl;
std::cout << "Environment:" << std::endl;
std::cout << "ADEPT directory is optional. If not set, it's looked into :" << std::endl;
std::cout << " * $ADEPT_DIR environment variable" << std::endl;
std::cout << " * /home/<user>/.config/adept" << std::endl;
std::cout << " * Current directory" << std::endl;
std::cout << " * .adept" << std::endl;
std::cout << " * adobe-digital-editions directory" << std::endl;
@ -365,10 +385,11 @@ int main(int argc, char** argv)
while (1) {
int option_index = 0;
static struct option long_options[] = {
{"activation-dir", required_argument, 0, 'd' },
{"adept-directory", required_argument, 0, 'D' },
{"list", no_argument, 0, 'l' },
{"return", no_argument, 0, 'r' },
{"delete", no_argument, 0, 'D' },
{"delete", no_argument, 0, 'd' },
{"no-notify", no_argument, 0, 'N' },
{"verbose", no_argument, 0, 'v' },
{"version", no_argument, 0, 'V' },
{"help", no_argument, 0, 'h' },
@ -381,8 +402,8 @@ int main(int argc, char** argv)
break;
switch (c) {
case 'd':
activationDir = optarg;
case 'D':
adeptDir = optarg;
break;
case 'l':
list = true;
@ -392,10 +413,13 @@ int main(int argc, char** argv)
returnID = optarg;
actions++;
break;
case 'D':
case 'd':
deleteID = optarg;
actions++;
break;
case 'N':
notify = false;
break;
case 'v':
verbose++;
break;
@ -432,9 +456,9 @@ int main(int argc, char** argv)
{
orig = *files[i];
if (activationDir)
if (adeptDir)
{
std::string path = std::string(activationDir) + std::string("/") + orig;
std::string path = std::string(adeptDir) + std::string("/") + orig;
filename = strdup(path.c_str());
}
else
@ -450,17 +474,17 @@ int main(int argc, char** argv)
if (hasErrors)
{
// In case of activation dir was provided by user
activationDir = 0;
// In case of adept dir was provided by user
adeptDir = 0;
goto end;
}
if (activationDir)
activationDir = strdup(activationDir); // For below free
if (adeptDir)
adeptDir = strdup(adeptDir); // For below free
else
{
activationDir = strdup(deviceFile);
activationDir = dirname(activationDir);
adeptDir = strdup(deviceFile);
adeptDir = dirname(adeptDir);
}
ret = loanMGT.run();
@ -472,8 +496,8 @@ end:
free((void*)*files[i]);
}
if (activationDir)
free(activationDir);
if (adeptDir)
free(adeptDir);
return ret;
}

View file

@ -27,6 +27,7 @@
*/
#include <getopt.h>
#include <libgen.h>
#include <iostream>
@ -80,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;
}
@ -102,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());
@ -141,22 +141,30 @@ public:
static void usage(const char* cmd)
{
std::cout << "Remove ADEPT DRM (from Adobe) of EPUB/PDF file" << std::endl;
std::cout << basename((char*)cmd) << " remove ADEPT DRM (from Adobe) of EPUB/PDF file" << std::endl << std::endl;
std::cout << "Usage: " << cmd << " [(-d|--device-file) device.xml] [(-a|--activation-file) activation.xml] [(-k|--device-key-file) devicesalt] [(-O|--output-dir) dir] [(-o|--output-file) output(.epub|.pdf|.der)] [(-v|--verbose)] [(-h|--help)] (-f|--input-file) file(.epub|pdf)" << std::endl << std::endl;
std::cout << "Usage: " << basename((char*)cmd) << " [OPTIONS] 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 << "Global Options:" << 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;
std::cout << " " << "-h|--help" << "\t\t" << "This help" << std::endl;
std::cout << "ADEPT Options:" << std::endl;
std::cout << " " << "-D|--adept-directory" << "\t" << ".adept directory that must contains device.xml, activation.xml and devicesalt" << 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 << std::endl;
std::cout << "Environment:" << 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 << " * $ADEPT_DIR environment variable" << std::endl;
std::cout << " * /home/<user>/.config/adept" << std::endl;
std::cout << " * Current directory" << std::endl;
std::cout << " * .adept" << std::endl;
std::cout << " * adobe-digital-editions directory" << std::endl;
@ -169,10 +177,12 @@ int main(int argc, char** argv)
const char** files[] = {&devicekeyFile, &deviceFile, &activationFile};
int verbose = gourou::DRMProcessor::getLogLevel();
std::string _deviceFile, _activationFile, _devicekeyFile;
while (1) {
int option_index = 0;
static struct option long_options[] = {
{"adept-directory", required_argument, 0, 'D' },
{"device-file", required_argument, 0, 'd' },
{"activation-file", required_argument, 0, 'a' },
{"device-key-file", required_argument, 0, 'k' },
@ -186,12 +196,20 @@ int main(int argc, char** argv)
{0, 0, 0, 0 }
};
c = getopt_long(argc, argv, "d:a:k:O:o:f:K:vVh",
c = getopt_long(argc, argv, "D:d:a:k:O:o:f:K:vVh",
long_options, &option_index);
if (c == -1)
break;
switch (c) {
case 'D':
_deviceFile = std::string(optarg) + "/device.xml";
_activationFile = std::string(optarg) + "/activation.xml";
_devicekeyFile = std::string(optarg) + "/devicesalt";
deviceFile = _deviceFile.c_str();
activationFile = _activationFile.c_str();
devicekeyFile = _devicekeyFile.c_str();
break;
case 'd':
deviceFile = optarg;
break;
@ -230,6 +248,9 @@ int main(int argc, char** argv)
gourou::DRMProcessor::setLogLevel(verbose);
if (optind == argc-1)
inputFile = argv[optind];
if (!inputFile || (outputDir && !outputDir[0]) ||
(outputFile && !outputFile[0]))
{
@ -237,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

@ -30,6 +30,9 @@
#include <algorithm>
#include <cctype>
#include <locale>
#include <stdlib.h>
#define OPENSSL_NO_DEPRECATED 1
#include <openssl/rand.h>
#include <openssl/pkcs12.h>
@ -44,21 +47,44 @@
#include <zip.h>
#include <libgourou_common.h>
#include <libgourou_log.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
strcpy(cookiejar, "C:\\temp\\libgourou_cookie_jar_XXXXXX");
#else
strcpy(cookiejar, "/tmp/libgourou_cookie_jar_XXXXXX");
#endif
int fd = mkstemp(cookiejar);
if (fd >= 0)
close(fd);
else
{
EXCEPTION(gourou::CLIENT_FILE_ERROR, "mkstemp error");
}
}
DRMProcessorClientImpl::~DRMProcessorClientImpl()
@ -70,6 +96,8 @@ DRMProcessorClientImpl::~DRMProcessorClientImpl()
if (deflt)
OSSL_PROVIDER_unload(deflt);
#endif
unlink(cookiejar);
}
/* Digest interface */
@ -81,32 +109,32 @@ void* DRMProcessorClientImpl::createDigest(const std::string& digestName)
if (EVP_DigestInit(md_ctx, md) != 1)
{
EVP_MD_CTX_free(md_ctx);
return 0;
EXCEPTION(gourou::CLIENT_DIGEST_ERROR, ERR_error_string(ERR_get_error(), NULL));
}
return md_ctx;
}
int DRMProcessorClientImpl::digestUpdate(void* handler, unsigned char* data, unsigned int length)
void DRMProcessorClientImpl::digestUpdate(void* handler, unsigned char* data, unsigned int length)
{
return (EVP_DigestUpdate((EVP_MD_CTX *)handler, data, length)) ? 0 : -1;
if (EVP_DigestUpdate((EVP_MD_CTX *)handler, data, length) != 1)
EXCEPTION(gourou::CLIENT_DIGEST_ERROR, ERR_error_string(ERR_get_error(), NULL));
}
int DRMProcessorClientImpl::digestFinalize(void* handler, unsigned char* digestOut)
void DRMProcessorClientImpl::digestFinalize(void* handler, unsigned char* digestOut)
{
int res = EVP_DigestFinal((EVP_MD_CTX *)handler, digestOut, NULL);
EVP_MD_CTX_free((EVP_MD_CTX *)handler);
return (res == 1) ? 0 : -1;
if (res <= 0)
EXCEPTION(gourou::CLIENT_DIGEST_ERROR, ERR_error_string(ERR_get_error(), NULL));
}
int DRMProcessorClientImpl::digest(const std::string& digestName, unsigned char* data, unsigned int length, unsigned char* digestOut)
void DRMProcessorClientImpl::digest(const std::string& digestName, unsigned char* data, unsigned int length, unsigned char* digestOut)
{
void* handler = createDigest(digestName);
if (!handler)
return -1;
if (digestUpdate(handler, data, length))
return -1;
return digestFinalize(handler, digestOut);
digestUpdate(handler, data, length);
digestFinalize(handler, digestOut);
}
/* Random interface */
@ -119,18 +147,23 @@ 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)
{
// For "big" files only
if (dltotal >= DISPLAY_THRESHOLD && gourou::logLevel >= gourou::WARN)
if (dltotal >= DISPLAY_THRESHOLD && gourou::logLevel >= gourou::LG_LOG_WARN)
{
int percent = 0;
if (dltotal)
percent = (dlnow * 100) / dltotal;
if (lastPercent != percent)
{
std::cout << "\rDownload " << percent << "%" << std::flush;
lastPercent = percent;
}
}
return 0;
@ -174,7 +207,7 @@ static size_t curlHeaders(char *buffer, size_t size, size_t nitems, void *userda
(*responseHeaders)[key] = value;
if (gourou::logLevel >= gourou::DEBUG)
if (gourou::logLevel >= gourou::LG_LOG_DEBUG)
std::cout << key << " : " << value << std::endl;
}
@ -189,10 +222,10 @@ std::string DRMProcessorClientImpl::sendHTTPRequest(const std::string& URL, cons
if (!responseHeaders)
responseHeaders = &localHeaders;
GOUROU_LOG(gourou::INFO, "Send request to " << URL);
GOUROU_LOG(INFO, "Send request to " << URL);
if (POSTData.size())
{
GOUROU_LOG(gourou::DEBUG, "<<< " << std::endl << POSTData);
GOUROU_LOG(DEBUG, "<<< " << std::endl << POSTData);
}
unsigned prevDownloadedBytes;
@ -202,11 +235,11 @@ std::string DRMProcessorClientImpl::sendHTTPRequest(const std::string& URL, cons
struct stat _stat;
if (!fstat(fd, &_stat))
{
GOUROU_LOG(gourou::WARN, "Resume download @ " << _stat.st_size << " bytes");
GOUROU_LOG(WARN, "Resume download @ " << _stat.st_size << " bytes");
downloadedBytes = _stat.st_size;
}
else
GOUROU_LOG(gourou::WARN, "Want to resume, but fstat failed");
GOUROU_LOG(WARN, "Want to resume, but fstat failed");
}
CURL *curl = curl_easy_init();
@ -226,6 +259,7 @@ std::string DRMProcessorClientImpl::sendHTTPRequest(const std::string& URL, cons
}
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, list);
curl_easy_setopt(curl, CURLOPT_COOKIEJAR, cookiejar);
if (POSTData.size())
{
@ -250,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++)
{
@ -262,7 +297,7 @@ std::string DRMProcessorClientImpl::sendHTTPRequest(const std::string& URL, cons
// Connexion failed, wait & retry
if (res == CURLE_COULDNT_CONNECT)
{
GOUROU_LOG(gourou::WARN, "\nConnection failed, attempt " << (i+1) << "/" << HTTP_REQ_MAX_RETRY);
GOUROU_LOG(WARN, "\nConnection failed, attempt " << (i+1) << "/" << HTTP_REQ_MAX_RETRY);
}
// Transfer failed but some data has been received
// --> try again without incrementing tries
@ -270,11 +305,11 @@ std::string DRMProcessorClientImpl::sendHTTPRequest(const std::string& URL, cons
{
if (prevDownloadedBytes != downloadedBytes)
{
GOUROU_LOG(gourou::WARN, "\nConnection broken, but data received, try again");
GOUROU_LOG(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);
GOUROU_LOG(WARN, "\nConnection broken and no data received, attempt " << (i+1) << "/" << HTTP_REQ_MAX_RETRY);
}
// Other error --> fail
else
@ -285,57 +320,120 @@ 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));
if (http_code >= 400)
EXCEPTION(gourou::CLIENT_HTTP_ERROR, "HTTP Error code " << http_code);
if ((downloadedBytes >= DISPLAY_THRESHOLD || replyData.size() >= DISPLAY_THRESHOLD) &&
gourou::logLevel >= gourou::WARN)
gourou::logLevel >= gourou::LG_LOG_WARN)
std::cout << std::endl;
if ((*responseHeaders)["Content-Type"] == "application/vnd.adobe.adept+xml")
{
GOUROU_LOG(gourou::DEBUG, ">>> " << std::endl << replyData.data());
GOUROU_LOG(DEBUG, ">>> " << std::endl << replyData.data());
}
return std::string((char*)replyData.data(), replyData.length());
}
void DRMProcessorClientImpl::padWithPKCS1(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 Padding is :
0x00 0x01 0xff * n 0x00 dataIn
*/
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);
}
void DRMProcessorClientImpl::RSAPrivateEncrypt(const unsigned char* RSAKey, unsigned int RSAKeyLength,
const RSA_KEY_TYPE keyType, const std::string& password,
const unsigned char* data, unsigned dataLength,
unsigned char* res)
{
PKCS12 * pkcs12;
EVP_PKEY* pkey;
X509* cert;
STACK_OF(X509)* ca;
RSA * rsa;
EVP_PKEY_CTX *ctx;
EVP_PKEY* pkey = NULL;
size_t outlen;
unsigned char* tmp;
int ret;
pkcs12 = d2i_PKCS12(NULL, &RSAKey, RSAKeyLength);
if (!pkcs12)
EXCEPTION(gourou::CLIENT_INVALID_PKCS12, ERR_error_string(ERR_get_error(), NULL));
PKCS12_parse(pkcs12, password.c_str(), &pkey, &cert, &ca);
if (!pkey)
if (PKCS12_parse(pkcs12, password.c_str(), &pkey, NULL, NULL) <= 0)
EXCEPTION(gourou::CLIENT_INVALID_PKCS12, ERR_error_string(ERR_get_error(), NULL));
rsa = EVP_PKEY_get1_RSA(pkey);
outlen = EVP_PKEY_get_size(pkey);
int ret = RSA_private_encrypt(dataLength, data, res, rsa, RSA_PKCS1_PADDING);
ctx = EVP_PKEY_CTX_new(pkey, NULL);
if (ret < 0)
/* Use RSA private key */
if (EVP_PKEY_decrypt_init(ctx) <= 0)
EXCEPTION(gourou::CLIENT_RSA_ERROR, ERR_error_string(ERR_get_error(), NULL));
if (gourou::logLevel >= gourou::DEBUG)
{
printf("Encrypted : ");
for(int i=0; i<ret; i++)
printf("%02x ", res[i]);
printf("\n");
}
if (EVP_PKEY_CTX_set_rsa_padding(ctx, RSA_NO_PADDING) <= 0)
EXCEPTION(gourou::CLIENT_RSA_ERROR, ERR_error_string(ERR_get_error(), NULL));
tmp = (unsigned char*)malloc(outlen);
/* PKCS1 functions are no more exported */
padWithPKCS1(tmp, outlen, data, dataLength);
ret = EVP_PKEY_decrypt(ctx, res, &outlen, tmp, outlen);
EVP_PKEY_CTX_free(ctx);
free(tmp);
if (ret <= 0)
EXCEPTION(gourou::CLIENT_RSA_ERROR, ERR_error_string(ERR_get_error(), NULL));
}
void DRMProcessorClientImpl::RSAPrivateDecrypt(const unsigned char* RSAKey, unsigned int RSAKeyLength,
@ -349,24 +447,30 @@ void DRMProcessorClientImpl::RSAPrivateDecrypt(const unsigned char* RSAKey, unsi
if (!p8inf)
EXCEPTION(gourou::CLIENT_INVALID_PKCS8, ERR_error_string(ERR_get_error(), NULL));
EVP_PKEY_CTX *ctx;
EVP_PKEY* pkey = EVP_PKCS82PKEY(p8inf);
RSA * rsa;
size_t outlen = dataLength;
int ret;
rsa = EVP_PKEY_get1_RSA(pkey);
if (!pkey)
EXCEPTION(gourou::CLIENT_INVALID_PKCS8, ERR_error_string(ERR_get_error(), NULL));
ret = RSA_private_decrypt(dataLength, data, res, rsa, RSA_NO_PADDING);
ctx = EVP_PKEY_CTX_new(pkey, NULL);
if (ret < 0)
if (EVP_PKEY_decrypt_init(ctx) <= 0)
EXCEPTION(gourou::CLIENT_RSA_ERROR, ERR_error_string(ERR_get_error(), NULL));
if (gourou::logLevel >= gourou::DEBUG)
{
printf("Decrypted : ");
for(int i=0; i<ret; i++)
printf("%02x ", res[i]);
printf("\n");
}
if (EVP_PKEY_CTX_set_rsa_padding(ctx, RSA_NO_PADDING) <= 0)
EXCEPTION(gourou::CLIENT_RSA_ERROR, ERR_error_string(ERR_get_error(), NULL));
ret = EVP_PKEY_decrypt(ctx, res, &outlen, data, dataLength);
PKCS8_PRIV_KEY_INFO_free(p8inf);
EVP_PKEY_CTX_free(ctx);
BIO_free(mem);
if (ret <= 0)
EXCEPTION(gourou::CLIENT_RSA_ERROR, ERR_error_string(ERR_get_error(), NULL));
}
void DRMProcessorClientImpl::RSAPublicEncrypt(const unsigned char* RSAKey, unsigned int RSAKeyLength,
@ -374,18 +478,44 @@ void DRMProcessorClientImpl::RSAPublicEncrypt(const unsigned char* RSAKey, unsig
const unsigned char* data, unsigned dataLength,
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 * evpKey = X509_get_pubkey(x509);
RSA* rsa = EVP_PKEY_get1_RSA(evpKey);
EVP_PKEY_free(evpKey);
EVP_PKEY_CTX *ctx;
EVP_PKEY * pkey = X509_get_pubkey(x509);
if (!rsa)
EXCEPTION(gourou::CLIENT_NO_PRIV_KEY, "No private key in certificate");
if (!pkey)
EXCEPTION(gourou::CLIENT_NO_PUB_KEY, "No public key in certificate");
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_NO_PADDING) <= 0)
EXCEPTION(gourou::CLIENT_RSA_ERROR, ERR_error_string(ERR_get_error(), NULL));
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);
int ret = RSA_public_encrypt(dataLength, data, res, rsa, RSA_PKCS1_PADDING);
if (ret < 0)
EXCEPTION(gourou::CLIENT_RSA_ERROR, ERR_error_string(ERR_get_error(), NULL));
}
@ -393,42 +523,45 @@ void DRMProcessorClientImpl::RSAPublicEncrypt(const unsigned char* RSAKey, unsig
void* DRMProcessorClientImpl::generateRSAKey(int keyLengthBits)
{
BIGNUM * bn = BN_new();
RSA * rsa = RSA_new();
EVP_PKEY_CTX* ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_RSA, NULL);
EVP_PKEY *key = NULL;
BN_set_word(bn, 0x10001);
RSA_generate_key_ex(rsa, keyLengthBits, bn, 0);
EVP_PKEY_keygen_init(ctx);
EVP_PKEY_CTX_set_rsa_keygen_bits(ctx, keyLengthBits);
EVP_PKEY_CTX_set1_rsa_keygen_pubexp(ctx, bn);
EVP_PKEY_keygen(ctx, &key);
EVP_PKEY_CTX_free(ctx);
BN_free(bn);
return rsa;
return key;
}
void DRMProcessorClientImpl::destroyRSAHandler(void* handler)
{
RSA_free((RSA*)handler);
free(handler);
}
void DRMProcessorClientImpl::extractRSAPublicKey(void* handler, unsigned char** keyOut, unsigned int* keyOutLength)
{
EVP_PKEY * evpKey = EVP_PKEY_new();
EVP_PKEY_set1_RSA(evpKey, (RSA*)handler);
X509_PUBKEY *x509_pubkey = 0;
X509_PUBKEY_set(&x509_pubkey, evpKey);
X509_PUBKEY_set(&x509_pubkey, (EVP_PKEY*)handler);
*keyOutLength = i2d_X509_PUBKEY(x509_pubkey, keyOut);
X509_PUBKEY_free(x509_pubkey);
EVP_PKEY_free(evpKey);
}
void DRMProcessorClientImpl::extractRSAPrivateKey(void* handler, unsigned char** keyOut, unsigned int* keyOutLength)
{
EVP_PKEY * evpKey = EVP_PKEY_new();
EVP_PKEY_set1_RSA(evpKey, (RSA*)handler);
PKCS8_PRIV_KEY_INFO * privKey = EVP_PKEY2PKCS8(evpKey);
PKCS8_PRIV_KEY_INFO * privKey = EVP_PKEY2PKCS8((EVP_PKEY*)handler);
*keyOutLength = i2d_PKCS8_PRIV_KEY_INFO(privKey, keyOut);
PKCS8_PRIV_KEY_INFO_free(privKey);
EVP_PKEY_free(evpKey);
}
void DRMProcessorClientImpl::extractCertificate(const unsigned char* RSAKey, unsigned int RSAKeyLength,
@ -438,12 +571,11 @@ void DRMProcessorClientImpl::extractCertificate(const unsigned char* RSAKey, uns
PKCS12 * pkcs12;
EVP_PKEY* pkey = 0;
X509* cert = 0;
STACK_OF(X509)* ca;
pkcs12 = d2i_PKCS12(NULL, &RSAKey, RSAKeyLength);
if (!pkcs12)
EXCEPTION(gourou::CLIENT_INVALID_PKCS12, ERR_error_string(ERR_get_error(), NULL));
PKCS12_parse(pkcs12, password.c_str(), &pkey, &cert, &ca);
PKCS12_parse(pkcs12, password.c_str(), &pkey, &cert, NULL);
if (!cert)
EXCEPTION(gourou::CLIENT_INVALID_PKCS12, ERR_error_string(ERR_get_error(), NULL));
@ -454,24 +586,27 @@ void DRMProcessorClientImpl::extractCertificate(const unsigned char* RSAKey, uns
}
/* Crypto interface */
void DRMProcessorClientImpl::Encrypt(CRYPTO_ALGO algo, CHAINING_MODE chaining,
void DRMProcessorClientImpl::encrypt(CRYPTO_ALGO algo, CHAINING_MODE chaining,
const unsigned char* key, unsigned int keyLength,
const unsigned char* iv, unsigned int ivLength,
const unsigned char* dataIn, unsigned int dataInLength,
unsigned char* dataOut, unsigned int* dataOutLength)
{
void* handler = EncryptInit(algo, chaining, key, keyLength, iv, ivLength);
EncryptUpdate(handler, dataIn, dataInLength, dataOut, dataOutLength);
EncryptFinalize(handler, dataOut+*dataOutLength, dataOutLength);
void* handler = encryptInit(algo, chaining, key, keyLength, iv, ivLength);
encryptUpdate(handler, dataIn, dataInLength, dataOut, dataOutLength);
encryptFinalize(handler, dataOut+*dataOutLength, dataOutLength);
}
void* DRMProcessorClientImpl::EncryptInit(CRYPTO_ALGO algo, CHAINING_MODE chaining,
void* DRMProcessorClientImpl::encryptInit(CRYPTO_ALGO algo, CHAINING_MODE chaining,
const unsigned char* key, unsigned int keyLength,
const unsigned char* iv, unsigned int ivLength)
{
EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new();
int ret = 0;
if (algo == ALGO_AES)
switch (algo)
{
case ALGO_AES:
{
switch(keyLength)
{
@ -479,10 +614,10 @@ void* DRMProcessorClientImpl::EncryptInit(CRYPTO_ALGO algo, CHAINING_MODE chaini
switch(chaining)
{
case CHAIN_ECB:
EVP_EncryptInit(ctx, EVP_aes_128_ecb(), key, iv);
ret = EVP_EncryptInit(ctx, EVP_aes_128_ecb(), key, iv);
break;
case CHAIN_CBC:
EVP_EncryptInit(ctx, EVP_aes_128_cbc(), key, iv);
ret = EVP_EncryptInit(ctx, EVP_aes_128_cbc(), key, iv);
break;
default:
EXCEPTION(gourou::CLIENT_BAD_CHAINING, "Unknown chaining mode " << chaining);
@ -492,26 +627,39 @@ void* DRMProcessorClientImpl::EncryptInit(CRYPTO_ALGO algo, CHAINING_MODE chaini
EVP_CIPHER_CTX_free(ctx);
EXCEPTION(gourou::CLIENT_BAD_KEY_SIZE, "Invalid key size " << keyLength);
}
break;
}
else if (algo == ALGO_RC4)
case 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);
ret = EVP_DecryptInit(ctx, EVP_rc4(), key, iv);
break;
}
}
if (ret <= 0)
{
EVP_CIPHER_CTX_free(ctx);
EXCEPTION(gourou::CLIENT_CRYPT_ERROR, ERR_error_string(ERR_get_error(), NULL));
}
return ctx;
}
void* DRMProcessorClientImpl::DecryptInit(CRYPTO_ALGO algo, CHAINING_MODE chaining,
void* DRMProcessorClientImpl::decryptInit(CRYPTO_ALGO algo, CHAINING_MODE chaining,
const unsigned char* key, unsigned int keyLength,
const unsigned char* iv, unsigned int ivLength)
{
EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new();
int ret = 0;
if (algo == ALGO_AES)
switch(algo)
{
case ALGO_AES:
{
switch(keyLength)
{
@ -519,10 +667,10 @@ void* DRMProcessorClientImpl::DecryptInit(CRYPTO_ALGO algo, CHAINING_MODE chaini
switch(chaining)
{
case CHAIN_ECB:
EVP_DecryptInit(ctx, EVP_aes_128_ecb(), key, iv);
ret = EVP_DecryptInit(ctx, EVP_aes_128_ecb(), key, iv);
break;
case CHAIN_CBC:
EVP_DecryptInit(ctx, EVP_aes_128_cbc(), key, iv);
ret = EVP_DecryptInit(ctx, EVP_aes_128_cbc(), key, iv);
break;
default:
EXCEPTION(gourou::CLIENT_BAD_CHAINING, "Unknown chaining mode " << chaining);
@ -532,58 +680,81 @@ void* DRMProcessorClientImpl::DecryptInit(CRYPTO_ALGO algo, CHAINING_MODE chaini
EVP_CIPHER_CTX_free(ctx);
EXCEPTION(gourou::CLIENT_BAD_KEY_SIZE, "Invalid key size " << keyLength);
}
break;
}
else if (algo == ALGO_RC4)
case 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);
ret = EVP_DecryptInit(ctx, EVP_rc4(), key, iv);
break;
}
}
if (ret <= 0)
{
EVP_CIPHER_CTX_free(ctx);
EXCEPTION(gourou::CLIENT_CRYPT_ERROR, ERR_error_string(ERR_get_error(), NULL));
}
return ctx;
}
void DRMProcessorClientImpl::EncryptUpdate(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)
{
EVP_EncryptUpdate((EVP_CIPHER_CTX*)handler, dataOut, (int*)dataOutLength, dataIn, dataInLength);
int ret = EVP_EncryptUpdate((EVP_CIPHER_CTX*)handler, dataOut, (int*)dataOutLength, dataIn, dataInLength);
if (ret <= 0)
EXCEPTION(gourou::CLIENT_CRYPT_ERROR, ERR_error_string(ERR_get_error(), NULL));
}
void DRMProcessorClientImpl::EncryptFinalize(void* handler,
void DRMProcessorClientImpl::encryptFinalize(void* handler,
unsigned char* dataOut, unsigned int* dataOutLength)
{
int len;
EVP_EncryptFinal_ex((EVP_CIPHER_CTX*)handler, dataOut, &len);
int len, ret;
ret = EVP_EncryptFinal_ex((EVP_CIPHER_CTX*)handler, dataOut, &len);
*dataOutLength += len;
EVP_CIPHER_CTX_free((EVP_CIPHER_CTX*)handler);
if (ret <= 0)
EXCEPTION(gourou::CLIENT_CRYPT_ERROR, ERR_error_string(ERR_get_error(), NULL));
}
void DRMProcessorClientImpl::Decrypt(CRYPTO_ALGO algo, CHAINING_MODE chaining,
void DRMProcessorClientImpl::decrypt(CRYPTO_ALGO algo, CHAINING_MODE chaining,
const unsigned char* key, unsigned int keyLength,
const unsigned char* iv, unsigned int ivLength,
const unsigned char* dataIn, unsigned int dataInLength,
unsigned char* dataOut, unsigned int* dataOutLength)
{
void* handler = DecryptInit(algo, chaining, key, keyLength, iv, ivLength);
DecryptUpdate(handler, dataIn, dataInLength, dataOut, dataOutLength);
DecryptFinalize(handler, dataOut+*dataOutLength, dataOutLength);
void* handler = decryptInit(algo, chaining, key, keyLength, iv, ivLength);
decryptUpdate(handler, dataIn, dataInLength, dataOut, dataOutLength);
decryptFinalize(handler, dataOut+*dataOutLength, dataOutLength);
}
void DRMProcessorClientImpl::DecryptUpdate(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)
{
EVP_DecryptUpdate((EVP_CIPHER_CTX*)handler, dataOut, (int*)dataOutLength, dataIn, dataInLength);
int ret = EVP_DecryptUpdate((EVP_CIPHER_CTX*)handler, dataOut, (int*)dataOutLength, dataIn, dataInLength);
if (ret <= 0)
EXCEPTION(gourou::CLIENT_CRYPT_ERROR, ERR_error_string(ERR_get_error(), NULL));
}
void DRMProcessorClientImpl::DecryptFinalize(void* handler, unsigned char* dataOut, unsigned int* dataOutLength)
void DRMProcessorClientImpl::decryptFinalize(void* handler, unsigned char* dataOut, unsigned int* dataOutLength)
{
int len;
EVP_DecryptFinal_ex((EVP_CIPHER_CTX*)handler, dataOut, &len);
int len, ret;
ret = EVP_DecryptFinal_ex((EVP_CIPHER_CTX*)handler, dataOut, &len);
*dataOutLength += len;
EVP_CIPHER_CTX_free((EVP_CIPHER_CTX*)handler);
if (ret <= 0)
EXCEPTION(gourou::CLIENT_CRYPT_ERROR, ERR_error_string(ERR_get_error(), NULL));
}
void* DRMProcessorClientImpl::zipOpen(const std::string& path)

View file

@ -45,9 +45,9 @@ public:
/* Digest interface */
virtual void* createDigest(const std::string& digestName);
virtual int digestUpdate(void* handler, unsigned char* data, unsigned int length);
virtual int digestFinalize(void* handler,unsigned char* digestOut);
virtual int digest(const std::string& digestName, unsigned char* data, unsigned int length, unsigned char* digestOut);
virtual void digestUpdate(void* handler, unsigned char* data, unsigned int length);
virtual void digestFinalize(void* handler,unsigned char* digestOut);
virtual void digest(const std::string& digestName, unsigned char* data, unsigned int length, unsigned char* digestOut);
/* Random interface */
virtual void randBytes(unsigned char* bytesOut, unsigned int length);
@ -80,34 +80,34 @@ public:
unsigned char** certOut, unsigned int* certOutLength);
/* Crypto interface */
virtual void Encrypt(CRYPTO_ALGO algo, CHAINING_MODE chaining,
virtual void encrypt(CRYPTO_ALGO algo, CHAINING_MODE chaining,
const unsigned char* key, unsigned int keyLength,
const unsigned char* iv, unsigned int ivLength,
const unsigned char* dataIn, unsigned int dataInLength,
unsigned char* dataOut, unsigned int* dataOutLength);
virtual void* EncryptInit(CRYPTO_ALGO algo, CHAINING_MODE chaining,
virtual void* encryptInit(CRYPTO_ALGO algo, CHAINING_MODE chaining,
const unsigned char* key, unsigned int keyLength,
const unsigned char* iv=0, unsigned int ivLength=0);
virtual void EncryptUpdate(void* handler, const unsigned char* dataIn, unsigned int dataInLength,
virtual void encryptUpdate(void* handler, const unsigned char* dataIn, unsigned int dataInLength,
unsigned char* dataOut, unsigned int* dataOutLength);
virtual void EncryptFinalize(void* handler, unsigned char* dataOut, unsigned int* dataOutLength);
virtual void encryptFinalize(void* handler, unsigned char* dataOut, unsigned int* dataOutLength);
virtual void Decrypt(CRYPTO_ALGO algo, CHAINING_MODE chaining,
virtual void decrypt(CRYPTO_ALGO algo, CHAINING_MODE chaining,
const unsigned char* key, unsigned int keyLength,
const unsigned char* iv, unsigned int ivLength,
const unsigned char* dataIn, unsigned int dataInLength,
unsigned char* dataOut, unsigned int* dataOutLength);
virtual void* DecryptInit(CRYPTO_ALGO algo, CHAINING_MODE chaining,
virtual void* decryptInit(CRYPTO_ALGO algo, CHAINING_MODE chaining,
const unsigned char* key, unsigned int keyLength,
const unsigned char* iv=0, unsigned int ivLength=0);
virtual void DecryptUpdate(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);
virtual void DecryptFinalize(void* handler, unsigned char* dataOut, unsigned int* dataOutLength);
virtual void decryptFinalize(void* handler, unsigned char* dataOut, unsigned int* dataOutLength);
/* ZIP Interface */
virtual void* zipOpen(const std::string& path);
@ -127,11 +127,19 @@ public:
int wbits=-15, int compressionLevel=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;
#else
void *legacy, *deflt;
#endif
char cookiejar[64];
};
#endif

40
utils/launcher.cpp Normal file
View file

@ -0,0 +1,40 @@
#include <iostream>
#include <unistd.h>
#include <libgen.h>
#include <string.h>
#include "utils_common.h"
#ifndef DEFAULT_UTIL
#define DEFAULT_UTIL "acsmdownloader"
#endif
/* Inspired from https://discourse.appimage.org/t/call-alternative-binary-from-appimage/93/10*/
int main(int argc, char** argv)
{
char* util, *argv0;
char* mountPoint = getenv("APPDIR");
std::string fullPath;
/* Original command is in ARGV0 env variable*/
argv0 = strdup(getenv("ARGV0"));
util = basename(argv0);
fullPath = std::string(mountPoint) + util;
if (std::string(util) == "launcher" || !pathExists(fullPath.c_str()))
fullPath = std::string(mountPoint) + DEFAULT_UTIL;
free(argv0);
argv[0] = strdup(fullPath.c_str());
if (execvp(argv[0], argv))
std::cout << "Unable to launch '" << argv[0] << "'" << std::endl;
/* Should not happens */
free(argv[0]);
return 0;
}

View file

@ -0,0 +1,61 @@
.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.49.3.
.TH ACSMDOWNLOADER "1" "January 2023" "acsmdownloader download EPUB file from ACSM request file" "User Commands"
.SH NAME
acsmdownloader \- download EPUB file from ACSM request file
.SH SYNOPSIS
.B acsmdownloader
[\fI\,OPTIONS\/\fR] \fI\,file.acsm\/\fR
.SH DESCRIPTION
Download EPUB file from ACSM request file
.SS "Global Options:"
.TP
\fB\-O\fR|\-\-output\-dir
Optional output directory were to put result (default ./)
.TP
\fB\-o\fR|\-\-output\-file
Optional output filename (default <title.(epub|pdf|der)>)
.TP
\fB\-f\fR|\-\-acsm\-file
Backward compatibility: ACSM request file for epub download
.TP
\fB\-e\fR|\-\-export\-private\-key
Export private key in DER format
.TP
\fB\-r\fR|\-\-resume
Try to resume download (in case of previous failure)
.TP
\fB\-v\fR|\-\-verbose
Increase verbosity, can be set multiple times
.TP
\fB\-V\fR|\-\-version
Display libgourou version
.TP
\fB\-h\fR|\-\-help
This help
.SS "ADEPT Options:"
.TP
\fB\-D\fR|\-\-adept\-directory
\&.adept directory that must contains device.xml, activation.xml and devicesalt
.TP
\fB\-d\fR|\-\-device\-file
device.xml file from eReader
.TP
\fB\-a\fR|\-\-activation\-file
activation.xml file from eReader
.TP
\fB\-k\fR|\-\-device\-key\-file
private device key file (eg devicesalt/devkey.bin) from eReader
.SH ENVIRONMENT
Device file, activation file and device key file are optionals. If not set, they are looked into :
.IP
* $ADEPT_DIR environment variable
.IP
* /home/<user>/.config/adept
.IP
* Current directory
.IP
* .adept
.IP
* adobe\-digital\-editions directory
.IP
* .adobe\-digital\-editions directory

View file

@ -0,0 +1,67 @@
.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.49.3.
.TH ADEPT_ACTIVATE "1" "January 2023" "adept_activate create new device files used by ADEPT DRM" "User Commands"
.SH NAME
adept_activate create new device files used by ADEPT DRM
.SH SYNOPSIS
.B adept_activate
\fI\,OPTIONS\/\fR
.SH DESCRIPTION
Create new device files used by ADEPT DRM
.SS "Global Options:"
.TP
\fB\-a\fR|\-\-anonymous
Anonymous account, no need for username/password (Use it only with a DRM removal software)
.TP
\fB\-u\fR|\-\-username
AdobeID username (ie adobe.com email account)
.TP
\fB\-p\fR|\-\-password
AdobeID password (asked if not set via command line)
.TP
\fB\-O\fR|\-\-output\-dir
Optional output directory were to put result (default ./.adept). This directory must not already exists
.TP
\fB\-H\fR|\-\-hobbes\-version
Force RMSDK version to a specific value (default: version of current librmsdk)
.TP
\fB\-r\fR|\-\-random\-serial
Generate a random device serial (if not set, it will be dependent of your current configuration)
.TP
\fB\-v\fR|\-\-verbose
Increase verbosity, can be set multiple times
.TP
\fB\-V\fR|\-\-version
Display libgourou version
.TP
\fB\-h\fR|\-\-help
This help
.PP
Usage: adept_activate OPTIONS
.SS "Global Options:"
.TP
\fB\-a\fR|\-\-anonymous
Anonymous account, no need for username/password (Use it only with a DRM removal software)
.TP
\fB\-u\fR|\-\-username
AdobeID username (ie adobe.com email account)
.TP
\fB\-p\fR|\-\-password
AdobeID password (asked if not set via command line)
.TP
\fB\-O\fR|\-\-output\-dir
Optional output directory were to put result (default ./.adept). This directory must not already exists
.TP
\fB\-H\fR|\-\-hobbes\-version
Force RMSDK version to a specific value (default: version of current librmsdk)
.TP
\fB\-r\fR|\-\-random\-serial
Generate a random device serial (if not set, it will be dependent of your current configuration)
.TP
\fB\-v\fR|\-\-verbose
Increase verbosity, can be set multiple times
.TP
\fB\-V\fR|\-\-version
Display libgourou version
.TP
\fB\-h\fR|\-\-help
This help

View file

@ -0,0 +1,46 @@
.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.49.3.
.TH ADEPT_LOAN_MGT "1" "January 2023" "adept_loan_mgt manage loaned books" "User Commands"
.SH NAME
adept_loan_mgt manage loaned books
.SH SYNOPSIS
.B adept_loan_mgt
[\fI\,OPTIONS\/\fR]
.SH DESCRIPTION
Manage ADEPT loaned books
.SS "Global Options:"
.TP
\fB\-l\fR|\-\-list
List all loaned books
.TP
\fB\-r\fR|\-\-return
Return a loaned book
.TP
\fB\-d\fR|\-\-delete
Delete a loan entry without returning it
.TP
\fB\-v\fR|\-\-verbose
Increase verbosity, can be set multiple times
.TP
\fB\-V\fR|\-\-version
Display libgourou version
.TP
\fB\-h\fR|\-\-help
This help
.SS "ADEPT Options:"
.TP
\fB\-D\fR|\-\-adept\-directory
\&.adept directory that must contains device.xml, activation.xml and devicesalt
.SH ENVIRONMENT
.SS "ADEPT directory is optional. If not set, it's looked into :"
.IP
* $ADEPT_DIR environment variable
.IP
* /home/<user>/.config/adept
.IP
* Current directory
.IP
* .adept
.IP
* adobe\-digital\-editions directory
.IP
* .adobe\-digital\-editions directory

55
utils/man/adept_remove.1 Normal file
View file

@ -0,0 +1,55 @@
.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.49.3.
.TH ADEPT_REMOVE "1" "January 2023" "adept_remove remove ADEPT DRM (from Adobe) of EPUB/PDF file" "User Commands"
.SH NAME
adept_remove remove ADEPT DRM (from Adobe) of EPUB/PDF file
.SH SYNOPSIS
.B adept_remove
[\fI\,OPTIONS\/\fR] \fI\,file(.epub|pdf)\/\fR
.SH DESCRIPTION
Remove ADEPT DRM (from Adobe) of EPUB/PDF file
.SS "Global Options:"
.TP
\fB\-O\fR|\-\-output\-dir
Optional output directory were to put result (default ./)
.TP
\fB\-o\fR|\-\-output\-file
Optional output filename (default inplace DRM removal>)
.TP
\fB\-f\fR|\-\-input\-file
Backward compatibility: EPUB/PDF file to process
.TP
\fB\-v\fR|\-\-verbose
Increase verbosity, can be set multiple times
.TP
\fB\-V\fR|\-\-version
Display libgourou version
.TP
\fB\-h\fR|\-\-help
This help
.SS "ADEPT Options:"
.TP
\fB\-D\fR|\-\-adept\-directory
\&.adept directory that must contains device.xml, activation.xml and devicesalt
.TP
\fB\-d\fR|\-\-device\-file
device.xml file from eReader
.TP
\fB\-a\fR|\-\-activation\-file
activation.xml file from eReader
.TP
\fB\-k\fR|\-\-device\-key\-file
private device key file (eg devicesalt/devkey.bin) from eReader
.SH ENVIRONMENT
.SS "Device file, activation file and device key file are optionals. If not set, they are looked into :"
.IP
* $ADEPT_DIR environment variable
.IP
* /home/<user>/.config/adept
.IP
* Current directory
.IP
* .adept
.IP
* adobe\-digital\-editions directory
.IP
* .adobe\-digital\-editions directory

View file

@ -33,6 +33,7 @@
#include <stdio.h>
#include <fcntl.h>
#include <limits.h>
#include <libgen.h>
#include <iostream>
@ -51,25 +52,39 @@ 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);
}
const char* findFile(const char* filename, bool inDefaultDirs)
{
if (fileExists(filename))
std::string path;
const char* adeptDir = getenv("ADEPT_DIR");
if (adeptDir && adeptDir[0])
{
path = adeptDir + std::string("/") + filename;
if (pathExists(path.c_str()))
return strdup(path.c_str());
}
path = gourou::DRMProcessor::getDefaultAdeptDir() + filename;
if (pathExists(path.c_str()))
return strdup(path.c_str());
if (pathExists(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()))
path = std::string(defaultDirs[i]) + filename;
if (pathExists(path.c_str()))
return strdup(path.c_str());
}
@ -98,8 +113,8 @@ void mkpath(const char *dir)
void fileCopy(const char* in, const char* out)
{
char buffer[4096];
int ret, fdIn, fdOut;
char buffer[4096], *_buffer;
int ret, ret2, fdIn, fdOut;
fdIn = open(in, O_RDONLY);
@ -119,9 +134,31 @@ void fileCopy(const char* in, const char* out)
ret = ::read(fdIn, buffer, sizeof(buffer));
if (ret <= 0)
break;
::write(fdOut, buffer, ret);
do
{
_buffer = buffer;
ret2 = ::write(fdOut, _buffer, ret);
if (ret2 >= 0)
{
ret -= ret2;
_buffer += ret2;
}
else
{
EXCEPTION(gourou::CLIENT_FILE_ERROR, "Error writing " << out);
}
} while (ret);
}
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