mirror of
https://forge.soutade.fr/soutade/libgourou.git
synced 2026-03-27 01:36:58 +00:00
Compare commits
87 commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d7bd98e719 | ||
|
|
72cb22ad2a | ||
|
|
b44b988966 | ||
|
|
76cab18667 | ||
|
|
2387dff2cb | ||
|
|
a7cfd3ef89 | ||
|
|
772abdd2f9 | ||
|
|
1605656c73 | ||
|
|
8f0341d0bd | ||
|
|
8061681705 | ||
|
|
724961566c | ||
|
|
98c511d0ca | ||
|
|
db3e2179a9 | ||
|
|
28aefba6d6 | ||
|
|
e0e2bc7430 | ||
|
|
d3c90f03bb | ||
|
|
469d378f9a | ||
|
|
98b531a232 | ||
|
|
956bad3068 | ||
|
|
d9a920b062 | ||
|
|
204500117d | ||
|
|
ce2cf4192a | ||
|
|
68bf48df27 | ||
|
|
81faf1f9be | ||
|
|
f60abf04d8 | ||
|
|
0d77cf55e1 | ||
|
|
86a79cc381 | ||
|
|
68bf982b6f | ||
|
|
ef8c2644ca | ||
|
|
e05639c09d | ||
|
|
69865e005b | ||
|
|
fd38e84da6 | ||
|
|
92a67312bd | ||
|
|
29d298b373 | ||
|
|
40dcb7a041 | ||
|
|
e0bb1bd4f8 | ||
|
|
9388d82138 | ||
|
|
c19279397f | ||
|
|
bb5349d710 | ||
|
|
9a75213b49 | ||
|
|
e06d20a392 | ||
|
|
a0f6324999 | ||
|
|
c259cbd5a3 | ||
|
|
46afe771c7 | ||
|
|
cad2189fc2 | ||
|
|
50bc16079f | ||
|
|
1213b34250 | ||
|
|
a66dcb959c | ||
|
|
84b01a5de3 | ||
|
|
be78d24236 | ||
|
|
8aec5be244 | ||
|
|
3a0ab4b438 | ||
|
|
891ed05926 | ||
|
|
fc839e671a | ||
|
|
ab5afa5003 | ||
|
|
937c27fc23 | ||
|
|
34216d1b6e | ||
|
|
ffd2004cbb | ||
|
|
c41dd46ca7 | ||
|
|
e4bd73c03d | ||
|
|
f65e8cd9eb | ||
|
|
24bae89095 | ||
|
|
afab1c0012 | ||
|
|
7878f91cdd | ||
|
|
6e3958f09e | ||
|
|
2dbd4cc343 | ||
|
|
e28dc39a68 | ||
|
|
7b8c7acbad | ||
|
|
56b3231f92 | ||
|
|
7084fb7025 | ||
|
|
7f5b787cb9 | ||
|
|
086e9b0610 | ||
|
|
600535d52c | ||
|
|
57c3a58994 | ||
|
|
210b265693 | ||
|
|
33bb983283 | ||
|
|
3c73b8ccb3 | ||
|
|
4acf401031 | ||
|
|
7666d2a241 | ||
|
|
201ec69b11 | ||
|
|
5e018ddbd8 | ||
|
|
81563056e0 | ||
|
|
22880c71c6 | ||
|
|
4f288f4e24 | ||
|
|
3d4e6e3918 | ||
|
|
7b6b1471fe | ||
|
|
4f9b2de5a5 |
36 changed files with 5051 additions and 3742 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -1,5 +1,6 @@
|
|||
obj
|
||||
lib
|
||||
*.o
|
||||
*.a
|
||||
*.so
|
||||
*~
|
||||
|
|
|
|||
86
Makefile
86
Makefile
|
|
@ -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)
|
||||
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
|
||||
|
|
|
|||
87
README.md
87
README.md
|
|
@ -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,65 @@ 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
|
||||
------
|
||||
|
||||
A docker image (by bcliang) is available at [https://github.com/bcliang/docker-libgourou/](https://github.com/bcliang/docker-libgourou/)
|
||||
|
||||
|
||||
Copyright
|
||||
---------
|
||||
|
|
@ -111,7 +144,6 @@ Copyright
|
|||
Grégory Soutadé
|
||||
|
||||
|
||||
|
||||
License
|
||||
-------
|
||||
|
||||
|
|
@ -120,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
133
include/Base64.h
Normal 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_ */
|
||||
|
|
@ -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;}
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
};
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
#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,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
@ -114,7 +116,11 @@ namespace gourou
|
|||
CLIENT_GENERIC_EXCEPTION,
|
||||
CLIENT_NETWORK_ERROR,
|
||||
CLIENT_INVALID_PKCS8,
|
||||
CLIENT_FILE_ERROR
|
||||
CLIENT_FILE_ERROR,
|
||||
CLIENT_OSSL_ERROR,
|
||||
CLIENT_CRYPT_ERROR,
|
||||
CLIENT_DIGEST_ERROR,
|
||||
CLIENT_HTTP_ERROR
|
||||
};
|
||||
|
||||
enum DRM_REMOVAL_ERROR {
|
||||
|
|
@ -124,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
|
||||
*/
|
||||
|
|
@ -139,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());
|
||||
}
|
||||
|
|
@ -147,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);
|
||||
}
|
||||
|
|
@ -163,7 +181,7 @@ namespace gourou
|
|||
|
||||
private:
|
||||
int code, line;
|
||||
const char* message, *file;
|
||||
const char* file;
|
||||
char* fullmessage;
|
||||
};
|
||||
|
||||
|
|
@ -218,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)
|
||||
{
|
||||
|
|
@ -249,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);
|
||||
}
|
||||
|
||||
|
|
@ -288,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
|
||||
*
|
||||
|
|
@ -413,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
|
||||
|
|
|
|||
|
|
@ -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__)
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -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
13
scripts/update_lib.sh
Executable 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
|
||||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
|
||||
|
|
|
|||
|
|
@ -29,12 +29,14 @@ namespace gourou
|
|||
if (!node)
|
||||
EXCEPTION(FFI_INVALID_LOAN_TOKEN, "No loanToken element in document");
|
||||
|
||||
node = doc.select_node("/envelope/loanToken/loan").node();
|
||||
|
||||
if (!node)
|
||||
EXCEPTION(FFI_INVALID_LOAN_TOKEN, "No loanToken/loan element in document");
|
||||
node = doc.select_node("/envelope/fulfillmentResult/fulfillment").node();
|
||||
|
||||
if (node)
|
||||
properties["id"] = node.first_child().value();
|
||||
else
|
||||
{
|
||||
EXCEPTION(FFI_INVALID_LOAN_TOKEN, "No fulfillment element in document");
|
||||
}
|
||||
|
||||
node = doc.select_node("/envelope/loanToken/operatorURL").node();
|
||||
|
||||
|
|
@ -50,6 +52,7 @@ namespace gourou
|
|||
else
|
||||
{
|
||||
node = doc.select_node("/envelope/fulfillmentResult/resourceItemInfo/licenseToken/permissions/play/until").node();
|
||||
|
||||
if (node)
|
||||
properties["validity"] = node.first_child().value();
|
||||
else
|
||||
|
|
|
|||
|
|
@ -1 +0,0 @@
|
|||
../lib/pugixml/src/pugixml.cpp
|
||||
|
|
@ -1,10 +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/
|
||||
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
|
||||
|
|
@ -13,34 +19,39 @@ LDFLAGS += -lgourou
|
|||
endif
|
||||
|
||||
ifneq ($(DEBUG),)
|
||||
CXXFLAGS += -ggdb -O0
|
||||
CXXFLAGS += -ggdb -O0 -DDEBUG
|
||||
else
|
||||
CXXFLAGS += -O2
|
||||
endif
|
||||
|
||||
|
||||
COMMON_DEPS = drmprocessorclientimpl.cpp utils_common.cpp
|
||||
COMMON_OBJECTS = $(COMMON_DEPS:.cpp=.o)
|
||||
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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
|
|
@ -114,16 +114,16 @@ public:
|
|||
// Use temp file for PDF
|
||||
if (type == gourou::DRMProcessor::ITEM_TYPE::PDF)
|
||||
{
|
||||
char* tempFile = tempnam("/tmp", NULL);
|
||||
processor.removeDRM(inputFile, tempFile, type, encryptionKey, encryptionKeySize);
|
||||
std::string tempFile = filename + ".tmp";
|
||||
/* Be sure there is not already a temp file */
|
||||
unlink(tempFile.c_str());
|
||||
processor.removeDRM(filename, tempFile, type, encryptionKey, encryptionKeySize);
|
||||
/* Original file must be removed before doing a copy... */
|
||||
unlink(inputFile);
|
||||
if (!rename(tempFile, filename.c_str()))
|
||||
unlink(filename.c_str());
|
||||
if (rename(tempFile.c_str(), filename.c_str()))
|
||||
{
|
||||
free(tempFile);
|
||||
EXCEPTION(gourou::DRM_FILE_ERROR, "Unable to copy " << tempFile << " into " << filename);
|
||||
}
|
||||
free(tempFile);
|
||||
}
|
||||
else
|
||||
processor.removeDRM(inputFile, filename, type, encryptionKey, encryptionKeySize);
|
||||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -30,11 +30,16 @@
|
|||
#include <algorithm>
|
||||
#include <cctype>
|
||||
#include <locale>
|
||||
#include <stdlib.h>
|
||||
|
||||
#define OPENSSL_NO_DEPRECATED 1
|
||||
|
||||
#include <openssl/rand.h>
|
||||
#include <openssl/pkcs12.h>
|
||||
#include <openssl/evp.h>
|
||||
#include <openssl/err.h>
|
||||
#include <openssl/rsa.h>
|
||||
#include <openssl/bn.h>
|
||||
|
||||
#include <curl/curl.h>
|
||||
|
||||
|
|
@ -42,9 +47,59 @@
|
|||
#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()
|
||||
{
|
||||
#if OPENSSL_VERSION_MAJOR >= 3
|
||||
if (legacy)
|
||||
OSSL_PROVIDER_unload(legacy);
|
||||
|
||||
if (deflt)
|
||||
OSSL_PROVIDER_unload(deflt);
|
||||
#endif
|
||||
|
||||
unlink(cookiejar);
|
||||
}
|
||||
|
||||
/* Digest interface */
|
||||
void* DRMProcessorClientImpl::createDigest(const std::string& digestName)
|
||||
{
|
||||
|
|
@ -54,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 */
|
||||
|
|
@ -92,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;
|
||||
|
|
@ -147,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;
|
||||
}
|
||||
|
||||
|
|
@ -162,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;
|
||||
|
|
@ -175,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();
|
||||
|
|
@ -199,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())
|
||||
{
|
||||
|
|
@ -223,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++)
|
||||
{
|
||||
|
|
@ -235,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
|
||||
|
|
@ -243,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
|
||||
|
|
@ -258,52 +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);
|
||||
rsa = EVP_PKEY_get1_RSA(pkey);
|
||||
|
||||
int ret = RSA_private_encrypt(dataLength, data, res, rsa, RSA_PKCS1_PADDING);
|
||||
if (PKCS12_parse(pkcs12, password.c_str(), &pkey, NULL, NULL) <= 0)
|
||||
EXCEPTION(gourou::CLIENT_INVALID_PKCS12, ERR_error_string(ERR_get_error(), NULL));
|
||||
|
||||
if (ret < 0)
|
||||
outlen = EVP_PKEY_get_size(pkey);
|
||||
|
||||
ctx = EVP_PKEY_CTX_new(pkey, NULL);
|
||||
|
||||
/* 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,
|
||||
|
|
@ -317,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,
|
||||
|
|
@ -342,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));
|
||||
}
|
||||
|
|
@ -361,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,
|
||||
|
|
@ -406,12 +571,14 @@ 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));
|
||||
|
||||
*certOutLength = i2d_X509(cert, certOut);
|
||||
|
||||
|
|
@ -419,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)
|
||||
{
|
||||
|
|
@ -444,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);
|
||||
|
|
@ -457,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)
|
||||
{
|
||||
|
|
@ -484,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);
|
||||
|
|
@ -497,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)
|
||||
|
|
|
|||
|
|
@ -31,16 +31,23 @@
|
|||
|
||||
#include <string>
|
||||
|
||||
#if OPENSSL_VERSION_MAJOR >= 3
|
||||
#include <openssl/provider.h>
|
||||
#endif
|
||||
|
||||
#include <drmprocessorclient.h>
|
||||
|
||||
class DRMProcessorClientImpl : public gourou::DRMProcessorClient
|
||||
{
|
||||
public:
|
||||
DRMProcessorClientImpl();
|
||||
~DRMProcessorClientImpl();
|
||||
|
||||
/* 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);
|
||||
|
|
@ -73,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);
|
||||
|
|
@ -118,6 +125,21 @@ public:
|
|||
|
||||
virtual void deflate(gourou::ByteArray& data, gourou::ByteArray& result,
|
||||
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
40
utils/launcher.cpp
Normal 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;
|
||||
}
|
||||
61
utils/man/acsmdownloader.1
Normal file
61
utils/man/acsmdownloader.1
Normal 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
|
||||
67
utils/man/adept_activate.1
Normal file
67
utils/man/adept_activate.1
Normal 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
|
||||
46
utils/man/adept_loan_mgt.1
Normal file
46
utils/man/adept_loan_mgt.1
Normal 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
55
utils/man/adept_remove.1
Normal 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
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue