Compare commits

...

80 commits

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

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

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

7365279fca
2026-03-14 13:51:40 +01:00
Grégory Soutadé
72cb22ad2a Refresh curl download percent only when updated 2026-02-17 20:07:57 +01:00
Grégory Soutadé
b44b988966 Fix bug in utils with -o and -O options 2026-02-01 08:56:27 +01:00
Grégory Soutadé
76cab18667 Delete all referenced objects deleted by libgourou during PDF DRM removal 2026-01-31 20:31:44 +01:00
Jake Waksbaum
2387dff2cb Add darwin support to Makefile 2026-01-25 10:15:35 +01:00
Jake Waksbaum
a7cfd3ef89 Replace sprintf with snprintf 2026-01-19 15:35:24 -05:00
Jake Waksbaum
772abdd2f9 Remove unused message field 2026-01-19 14:46:49 -05:00
Jake Waksbaum
1605656c73 Exception: fix constructor 2026-01-19 14:45:59 -05:00
Grégory Soutadé
8f0341d0bd Some code formating 2026-01-18 15:44:20 +01:00
Grégory Soutadé
8061681705 Add a warning when ACSM file is expired 2026-01-18 15:22:02 +01:00
Grégory Soutadé
724961566c Update version 2026-01-18 14:35:18 +01:00
Grégory Soutadé
98c511d0ca Fix mkstemp unused result warning 2026-01-18 14:31:52 +01:00
Grégory Soutadé
db3e2179a9 Add PKCS1 Type 2 padding for Signing request 2026-01-18 14:31:52 +01:00
Grégory Soutadé
28aefba6d6 Update README.md 2025-11-17 22:29:35 +01:00
Grégory Soutadé
e0e2bc7430 Update uPDFParser git address 2025-08-25 20:37:52 +02:00
Grégory Soutadé
d3c90f03bb Update README.md 2025-04-21 08:59:51 +02:00
Grégory Soutadé
469d378f9a Update version to 0.8.7 2025-02-16 21:32:51 +01:00
Philipp Pagel
98b531a232 Fix setup.sh
fix path for updfparser
2025-02-11 18:12:36 +01:00
Grégory Soutadé
956bad3068 Update README.md 2024-12-31 13:21:14 +01:00
Ismail Dönmez
d9a920b062 Use $HOME variable if it exists
Signed-off-by: Ismail Dönmez <ismail@i10z.com>
2024-09-22 10:55:54 +02:00
Grégory Soutadé
204500117d Typo fix 2024-09-19 08:41:15 +02:00
Grégory Soutadé
ce2cf4192a Update README.md 2024-08-31 08:27:53 +02:00
Grégory Soutadé
68bf48df27 Update README.md 2024-08-27 21:48:39 +02:00
Grégory Soutadé
81faf1f9be adept_loan_mgt: manage cases were name is empty 2024-04-15 07:37:28 +02:00
nicokosi
f60abf04d8 Fix typos in README.md 2024-04-11 06:05:23 +02:00
Grégory Soutadé
0d77cf55e1 Update version 2024-03-28 21:58:07 +01:00
Grégory Soutadé
86a79cc381 Remove whole EBX objects for PDF when removing DRM 2024-03-28 21:54:23 +01:00
Grégory Soutadé
68bf982b6f Fix use after free in DRMProcessorClientImpl::sendHTTPRequest() 2024-01-24 19:13:22 +01:00
Grégory Soutadé
ef8c2644ca Update version 2024-01-16 11:09:31 +01:00
Grégory Soutadé
e05639c09d Support HTTP error codes != 200 (exception) and cookies in drmprocessorclientimpl 2024-01-06 09:25:11 +01:00
Grégory Soutadé
69865e005b Register operatorURL only when certificate from operator is fetched 2024-01-06 09:23:03 +01:00
Grégory Soutadé
fd38e84da6 Fix for Android for adept_loan_mgt.cpp (strptime format) 2024-01-06 09:22:11 +01:00
Grégory Soutadé
92a67312bd Update README.md 2023-09-06 21:21:43 +02:00
Grégory Soutadé
29d298b373 Update libgourou version 2023-09-06 21:17:06 +02:00
Grégory Soutadé
40dcb7a041 Add --no-notify option to utils 2023-09-06 21:17:06 +02:00
Grégory Soutadé
e0bb1bd4f8 Add notify server feature 2023-09-06 21:17:06 +02:00
Grégory Soutadé
9388d82138 Loan ID must be Fullfilment ID, not <loan> value(s) 2023-09-04 18:28:47 +02:00
Grégory Soutadé
c19279397f Update README.md 2023-08-09 20:58:06 +02:00
Grégory Soutadé
bb5349d710 Update version 2023-08-08 20:14:23 +02:00
Grégory Soutadé
9a75213b49 Fix misuse of DESTDIR and PREFIX in Makefile 2023-08-08 20:13:03 +02:00
Grégory Soutadé
e06d20a392 DRM removal: Forgot to decrypt HexaString objects 2023-08-05 14:43:48 +02:00
Grégory Soutadé
a0f6324999 Try to fix GCC 13 compilation errors 2023-05-03 21:15:31 +02:00
Grégory Soutadé
c259cbd5a3 Add missing libgen.h in utils for basename() call 2023-03-28 20:32:05 +02:00
Grégory Soutadé
46afe771c7 Remove old pugixml include in utils Makefile 2023-01-15 09:51:00 +01:00
Grégory Soutadé
cad2189fc2 Typo fix 2023-01-14 18:21:06 +01:00
Grégory Soutadé
50bc16079f Fix static build 2023-01-14 12:56:06 +01:00
Grégory Soutadé
1213b34250 Fix ADEPT path creation within adept_activate 2023-01-11 19:57:47 +01:00
Grégory Soutadé
a66dcb959c Work on Makefile 2023-01-11 19:57:47 +01:00
Grégory Soutadé
84b01a5de3 Use system version of pugixml, not a checkouted one 2023-01-08 21:15:33 +01:00
Grégory Soutadé
be78d24236 Add man pages for utils 2023-01-08 21:05:22 +01:00
Grégory Soutadé
8aec5be244 Update Makefile to be more GNU/Linux style 2023-01-08 21:05:04 +01:00
Grégory Soutadé
3a0ab4b438 Update --help for utils and README.md 2023-01-08 21:04:23 +01:00
Grégory Soutadé
891ed05926 We can now specify directly file path for acsmdownloader and adept_remove (-f stille keeped for compatibility) 2023-01-08 20:58:41 +01:00
Grégory Soutadé
fc839e671a Manage ACSM files that contains server internal error 2023-01-06 21:17:57 +01:00
Grégory Soutadé
ab5afa5003 Add new default ADEPT directories : /home/<user>/.config/adept and $ADEPT_DIR environment variable 2023-01-05 21:29:55 +01:00
Grégory Soutadé
937c27fc23 Fix some warnings 2023-01-05 21:27:50 +01:00
Grégory Soutadé
34216d1b6e Check for target user before trying to decrypt a file 2023-01-05 21:26:05 +01:00
Berwyn
ffd2004cbb Correct 'any book loaned' message 2023-01-04 20:23:57 +01:00
Grégory Soutadé
c41dd46ca7 Check for potential write error (or not buffer fully consumed) 2022-12-23 17:51:51 +01:00
Grégory Soutadé
e4bd73c03d Add global option -D to utils, allowing to specify .adept directory instead of every single files. WARNING : -D has been changed by -d in adept_loan_mgt ! 2022-12-21 21:23:42 +01:00
Grégory Soutadé
f65e8cd9eb Check for target user before trying to decrypt a file 2022-12-21 21:06:03 +01:00
Grégory Soutadé
24bae89095 Factorize decryptADEPTKey() for ePub and PDF 2022-12-21 20:58:49 +01:00
Grégory Soutadé
afab1c0012 Fix over encrypted RSA key decryption algorithm 2022-12-21 20:15:11 +01:00
Grégory Soutadé
7878f91cdd Add support for MacOS and old compilers (not supporting C++11). Main patch is from Samuel Marks. 2022-11-21 17:56:29 +01:00
Grégory Soutadé
6e3958f09e Compute over encrypted key also for PDF files 2022-09-04 09:25:06 +02:00
Grégory Soutadé
2dbd4cc343 Update version 2022-08-29 15:19:23 +02:00
Grégory Soutadé
e28dc39a68 Make DRMProcessorClient API more consistent 2022-08-29 15:19:23 +02:00
Grégory Soutadé
7b8c7acbad Compute first pass for encryptedKey if keyType attribute is set 2022-08-29 11:57:33 +02:00
Grégory Soutadé
56b3231f92 Add dumpBuffer() in libgourou_common 2022-08-29 11:56:47 +02:00
Grégory Soutadé
7084fb7025 Add fromHex() static function to ByteArray 2022-08-29 11:56:47 +02:00
Grégory Soutadé
7f5b787cb9 Add launcher util for AppImage 2022-08-29 11:56:47 +02:00
Grégory Soutadé
086e9b0610 Update version 2022-08-29 11:56:47 +02:00
Grégory Soutadé
600535d52c Utils: Migration to OpenSSL3 2022-08-29 11:56:47 +02:00
Grégory Soutadé
57c3a58994 Add STATIC_NONCE option for build (developper mode) 2022-08-10 21:34:30 +02:00
Grégory Soutadé
210b265693 Forward DEBUG flag in Makefile 2022-08-10 21:34:30 +02:00
Grégory Soutadé
33bb983283 Change log levels names to avoid collisions 2022-08-10 21:34:30 +02:00
Grégory Soutadé
3c73b8ccb3 Update .gitignore
Patch from Nguyễn Gia Phong
2022-07-03 09:22:06 +02:00
Grégory Soutadé
4acf401031 Don't clone base64 repository at first build, use a static version of Base64.h (not modified since many years)
Patch from Nguyễn Gia Phong
2022-07-03 09:20:05 +02:00
Grégory Soutadé
7666d2a241 Update README 2022-06-12 15:03:14 +02:00
Grégory Soutadé
201ec69b11 Add scripts/update_lib.sh 2022-06-12 15:03:14 +02:00
36 changed files with 5003 additions and 3774 deletions

1
.gitignore vendored
View file

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

View file

@ -1,22 +1,41 @@
PREFIX ?= /usr/local
LIBDIR ?= /lib
INCDIR ?= /include
AR ?= $(CROSS)ar AR ?= $(CROSS)ar
CXX ?= $(CROSS)g++ CXX ?= $(CROSS)g++
UPDFPARSERLIB = ./lib/updfparser/libupdfparser.a UPDFPARSERLIB = ./lib/updfparser/libupdfparser.a
CXXFLAGS=-Wall -fPIC -I./include -I./lib -I./lib/pugixml/src/ -I./lib/updfparser/include CXXFLAGS += -Wall -fPIC -I./include -I./usr/include/pugixml -I./lib/updfparser/include
LDFLAGS = $(UPDFPARSERLIB) LDFLAGS = -lpugixml
VERSION := $(shell cat include/libgourou.h |grep LIBGOUROU_VERSION|cut -d '"' -f2)
UNAME := $(shell uname -s)
BUILD_STATIC ?= 0 BUILD_STATIC ?= 0
BUILD_SHARED ?= 1 BUILD_SHARED ?= 1
BUILD_UTILS ?= 1 BUILD_UTILS ?= 1
TARGETS = TARGETS =
TARGET_LIBRARIES =
ifneq ($(STATIC_UTILS),)
BUILD_STATIC=1
endif
ifneq ($(BUILD_STATIC), 0) ifneq ($(BUILD_STATIC), 0)
TARGETS += libgourou.a TARGETS += libgourou.a
TARGET_LIBRARIES += libgourou.a
STATIC_UTILS=1
endif endif
ifneq ($(BUILD_SHARED), 0) ifneq ($(BUILD_SHARED), 0)
ifeq ($(UNAME), Darwin)
TARGETS += libgourou.dylib
TARGET_LIBRARIES += libgourou.dylib libgourou.dylib.$(VERSION)
else
TARGETS += libgourou.so TARGETS += libgourou.so
TARGET_LIBRARIES += libgourou.so libgourou.so.$(VERSION)
endif
endif endif
ifneq ($(BUILD_UTILS), 0) ifneq ($(BUILD_UTILS), 0)
TARGETS += build_utils TARGETS += build_utils
@ -24,48 +43,83 @@ endif
ifneq ($(DEBUG),) ifneq ($(DEBUG),)
CXXFLAGS += -ggdb -O0 CXXFLAGS += -ggdb -O0 -DDEBUG
else else
CXXFLAGS += -O2 CXXFLAGS += -O2
endif endif
ifneq ($(STATIC_NONCE),)
CXXFLAGS += -DSTATIC_NONCE=1
endif
SRCDIR := src SRCDIR := src
INCDIR := inc
BUILDDIR := obj BUILDDIR := obj
TARGETDIR := bin
SRCEXT := cpp SRCEXT := cpp
OBJEXT := o OBJEXT := o
SOURCES = src/libgourou.cpp src/user.cpp src/device.cpp src/fulfillment_item.cpp src/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))) OBJECTS := $(patsubst $(SRCDIR)/%,$(BUILDDIR)/%,$(SOURCES:.$(SRCEXT)=.$(OBJEXT)))
all: lib obj $(TARGETS) all: version lib obj $(TARGETS)
version:
@echo "Building libgourou $(VERSION)"
lib: lib:
mkdir lib mkdir lib
./scripts/setup.sh ./scripts/setup.sh
update_lib:
./scripts/update_lib.sh
obj: obj:
mkdir obj mkdir obj
$(BUILDDIR)/%.$(OBJEXT): $(SRCDIR)/%.$(SRCEXT) $(BUILDDIR)/%.$(OBJEXT): $(SRCDIR)/%.$(SRCEXT)
$(CXX) $(CXXFLAGS) -c $^ -o $@ $(CXX) $(CXXFLAGS) -c $^ -o $@
libgourou: libgourou.a libgourou.so libgourou: $(TARGET_LIBRARIES)
libgourou.a: $(OBJECTS) $(UPDFPARSERLIB) libgourou.a: $(OBJECTS) $(UPDFPARSERLIB)
$(AR) crs $@ obj/*.o $(UPDFPARSERLIB) $(AR) rcs --thin $@ $^
libgourou.so: $(OBJECTS) $(UPDFPARSERLIB) libgourou.so.$(VERSION): $(OBJECTS) $(UPDFPARSERLIB)
$(CXX) obj/*.o $(LDFLAGS) -o $@ -shared $(CXX) $^ -Wl,-soname,$@ $(LDFLAGS) -o $@ -shared
build_utils: libgourou.so: libgourou.so.$(VERSION)
make -C utils ROOT=$(PWD) CXX=$(CXX) AR=$(AR) DEBUG=$(DEBUG) STATIC_UTILS=$(STATIC_UTILS) OPENSSL3=$(OPENSSL3) 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: clean:
rm -rf libgourou.a libgourou.so obj rm -rf libgourou.a libgourou.so libgourou.so.$(VERSION)* obj
make -C utils clean $(MAKE) -C utils clean
ultraclean: clean ultraclean: clean
rm -rf lib rm -rf lib
make -C utils ultraclean $(MAKE) -C utils ultraclean

View file

@ -1,16 +1,16 @@
Introduction 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 Architecture
------------ ------------
Like RMSDK, libgourou has a client/server scheme. All platform specific functions (crypto, network...) has to be implemented in a client class (that derives from DRMProcessorClient) while server implements ADEPT protocol. Like RMSDK, libgourou has a client/server scheme. All platform specific functions (crypto, network...) have to be implemented in a client class (that derives from DRMProcessorClient) while server implements ADEPT protocol.
A reference implementation using Qt, OpenSSL and libzip is provided (in _utils_ directory). 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()_ * Get an ePub from an ACSM file : _fulfill()_ and _download()_
* Create a new device : _createDRMProcessor()_ * 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 * 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 * Bookeen device : .adobe-digital-editions/device.xml, root/devkey.bin and .adobe-digital-editions/activation.xml
Or create a new one. Be careful : there is a limited number of devices that can be created bye one account. Or create a new one. Be careful: there is a limited number of devices that can be created by one account.
ePub are encrypted using a shared key: one account / multiple devices, so you can create and register a device into your computer and read downloaded (and encrypted) ePub file with your eReader configured using the same AdobeID account. ePub are encrypted using a shared key: one account / multiple devices, so you can create and register a device into your computer and read downloaded (and encrypted) ePub file with your eReader configured using the same AdobeID account.
For those who wants to remove DRM without adept_remove, you can export your private key and import it within [Calibre](https://calibre-ebook.com/) an its DeDRM plugin. For those who want to remove DRM without adept_remove, you can export your private key and import it within [Calibre](https://calibre-ebook.com/) an its DeDRM plugin.
Dependencies Dependencies
@ -35,13 +35,28 @@ Dependencies
For libgourou: For libgourou:
* None _externals_ :
* libpugixml
_internals_:
* uPDFParser
For utils: For utils:
* libcurl * libcurl
* OpenSSL * openssl
* libzip * 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 Compilation
@ -49,7 +64,7 @@ Compilation
Use _make_ command 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-) CROSS can define a cross compiler prefix (ie arm-linux-gnueabihf-)
@ -63,47 +78,60 @@ BUILD_STATIC build libgourou.a if 1, nothing if 0, can be combined with BUILD_SH
BUILD_SHARED build libgourou.so if 1, nothing if 0, can be combined with BUILD_STATIC 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 * Default value
Utils 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 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> ./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: To download an ePub/PDF:
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$PWD ./utils/acsmdownloader <ACSM_FILE>
./utils/acsmdownloader -f <ACSM_FILE>
To export your private key (for DeDRM software): 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] ./utils/acsmdownloader --export-private-key [-o adobekey_1.der]
To remove ADEPT DRM: To remove ADEPT DRM:
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$PWD ./utils/adept_remove <encryptedFile>
./utils/adept_remove -f <encryptedFile>
To list loaned books: To list loaned books:
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$PWD
./utils/adept_loan_mgt [-l] ./utils/adept_loan_mgt [-l]
To return a loaned book: To return a loaned book:
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$PWD
./utils/adept_loan_mgt -r <id> ./utils/adept_loan_mgt -r <id>
You can get utils full options description with -h or --help switch 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 Docker
------ ------
@ -116,7 +144,6 @@ Copyright
Grégory Soutadé Grégory Soutadé
License License
------- -------
@ -125,9 +152,24 @@ libgourou : LGPL v3 or later
utils : BSD utils : BSD
Special thanks Special thanks
-------------- --------------
* _Jens_ for all test samples and utils testing * _Jens_ for all test samples and utils testing
* _Milian_ for debug & code * _Milian_ for debug & code
* _Berwyn H_ for all test samples, feedbacks, patches and kind donation
Donation
--------
https://www.paypal.com/donate/?hosted_button_id=JD3U6XMZCPHKN
Donators
--------
* _Berwyn H_
* _bwitt_
* _Ismail_
* _Radon_

133
include/Base64.h Normal file
View file

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

View file

@ -104,6 +104,13 @@ namespace gourou
*/ */
std::string toBase64(); 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 * @brief Return a string with human readable hex encoded internal data
*/ */
@ -130,7 +137,7 @@ namespace gourou
void append(const std::string& str); 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;} unsigned char* data() {return _data;}

View file

@ -47,20 +47,16 @@ namespace gourou
* @param handler Digest handler * @param handler Digest handler
* @param data Data to digest * @param data Data to digest
* @param length Length of data * @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 * @brief Finalize digest with remained buffered data and destroy handler
* *
* @param handler Digest handler * @param handler Digest handler
* @param digestOut Digest result (buffer must be pre allocated with right size) * @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 * @brief Global digest function
@ -69,10 +65,8 @@ namespace gourou
* @param data Data to digest * @param data Data to digest
* @param length Length of data * @param length Length of data
* @param digestOut Digest result (buffer must be pre allocated with right size) * @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 class RandomInterface
@ -111,6 +105,7 @@ namespace gourou
public: public:
enum RSA_KEY_TYPE { enum RSA_KEY_TYPE {
RSA_KEY_PKCS12 = 0, RSA_KEY_PKCS12 = 0,
RSA_KEY_PKCS8,
RSA_KEY_X509 RSA_KEY_X509
}; };
@ -238,7 +233,7 @@ namespace gourou
* @param dataOut Encrypted data * @param dataOut Encrypted data
* @param dataOutLength Length of 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* key, unsigned int keyLength,
const unsigned char* iv, unsigned int ivLength, const unsigned char* iv, unsigned int ivLength,
const unsigned char* dataIn, unsigned int dataInLength, const unsigned char* dataIn, unsigned int dataInLength,
@ -255,7 +250,7 @@ namespace gourou
* *
* @return AES handler * @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* key, unsigned int keyLength,
const unsigned char* iv=0, unsigned int ivLength=0) = 0; const unsigned char* iv=0, unsigned int ivLength=0) = 0;
@ -268,7 +263,7 @@ namespace gourou
* @param dataOut Encrypted data * @param dataOut Encrypted data
* @param dataOutLength Length of 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; unsigned char* dataOut, unsigned int* dataOutLength) = 0;
/** /**
@ -279,7 +274,7 @@ namespace gourou
* @param dataOut Last block of encrypted data * @param dataOut Last block of encrypted data
* @param dataOutLength Length of encrypted data * @param dataOutLength Length of encrypted data
*/ */
virtual void 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 * @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 dataOut Encrypted data
* @param dataOutLength Length of 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* key, unsigned int keyLength,
const unsigned char* iv, unsigned int ivLength, const unsigned char* iv, unsigned int ivLength,
const unsigned char* dataIn, unsigned int dataInLength, const unsigned char* dataIn, unsigned int dataInLength,
@ -312,7 +307,7 @@ namespace gourou
* *
* @return AES handler * @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* key, unsigned int keyLength,
const unsigned char* iv=0, unsigned int ivLength=0) = 0; const unsigned char* iv=0, unsigned int ivLength=0) = 0;
@ -325,7 +320,7 @@ namespace gourou
* @param dataOut Decrypted data * @param dataOut Decrypted data
* @param dataOutLength Length of 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; unsigned char* dataOut, unsigned int* dataOutLength) = 0;
/** /**
* @brief Finalize decryption (decrypt last block and remove padding if it is set). * @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 dataOut Last block decrypted data
* @param dataOutLength Length of decrypted data * @param dataOutLength Length of decrypted data
*/ */
virtual void DecryptFinalize(void* handler, unsigned char* dataOut, unsigned int* dataOutLength) = 0; virtual void decryptFinalize(void* handler, unsigned char* dataOut, unsigned int* dataOutLength) = 0;
}; };

View file

@ -27,20 +27,17 @@
#include "drmprocessorclient.h" #include "drmprocessorclient.h"
#include <pugixml.hpp> #include <pugixml.hpp>
#include <stdint.h>
#ifndef HOBBES_DEFAULT_VERSION #ifndef HOBBES_DEFAULT_VERSION
#define HOBBES_DEFAULT_VERSION "10.0.4" #define HOBBES_DEFAULT_VERSION "10.0.4"
#endif #endif
#ifndef DEFAULT_ADEPT_DIR
#define DEFAULT_ADEPT_DIR "./.adept"
#endif
#ifndef ACS_SERVER #ifndef ACS_SERVER
#define ACS_SERVER "http://adeactivate.adobe.com/adept" #define ACS_SERVER "http://adeactivate.adobe.com/adept"
#endif #endif
#define LIBGOUROU_VERSION "0.7.2" #define LIBGOUROU_VERSION "0.8.8"
namespace gourou namespace gourou
{ {
@ -70,10 +67,11 @@ namespace gourou
* @brief Fulfill ACSM file to server in order to retrieve ePub fulfillment item * @brief Fulfill ACSM file to server in order to retrieve ePub fulfillment item
* *
* @param ACSMFile Path of ACSMFile * @param ACSMFile Path of ACSMFile
* @param notify Notify server if requested by response
* *
* @return a FulfillmentItem if all is OK * @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. * @brief Once fulfilled, ePub file needs to be downloaded.
@ -105,8 +103,14 @@ namespace gourou
* *
* @param loanID Loan ID received during fulfill * @param loanID Loan ID received during fulfill
* @param operatorURL URL of operator that loans this book * @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). * @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) * @param ACSServer Override main ACS server (default adeactivate.adobe.com)
*/ */
static DRMProcessor* createDRMProcessor(DRMProcessorClient* client, 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& hobbes=std::string(HOBBES_DEFAULT_VERSION),
const std::string& ACSServer=ACS_SERVER); 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 buildSignInRequest(pugi::xml_document& signInRequest, const std::string& adobeID, const std::string& adobePassword, const std::string& authenticationCertificate);
void fetchLicenseServiceCertificate(const std::string& licenseURL, void fetchLicenseServiceCertificate(const std::string& licenseURL,
const std::string& operatorURL); const std::string& operatorURL);
void decryptADEPTKey(const std::string& encryptedKey, unsigned char* decryptedKey); void 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 removeEPubDRM(const std::string& filenameIn, const std::string& filenameOut, const unsigned char* encryptionKey, unsigned encryptionKeySize);
void generatePDFObjectKey(int version, void generatePDFObjectKey(int version,
const unsigned char* masterKey, unsigned int masterKeyLength, const unsigned char* masterKey, unsigned int masterKeyLength,

View file

@ -64,7 +64,8 @@ namespace gourou
FF_INVALID_ACSM_FILE, FF_INVALID_ACSM_FILE,
FF_NO_HMAC_IN_ACSM_FILE, FF_NO_HMAC_IN_ACSM_FILE,
FF_NOT_ACTIVATED, FF_NOT_ACTIVATED,
FF_NO_OPERATOR_URL FF_NO_OPERATOR_URL,
FF_SERVER_INTERNAL_ERROR
}; };
enum DOWNLOAD_ERROR { enum DOWNLOAD_ERROR {
@ -106,6 +107,7 @@ namespace gourou
CLIENT_INVALID_PKCS12, CLIENT_INVALID_PKCS12,
CLIENT_INVALID_CERTIFICATE, CLIENT_INVALID_CERTIFICATE,
CLIENT_NO_PRIV_KEY, CLIENT_NO_PRIV_KEY,
CLIENT_NO_PUB_KEY,
CLIENT_RSA_ERROR, CLIENT_RSA_ERROR,
CLIENT_BAD_CHAINING, CLIENT_BAD_CHAINING,
CLIENT_BAD_KEY_SIZE, CLIENT_BAD_KEY_SIZE,
@ -116,6 +118,9 @@ namespace gourou
CLIENT_INVALID_PKCS8, CLIENT_INVALID_PKCS8,
CLIENT_FILE_ERROR, CLIENT_FILE_ERROR,
CLIENT_OSSL_ERROR, CLIENT_OSSL_ERROR,
CLIENT_CRYPT_ERROR,
CLIENT_DIGEST_ERROR,
CLIENT_HTTP_ERROR
}; };
enum DRM_REMOVAL_ERROR { enum DRM_REMOVAL_ERROR {
@ -125,9 +130,21 @@ namespace gourou
DRM_FORMAT_NOT_SUPPORTED, DRM_FORMAT_NOT_SUPPORTED,
DRM_IN_OUT_EQUALS, DRM_IN_OUT_EQUALS,
DRM_MISSING_PARAMETER, 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 * Generic exception class
*/ */
@ -140,7 +157,7 @@ namespace gourou
std::stringstream msg; std::stringstream msg;
msg << "Exception code : 0x" << std::setbase(16) << code << std::endl; msg << "Exception code : 0x" << std::setbase(16) << code << std::endl;
msg << "Message : " << message << std::endl; msg << "Message : " << message << std::endl;
if (logLevel >= DEBUG) if (logLevel >= LG_LOG_DEBUG)
msg << "File : " << file << ":" << std::setbase(10) << line << std::endl; msg << "File : " << file << ":" << std::setbase(10) << line << std::endl;
fullmessage = strdup(msg.str().c_str()); fullmessage = strdup(msg.str().c_str());
} }
@ -148,12 +165,12 @@ namespace gourou
Exception(const Exception& other) Exception(const Exception& other)
{ {
this->code = other.code; this->code = other.code;
this->line = line; this->line = other.line;
this->file = file; this->file = other.file;
this->fullmessage = strdup(other.fullmessage); this->fullmessage = strdup(other.fullmessage);
} }
~Exception() ~Exception() _NOEXCEPT
{ {
free(fullmessage); free(fullmessage);
} }
@ -164,7 +181,7 @@ namespace gourou
private: private:
int code, line; int code, line;
const char* message, *file; const char* file;
char* fullmessage; char* fullmessage;
}; };
@ -219,24 +236,32 @@ namespace gourou
return ltrim(rtrim(s, t), t); return ltrim(rtrim(s, t), t);
} }
/** static inline pugi::xml_node getNode(const pugi::xml_node& root, const char* tagName, bool throwOnNull=true)
* @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)
{ {
pugi::xpath_node xpath_node = doc.select_node(tagName); pugi::xpath_node xpath_node = root.select_node(tagName);
if (!xpath_node) if (!xpath_node)
{ {
if (throwOnNull) if (throwOnNull)
EXCEPTION(GOUROU_TAG_NOT_FOUND, "Tag " << tagName << " not found"); 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) if (!node)
{ {
@ -250,29 +275,50 @@ namespace gourou
return trim(res); 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); pugi::xml_node node = getNode(root, tagName, throwOnNull);
if (!xpath_node)
{
if (throwOnNull)
EXCEPTION(GOUROU_TAG_NOT_FOUND, "Tag " << tagName << " not found");
return "";
}
pugi::xml_node node = xpath_node.node().first_child();
if (!node) if (!node)
{ {
if (throwOnNull) if (throwOnNull)
EXCEPTION(GOUROU_TAG_NOT_FOUND, "Text element for tag " << tagName << " not found"); 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 ""; return "";
} }
std::string res = node.value(); std::string res = attr.value();
return trim(res); return trim(res);
} }
@ -289,6 +335,29 @@ namespace gourou
node.append_child(pugi::node_pcdata).set_value(value.c_str()); 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 * @brief Open a file descriptor on path. If it already exists and truncate == true, it's truncated
* *
@ -414,6 +483,20 @@ namespace gourou
} }
return 0; 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 #endif

View file

@ -24,16 +24,16 @@
namespace gourou { namespace gourou {
enum GOUROU_LOG_LEVEL { enum GOUROU_LOG_LEVEL {
ERROR, LG_LOG_ERROR,
WARN, LG_LOG_WARN,
INFO, LG_LOG_INFO,
DEBUG, LG_LOG_DEBUG,
TRACE LG_LOG_TRACE
}; };
extern GOUROU_LOG_LEVEL logLevel; 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__) #define GOUROU_LOG_FUNC() GOUROU_LOG(TRACE, __FUNCTION__ << "() @ " << __FILE__ << ":" << __LINE__)
/** /**

View file

@ -1,21 +1,8 @@
#!/bin/bash #!/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 # uPDFParser
if [ ! -d lib/updfparser ] ; then 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 pushd lib/updfparser
make BUILD_STATIC=1 BUILD_SHARED=0 make BUILD_STATIC=1 BUILD_SHARED=0
popd popd

13
scripts/update_lib.sh Executable file
View file

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

View file

@ -17,8 +17,9 @@
along with libgourou. If not, see <http://www.gnu.org/licenses/>. along with libgourou. If not, see <http://www.gnu.org/licenses/>.
*/ */
#include <string.h> #include <string.h>
#include <stdexcept>
#include <base64/Base64.h> #include <Base64.h>
#include <bytearray.h> #include <bytearray.h>
@ -155,12 +156,53 @@ namespace gourou
return macaron::Base64::Encode(std::string((char*)_data, _length)); 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() std::string ByteArray::toHex()
{ {
char* tmp = new char[_length*2+1]; char* tmp = new char[_length*2+1];
for(int i=0; i<(int)_length; i++) 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; tmp[_length*2] = 0;

View file

@ -29,13 +29,23 @@
#include <libgourou_log.h> #include <libgourou_log.h>
#include <device.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 <sys/ioctl.h>
#include <net/if.h> #include <net/if.h>
#include <unistd.h> #include <unistd.h>
#include <netinet/in.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) int get_mac_address(unsigned char* mac_address)
{ {
struct ifreq ifr; struct ifreq ifr;
@ -74,6 +84,43 @@ int get_mac_address(unsigned char* mac_address)
return 1; 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 namespace gourou

View file

@ -17,6 +17,7 @@
along with libgourou. If not, see <http://www.gnu.org/licenses/>. along with libgourou. If not, see <http://www.gnu.org/licenses/>.
*/ */
#include <cctype>
#include <fulfillment_item.h> #include <fulfillment_item.h>
#include <libgourou_common.h> #include <libgourou_common.h>
#include "user.h" #include "user.h"
@ -93,8 +94,12 @@ namespace gourou
std::string FulfillmentItem::getMetadata(std::string name) std::string FulfillmentItem::getMetadata(std::string name)
{ {
// https://stackoverflow.com/questions/313970/how-to-convert-an-instance-of-stdstring-to-lower-case // 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(), std::transform(name.begin(), name.end(), name.begin(),
[](unsigned char c){ return std::tolower(c); }); [](unsigned char c){ return std::tolower(c); });
#else
std::transform(name.begin(), name.end(), name.begin(), tolower);
#endif
name = std::string("dc:") + name; name = std::string("dc:") + name;
pugi::xpath_node path = metadatas.select_node(name.c_str()); pugi::xpath_node path = metadatas.select_node(name.c_str());

View file

@ -21,6 +21,7 @@
#include <sys/time.h> #include <sys/time.h>
#include <time.h> #include <time.h>
#include <vector> #include <vector>
#include <ctime>
#include <uPDFParser.h> #include <uPDFParser.h>
@ -28,6 +29,8 @@
#include <libgourou_common.h> #include <libgourou_common.h>
#include <libgourou_log.h> #include <libgourou_log.h>
#define LOCAL_ADEPT_DIR "./.adept"
#define ASN_NONE 0x00 #define ASN_NONE 0x00
#define ASN_NS_TAG 0x01 #define ASN_NS_TAG 0x01
#define ASN_CHILD 0x02 #define ASN_CHILD 0x02
@ -37,7 +40,7 @@
namespace gourou namespace gourou
{ {
GOUROU_LOG_LEVEL logLevel = WARN; GOUROU_LOG_LEVEL logLevel = LG_LOG_WARN;
const std::string DRMProcessor::VERSION = LIBGOUROU_VERSION; const std::string DRMProcessor::VERSION = LIBGOUROU_VERSION;
DRMProcessor::DRMProcessor(DRMProcessorClient* client):client(client), device(0), user(0) DRMProcessor::DRMProcessor(DRMProcessorClient* client):client(client), device(0), user(0)
@ -68,11 +71,23 @@ namespace gourou
if (user) delete user; 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) const std::string& hobbes, const std::string& ACSServer)
{ {
DRMProcessor* processor = new DRMProcessor(client); DRMProcessor* processor = new DRMProcessor(client);
if (dirName == "")
dirName = getDefaultAdeptDir();
Device* device = Device::createDevice(processor, dirName, hobbes, randomSerial); Device* device = Device::createDevice(processor, dirName, hobbes, randomSerial);
processor->device = device; processor->device = device;
@ -89,7 +104,7 @@ namespace gourou
uint16_t nlength = htons(length); uint16_t nlength = htons(length);
char c; char c;
if (logLevel >= TRACE) if (logLevel >= LG_LOG_TRACE)
printf("%02x %02x ", ((uint8_t*)&nlength)[0], ((uint8_t*)&nlength)[1]); printf("%02x %02x ", ((uint8_t*)&nlength)[0], ((uint8_t*)&nlength)[1]);
client->digestUpdate(sha_ctx, (unsigned char*)&nlength, sizeof(nlength)); client->digestUpdate(sha_ctx, (unsigned char*)&nlength, sizeof(nlength));
@ -98,17 +113,17 @@ namespace gourou
{ {
c = string[i]; c = string[i];
client->digestUpdate(sha_ctx, (unsigned char*)&c, 1); client->digestUpdate(sha_ctx, (unsigned char*)&c, 1);
if (logLevel >= TRACE) if (logLevel >= LG_LOG_TRACE)
printf("%c", c); printf("%c", c);
} }
if (logLevel >= TRACE) if (logLevel >= LG_LOG_TRACE)
printf("\n"); printf("\n");
} }
void DRMProcessor::pushTag(void* sha_ctx, uint8_t tag) void DRMProcessor::pushTag(void* sha_ctx, uint8_t tag)
{ {
client->digestUpdate(sha_ctx, &tag, sizeof(tag)); client->digestUpdate(sha_ctx, &tag, sizeof(tag));
if (logLevel >= TRACE) if (logLevel >= LG_LOG_TRACE)
printf("%02x ", tag); printf("%02x ", tag);
} }
@ -226,14 +241,7 @@ namespace gourou
client->digestFinalize(sha_ctx, sha_out); client->digestFinalize(sha_ctx, sha_out);
if (logLevel >= DEBUG) dumpBuffer(gourou::LG_LOG_DEBUG, "\nSHA OUT : ", sha_out, SHA1_LEN);
{
printf("\nSHA OUT : ");
for(int i=0; i<(int)SHA1_LEN; i++)
printf("%02x ", sha_out[i]);
printf("\n");
}
} }
void DRMProcessor::signNode(pugi::xml_node& rootNode) void DRMProcessor::signNode(pugi::xml_node& rootNode)
@ -252,13 +260,8 @@ namespace gourou
client->RSAPrivateEncrypt(privateRSAKey.data(), privateRSAKey.length(), client->RSAPrivateEncrypt(privateRSAKey.data(), privateRSAKey.length(),
RSAInterface::RSA_KEY_PKCS12, deviceKey.toBase64().data(), RSAInterface::RSA_KEY_PKCS12, deviceKey.toBase64().data(),
sha_out, sizeof(sha_out), res); sha_out, sizeof(sha_out), res);
if (logLevel >= DEBUG)
{ dumpBuffer(gourou::LG_LOG_DEBUG, "Sig : ", res, sizeof(res));
printf("Sig : ");
for(int i=0; i<(int)sizeof(res); i++)
printf("%02x ", res[i]);
printf("\n");
}
std::string signature = ByteArray(res, sizeof(res)).toBase64(); std::string signature = ByteArray(res, sizeof(res)).toBase64();
appendTextElem(rootNode, "adept:signature", signature); appendTextElem(rootNode, "adept:signature", signature);
@ -282,7 +285,11 @@ namespace gourou
struct timeval tv; struct timeval tv;
gettimeofday(&tv, 0); gettimeofday(&tv, 0);
uint32_t nonce32[2] = {0x6f046000, 0x388a}; uint32_t nonce32[2] = {0x6f046000, 0x388a};
#ifdef STATIC_NONCE
uint64_t bigtime = 0xAA001122BBCCAAULL;
#else
uint64_t bigtime = tv.tv_sec*1000; uint64_t bigtime = tv.tv_sec*1000;
#endif
nonce32[0] += (bigtime & 0xFFFFFFFF) + (tv.tv_usec/1000); nonce32[0] += (bigtime & 0xFFFFFFFF) + (tv.tv_usec/1000);
nonce32[1] += ((bigtime >> 32) & 0xFFFFFFFF); nonce32[1] += ((bigtime >> 32) & 0xFFFFFFFF);
@ -402,30 +409,6 @@ namespace gourou
} }
doOperatorAuth(operatorURL); 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) 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:licenseURL", licenseURL);
appendTextElem(root, "adept:certificate", certificate); 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); user->updateActivationFile(activationDoc);
} }
FulfillmentItem* DRMProcessor::fulfill(const std::string& ACSMFile) FulfillmentItem* DRMProcessor::fulfill(const std::string& ACSMFile, bool notify)
{ {
if (!user->getPKCS12().length()) if (!user->getPKCS12().length())
EXCEPTION(FF_NOT_ACTIVATED, "Device not activated"); 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)) 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); 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); 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 // Build req file
pugi::xml_document fulfillReq; pugi::xml_document fulfillReq;
buildFulfillRequest(acsmDoc, fulfillReq); buildFulfillRequest(acsmDoc, fulfillReq);
pugi::xpath_node root = fulfillReq.select_node("//adept:fulfill"); pugi::xpath_node root = fulfillReq.select_node("//adept:fulfill");
pugi::xml_node rootNode = root.node(); rootNode = root.node();
// Remove HMAC // Remove HMAC
pugi::xpath_node xpathRes = fulfillReq.select_node("//hmac"); pugi::xpath_node xpathRes = fulfillReq.select_node("//hmac");
@ -576,7 +594,12 @@ namespace gourou
fetchLicenseServiceCertificate(licenseURL, operatorURL); 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) DRMProcessor::ITEM_TYPE DRMProcessor::download(FulfillmentItem* item, std::string path, bool resume)
@ -853,7 +876,31 @@ namespace gourou
signNode(root); 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; pugi::xml_document returnReq;
@ -861,7 +908,71 @@ namespace gourou
buildReturnReq(returnReq, loanID, operatorURL); 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) ByteArray DRMProcessor::encryptWithDeviceKey(const unsigned char* data, unsigned int len)
@ -877,7 +988,7 @@ namespace gourou
// Generate IV in front // Generate IV in front
client->randBytes(encrypted_data, 16); 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, deviceKey, 16, encrypted_data, 16,
data, len, data, len,
encrypted_data+16, &outLen); encrypted_data+16, &outLen);
@ -896,7 +1007,7 @@ namespace gourou
const unsigned char* deviceKey = device->getDeviceKey(); const unsigned char* deviceKey = device->getDeviceKey();
unsigned char* decrypted_data = new unsigned char[len-16]; unsigned char* decrypted_data = new unsigned char[len-16];
client->Decrypt(CryptoInterface::ALGO_AES, CryptoInterface::CHAIN_CBC, client->decrypt(CryptoInterface::ALGO_AES, CryptoInterface::CHAIN_CBC,
deviceKey, 16, data, 16, deviceKey, 16, data, 16,
data+16, len-16, data+16, len-16,
decrypted_data, &outLen); decrypted_data, &outLen);
@ -939,42 +1050,154 @@ namespace gourou
void DRMProcessor::exportPrivateLicenseKey(std::string path) void DRMProcessor::exportPrivateLicenseKey(std::string path)
{ {
int fd = open(path.c_str(), O_CREAT|O_TRUNC|O_WRONLY, S_IRWXU); int fd = open(path.c_str(), O_CREAT|O_TRUNC|O_WRONLY, S_IRWXU);
int ret;
if (fd <= 0) if (fd <= 0)
EXCEPTION(GOUROU_FILE_ERROR, "Unable to open " << path); EXCEPTION(GOUROU_FILE_ERROR, "Unable to open " << path);
ByteArray privateLicenseKey = ByteArray::fromBase64(user->getPrivateLicenseKey()); ByteArray privateLicenseKey = ByteArray::fromBase64(user->getPrivateLicenseKey());
/* In adobekey.py, we get base64 decoded data [26:] */ /* 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); close(fd);
if (ret != (int)(privateLicenseKey.length()-26))
{
EXCEPTION(gourou::GOUROU_FILE_ERROR, "Error writing " << path);
}
} }
int DRMProcessor::getLogLevel() {return (int)gourou::logLevel;} int DRMProcessor::getLogLevel() {return (int)gourou::logLevel;}
void DRMProcessor::setLogLevel(int logLevel) {gourou::logLevel = (GOUROU_LOG_LEVEL)logLevel;} void DRMProcessor::setLogLevel(int logLevel) {gourou::logLevel = (GOUROU_LOG_LEVEL)logLevel;}
void DRMProcessor::decryptADEPTKey(const std::string& encryptedKey, unsigned char* decryptedKey) /**
* 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) if (encryptedKey.size() != 172)
EXCEPTION(DRM_INVALID_KEY_SIZE, "Invalid encrypted key size (" << encryptedKey.size() << "). DRM version not supported"); EXCEPTION(DRM_INVALID_KEY_SIZE, "Invalid encrypted key size (" << encryptedKey.size() << "). DRM version not supported");
ByteArray arrayEncryptedKey = ByteArray::fromBase64(encryptedKey); ByteArray arrayEncryptedKey = ByteArray::fromBase64(encryptedKey);
std::string privateKeyData = this->user->getPrivateLicenseKey();
std::string privateKeyData = user->getPrivateLicenseKey();
ByteArray privateRSAKey = ByteArray::fromBase64(privateKeyData); ByteArray privateRSAKey = ByteArray::fromBase64(privateKeyData);
ByteArray deviceKey(device->getDeviceKey(), Device::DEVICE_KEY_SIZE); dumpBuffer(gourou::LG_LOG_DEBUG, "To decrypt : ", arrayEncryptedKey.data(), arrayEncryptedKey.length());
std::string pkcs12 = user->getPKCS12();
client->RSAPrivateDecrypt(privateRSAKey.data(), privateRSAKey.length(), client->RSAPrivateDecrypt(privateRSAKey.data(), privateRSAKey.length(),
RSAInterface::RSA_KEY_PKCS12, deviceKey.toBase64().data(), RSAInterface::RSA_KEY_PKCS8, "",
arrayEncryptedKey.data(), arrayEncryptedKey.length(), decryptedKey); arrayEncryptedKey.data(), arrayEncryptedKey.length(), rsaKey);
if (decryptedKey[0] != 0x00 || decryptedKey[1] != 0x02 || dumpBuffer(gourou::LG_LOG_DEBUG, "Decrypted : ", rsaKey, sizeof(rsaKey));
decryptedKey[RSA_KEY_SIZE-16-1] != 0x00)
if (rsaKey[0] != 0x00 || rsaKey[1] != 0x02 ||
rsaKey[RSA_KEY_SIZE-16-1] != 0x00)
EXCEPTION(DRM_ERR_ENCRYPTION_KEY, "Unable to retrieve encryption key"); 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, void DRMProcessor::removeEPubDRM(const std::string& filenameIn, const std::string& filenameOut,
const unsigned char* encryptionKey, unsigned encryptionKeySize) const unsigned char* encryptionKey, unsigned encryptionKeySize)
@ -987,19 +1210,9 @@ namespace gourou
pugi::xml_document rightsDoc; pugi::xml_document rightsDoc;
rightsDoc.load_string((const char*)zipData.data()); rightsDoc.load_string((const char*)zipData.data());
std::string encryptedKey = extractTextElem(rightsDoc, "/adept:rights/licenseToken/encryptedKey"); unsigned char decryptedKey[16];
unsigned char decryptedKey[RSA_KEY_SIZE];
if (!encryptionKey) decryptADEPTKey(rightsDoc, decryptedKey, encryptionKey, encryptionKeySize);
decryptADEPTKey(encryptedKey, decryptedKey);
else
{
GOUROU_LOG(DEBUG, "Use provided encryption key");
if (encryptionKeySize != 16)
EXCEPTION(DRM_ERR_ENCRYPTION_KEY, "Provided encryption key must be 16 bytes");
memcpy(&decryptedKey[sizeof(decryptedKey)-16], encryptionKey, encryptionKeySize);
}
client->zipReadFile(zipHandler, "META-INF/encryption.xml", zipData); client->zipReadFile(zipHandler, "META-INF/encryption.xml", zipData);
pugi::xml_document encryptionDoc; pugi::xml_document encryptionDoc;
@ -1037,8 +1250,8 @@ namespace gourou
gourou::ByteArray inflateData(true); gourou::ByteArray inflateData(true);
unsigned int dataOutLength; unsigned int dataOutLength;
client->Decrypt(CryptoInterface::ALGO_AES, CryptoInterface::CHAIN_CBC, client->decrypt(CryptoInterface::ALGO_AES, CryptoInterface::CHAIN_CBC,
decryptedKey+sizeof(decryptedKey)-16, 16, /* Key */ decryptedKey, sizeof(decryptedKey), /* Key */
_data, 16, /* IV */ _data, 16, /* IV */
&_data[16], zipData.length()-16, &_data[16], zipData.length()-16,
_clearData, &dataOutLength); _clearData, &dataOutLength);
@ -1129,9 +1342,10 @@ namespace gourou
uPDFParser::Integer* ebxVersion; uPDFParser::Integer* ebxVersion;
std::vector<uPDFParser::Object*> objects = parser.objects(); std::vector<uPDFParser::Object*> objects = parser.objects();
std::vector<uPDFParser::Object*>::iterator it; std::vector<uPDFParser::Object*>::iterator it, ebxIt;
std::vector<uPDFParser::Object*>::reverse_iterator rIt; std::vector<uPDFParser::Object*>::reverse_iterator rIt;
unsigned char decryptedKey[RSA_KEY_SIZE]; std::vector<uPDFParser::Object*> ebxObjects;
unsigned char decryptedKey[16];
int ebxId; int ebxId;
for(rIt = objects.rbegin(); rIt != objects.rend(); rIt++) for(rIt = objects.rbegin(); rIt != objects.rend(); rIt++)
@ -1170,18 +1384,7 @@ namespace gourou
pugi::xml_document rightsDoc; pugi::xml_document rightsDoc;
rightsDoc.load_string((const char*)rightsStr.data()); rightsDoc.load_string((const char*)rightsStr.data());
std::string encryptedKey = extractTextElem(rightsDoc, "/adept:rights/licenseToken/encryptedKey"); decryptADEPTKey(rightsDoc, decryptedKey, encryptionKey, encryptionKeySize);
if (!encryptionKey)
decryptADEPTKey(encryptedKey, decryptedKey);
else
{
GOUROU_LOG(DEBUG, "Use provided encryption key");
if (encryptionKeySize != 16)
EXCEPTION(DRM_ERR_ENCRYPTION_KEY, "Provided encryption key must be 16 bytes");
memcpy(&decryptedKey[sizeof(decryptedKey)-16], encryptionKey, encryptionKeySize);
}
ebxId = ebx->objectId(); ebxId = ebx->objectId();
@ -1200,7 +1403,7 @@ namespace gourou
if (object->objectId() == ebxId) if (object->objectId() == ebxId)
{ {
// object->deleteKey("Filter"); ebxObjects.push_back(object);
continue; continue;
} }
@ -1213,10 +1416,10 @@ namespace gourou
GOUROU_LOG(DEBUG, "Obj " << object->objectId()); GOUROU_LOG(DEBUG, "Obj " << object->objectId());
unsigned char tmpKey[16]; unsigned char tmpKey[sizeof(decryptedKey)];
generatePDFObjectKey(ebxVersion->value(), generatePDFObjectKey(ebxVersion->value(),
decryptedKey+sizeof(decryptedKey)-16, 16, decryptedKey, sizeof(decryptedKey),
object->objectId(), object->generationNumber(), object->objectId(), object->generationNumber(),
tmpKey); tmpKey);
@ -1241,8 +1444,8 @@ namespace gourou
GOUROU_LOG(DEBUG, "Decrypt string " << dictIt->first << " " << dataLength); GOUROU_LOG(DEBUG, "Decrypt string " << dictIt->first << " " << dataLength);
client->Decrypt(CryptoInterface::ALGO_RC4, CryptoInterface::CHAIN_ECB, client->decrypt(CryptoInterface::ALGO_RC4, CryptoInterface::CHAIN_ECB,
tmpKey, 16, /* Key */ tmpKey, sizeof(tmpKey), /* Key */
NULL, 0, /* IV */ NULL, 0, /* IV */
encryptedData, dataLength, encryptedData, dataLength,
clearData, &dataOutLength); clearData, &dataOutLength);
@ -1252,6 +1455,30 @@ namespace gourou
delete[] clearData; 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++) 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()); GOUROU_LOG(DEBUG, "Decrypt stream id " << object->objectId() << ", size " << stream->dataLength());
client->Decrypt(CryptoInterface::ALGO_RC4, CryptoInterface::CHAIN_ECB, client->decrypt(CryptoInterface::ALGO_RC4, CryptoInterface::CHAIN_ECB,
tmpKey, 16, /* Key */ tmpKey, sizeof(tmpKey), /* Key */
NULL, 0, /* IV */ NULL, 0, /* IV */
encryptedData, dataLength, encryptedData, dataLength,
clearData, &dataOutLength); 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(); uPDFParser::Object& trailer = parser.getTrailer();
trailer.deleteKey("Encrypt"); trailer.deleteKey("Encrypt");

View file

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

View file

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

View file

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

View file

@ -46,6 +46,7 @@ static bool exportPrivateKey = false;
static const char* outputFile = 0; static const char* outputFile = 0;
static const char* outputDir = 0; static const char* outputDir = 0;
static bool resume = false; static bool resume = false;
static bool notify = true;
class ACSMDownloader class ACSMDownloader
@ -63,29 +64,30 @@ public:
if (exportPrivateKey) if (exportPrivateKey)
{ {
std::string filename; std::string filename;
if (!outputFile) if (outputFile)
filename = std::string("Adobe_PrivateLicenseKey--") + user->getUsername() + ".der";
else
filename = outputFile; filename = outputFile;
else
{
filename = std::string("Adobe_PrivateLicenseKey--") + user->getUsername() + ".der";
if (outputDir) if (outputDir)
{
if (!fileExists(outputDir))
mkpath(outputDir);
filename = std::string(outputDir) + "/" + filename; filename = std::string(outputDir) + "/" + filename;
} }
createPath(filename.c_str());
processor.exportPrivateLicenseKey(filename); processor.exportPrivateLicenseKey(filename);
std::cout << "Private license key exported to " << filename << std::endl; std::cout << "Private license key exported to " << filename << std::endl;
} }
else else
{ {
gourou::FulfillmentItem* item = processor.fulfill(acsmFile); gourou::FulfillmentItem* item = processor.fulfill(acsmFile, notify);
std::string filename; std::string filename;
if (!outputFile) if (outputFile)
filename = outputFile;
else
{ {
filename = item->getMetadata("title"); filename = item->getMetadata("title");
if (filename == "") if (filename == "")
@ -95,18 +97,13 @@ public:
// Remove invalid characters // Remove invalid characters
std::replace(filename.begin(), filename.end(), '/', '_'); std::replace(filename.begin(), filename.end(), '/', '_');
} }
}
else
filename = outputFile;
if (outputDir) if (outputDir)
{
if (!fileExists(outputDir))
mkpath(outputDir);
filename = std::string(outputDir) + "/" + filename; filename = std::string(outputDir) + "/" + filename;
} }
createPath(filename.c_str());
gourou::DRMProcessor::ITEM_TYPE type = processor.download(item, filename, resume); gourou::DRMProcessor::ITEM_TYPE type = processor.download(item, filename, resume);
if (!outputFile) if (!outputFile)
@ -182,24 +179,31 @@ private:
static void usage(const char* cmd) static void usage(const char* cmd)
{ {
std::cout << "Download EPUB file from ACSM request file" << std::endl; std::cout << 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 << "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 << "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 << " " << "-d|--device-file" << "\t" << "device.xml file from eReader" << std::endl; std::cout << " " << "-o|--output-file" << "\t" << "Optional output filename (default <title.(epub|pdf|der)>) (not compatible with -O)" << std::endl;
std::cout << " " << "-a|--activation-file" << "\t" << "activation.xml file from eReader" << std::endl; std::cout << " " << "-f|--acsm-file" << "\t" << "Backward compatibility: ACSM request file for epub download" << 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 << " " << "-e|--export-private-key"<< "\t" << "Export private key in DER format" << std::endl; std::cout << " " << "-e|--export-private-key"<< "\t" << "Export private key in DER format" << std::endl;
std::cout << " " << "-r|--resume" << "\t\t" << "Try to resume download (in case of previous failure)" << std::endl; std::cout << " " << "-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|--verbose" << "\t\t" << "Increase verbosity, can be set multiple times" << std::endl;
std::cout << " " << "-V|--version" << "\t\t" << "Display libgourou version" << std::endl; std::cout << " " << "-V|--version" << "\t\t" << "Display libgourou version" << std::endl;
std::cout << " " << "-h|--help" << "\t\t" << "This help" << std::endl; std::cout << " " << "-h|--help" << "\t\t" << "This help" << std::endl;
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 << 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 << " * Current directory" << std::endl;
std::cout << " * .adept" << std::endl; std::cout << " * .adept" << std::endl;
std::cout << " * adobe-digital-editions directory" << 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 main(int argc, char** argv)
{ {
int c, ret = -1; int c, ret = -1;
std::string _deviceFile, _activationFile, _devicekeyFile;
const char** files[] = {&devicekeyFile, &deviceFile, &activationFile}; const char** files[] = {&devicekeyFile, &deviceFile, &activationFile};
int verbose = gourou::DRMProcessor::getLogLevel(); int verbose = gourou::DRMProcessor::getLogLevel();
@ -216,6 +221,7 @@ int main(int argc, char** argv)
while (1) { while (1) {
int option_index = 0; int option_index = 0;
static struct option long_options[] = { static struct option long_options[] = {
{"adept-directory", required_argument, 0, 'D' },
{"device-file", required_argument, 0, 'd' }, {"device-file", required_argument, 0, 'd' },
{"activation-file", required_argument, 0, 'a' }, {"activation-file", required_argument, 0, 'a' },
{"device-key-file", required_argument, 0, 'k' }, {"device-key-file", required_argument, 0, 'k' },
@ -224,18 +230,27 @@ int main(int argc, char** argv)
{"acsm-file", required_argument, 0, 'f' }, {"acsm-file", required_argument, 0, 'f' },
{"export-private-key",no_argument, 0, 'e' }, {"export-private-key",no_argument, 0, 'e' },
{"resume", no_argument, 0, 'r' }, {"resume", no_argument, 0, 'r' },
{"no-notify", no_argument, 0, 'N' },
{"verbose", no_argument, 0, 'v' }, {"verbose", no_argument, 0, 'v' },
{"version", no_argument, 0, 'V' }, {"version", no_argument, 0, 'V' },
{"help", no_argument, 0, 'h' }, {"help", no_argument, 0, 'h' },
{0, 0, 0, 0 } {0, 0, 0, 0 }
}; };
c = getopt_long(argc, argv, "d:a:k:O:o:f:ervVh", c = getopt_long(argc, argv, "D:d:a:k:O:o:f:erNvVh",
long_options, &option_index); long_options, &option_index);
if (c == -1) if (c == -1)
break; break;
switch (c) { 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': case 'd':
deviceFile = optarg; deviceFile = optarg;
break; break;
@ -260,6 +275,9 @@ int main(int argc, char** argv)
case 'r': case 'r':
resume = true; resume = true;
break; break;
case 'N':
notify = false;
break;
case 'v': case 'v':
verbose++; verbose++;
break; break;
@ -277,6 +295,9 @@ int main(int argc, char** argv)
gourou::DRMProcessor::setLogLevel(verbose); gourou::DRMProcessor::setLogLevel(verbose);
if (optind == argc-1)
acsmFile = argv[optind];
if ((!acsmFile && !exportPrivateKey) || (outputDir && !outputDir[0]) || if ((!acsmFile && !exportPrivateKey) || (outputDir && !outputDir[0]) ||
(outputFile && !outputFile[0])) (outputFile && !outputFile[0]))
{ {
@ -284,6 +305,12 @@ int main(int argc, char** argv)
return -1; return -1;
} }
if (outputDir && outputFile)
{
std::cout << "Error : you cannot use both -o and -O" << std::endl;
return -1;
}
ACSMDownloader downloader; ACSMDownloader downloader;
int i; int i;
@ -314,7 +341,7 @@ int main(int argc, char** argv)
} }
else else
{ {
if (!fileExists(acsmFile)) if (!pathExists(acsmFile))
{ {
std::cout << "Error : " << acsmFile << " doesn't exists" << std::endl; std::cout << "Error : " << acsmFile << " doesn't exists" << std::endl;
ret = -1; ret = -1;

View file

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

View file

@ -45,13 +45,14 @@
#define MAX_SIZE_BOOK_NAME 30 #define MAX_SIZE_BOOK_NAME 30
static char* activationDir = 0; static char* adeptDir = 0;
static const char* deviceFile = "device.xml"; static const char* deviceFile = "device.xml";
static const char* activationFile = "activation.xml"; static const char* activationFile = "activation.xml";
static const char* devicekeyFile = "devicesalt"; static const char* devicekeyFile = "devicesalt";
static bool list = false; static bool list = false;
static const char* returnID = 0; static const char* returnID = 0;
static const char* deleteID = 0; static const char* deleteID = 0;
static bool notify = true;
struct Loan struct Loan
{ {
@ -106,9 +107,9 @@ private:
struct Loan* loan; struct Loan* loan;
char * res; 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; return;
dp = opendir (loanDir.c_str()); dp = opendir (loanDir.c_str());
@ -182,8 +183,13 @@ private:
loan->bookName = node.first_child().value(); loan->bookName = node.first_child().value();
struct tm tm; 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); 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)) if (mktime(&tm) <= time(NULL))
loan->validity = " (Expired)"; loan->validity = " (Expired)";
@ -209,7 +215,7 @@ private:
{ {
if (!loanedBooks.size()) if (!loanedBooks.size())
{ {
std::cout << "Any book loaned" << std::endl; std::cout << "No books loaned" << std::endl;
return; return;
} }
@ -223,7 +229,12 @@ private:
maxSizeBookName = loan->bookName.size(); 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; maxSizeBookName = MAX_SIZE_BOOK_NAME;
else if ((maxSizeBookName % 2)) else if ((maxSizeBookName % 2))
maxSizeBookName++; maxSizeBookName++;
@ -252,7 +263,7 @@ private:
std::cout.width (fillExpiration); std::cout.width (fillExpiration);
std::cout << ""; std::cout << "";
std::cout << "Exipration"; std::cout << "Expiration";
std::cout.width (fillExpiration); std::cout.width (fillExpiration);
std::cout << "" << std::endl; std::cout << "" << std::endl;
@ -270,7 +281,9 @@ private:
std::cout << kv.first; std::cout << kv.first;
std::cout << " "; 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); bookName = std::string(loan->bookName.c_str(), MAX_SIZE_BOOK_NAME);
else else
bookName = loan->bookName; bookName = loan->bookName;
@ -296,7 +309,7 @@ private:
return; return;
} }
processor.returnLoan(loan->id, loan->operatorURL); processor.returnLoan(loan->id, loan->operatorURL, notify);
deleteID = returnID; deleteID = returnID;
if (deleteLoan(false)) if (deleteLoan(false))
@ -334,20 +347,27 @@ private:
static void usage(const char* cmd) 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 << " " << "-l|--list" << "\t\t" << "List all loaned books" << std::endl;
std::cout << " " << "-r|--return" << "\t\t" << "Return a loaned book" << 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|--verbose" << "\t\t" << "Increase verbosity, can be set multiple times" << std::endl;
std::cout << " " << "-V|--version" << "\t\t" << "Display libgourou version" << std::endl; std::cout << " " << "-V|--version" << "\t\t" << "Display libgourou version" << std::endl;
std::cout << " " << "-h|--help" << "\t\t" << "This help" << std::endl; std::cout << " " << "-h|--help" << "\t\t" << "This help" << std::endl;
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 << 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 << " * Current directory" << std::endl;
std::cout << " * .adept" << std::endl; std::cout << " * .adept" << std::endl;
std::cout << " * adobe-digital-editions directory" << std::endl; std::cout << " * adobe-digital-editions directory" << std::endl;
@ -365,10 +385,11 @@ int main(int argc, char** argv)
while (1) { while (1) {
int option_index = 0; int option_index = 0;
static struct option long_options[] = { static struct option long_options[] = {
{"activation-dir", required_argument, 0, 'd' }, {"adept-directory", required_argument, 0, 'D' },
{"list", no_argument, 0, 'l' }, {"list", no_argument, 0, 'l' },
{"return", no_argument, 0, 'r' }, {"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' }, {"verbose", no_argument, 0, 'v' },
{"version", no_argument, 0, 'V' }, {"version", no_argument, 0, 'V' },
{"help", no_argument, 0, 'h' }, {"help", no_argument, 0, 'h' },
@ -381,8 +402,8 @@ int main(int argc, char** argv)
break; break;
switch (c) { switch (c) {
case 'd': case 'D':
activationDir = optarg; adeptDir = optarg;
break; break;
case 'l': case 'l':
list = true; list = true;
@ -392,10 +413,13 @@ int main(int argc, char** argv)
returnID = optarg; returnID = optarg;
actions++; actions++;
break; break;
case 'D': case 'd':
deleteID = optarg; deleteID = optarg;
actions++; actions++;
break; break;
case 'N':
notify = false;
break;
case 'v': case 'v':
verbose++; verbose++;
break; break;
@ -432,9 +456,9 @@ int main(int argc, char** argv)
{ {
orig = *files[i]; 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()); filename = strdup(path.c_str());
} }
else else
@ -450,17 +474,17 @@ int main(int argc, char** argv)
if (hasErrors) if (hasErrors)
{ {
// In case of activation dir was provided by user // In case of adept dir was provided by user
activationDir = 0; adeptDir = 0;
goto end; goto end;
} }
if (activationDir) if (adeptDir)
activationDir = strdup(activationDir); // For below free adeptDir = strdup(adeptDir); // For below free
else else
{ {
activationDir = strdup(deviceFile); adeptDir = strdup(deviceFile);
activationDir = dirname(activationDir); adeptDir = dirname(adeptDir);
} }
ret = loanMGT.run(); ret = loanMGT.run();
@ -472,8 +496,8 @@ end:
free((void*)*files[i]); free((void*)*files[i]);
} }
if (activationDir) if (adeptDir)
free(activationDir); free(adeptDir);
return ret; return ret;
} }

View file

@ -27,6 +27,7 @@
*/ */
#include <getopt.h> #include <getopt.h>
#include <libgen.h>
#include <iostream> #include <iostream>
@ -80,16 +81,13 @@ public:
gourou::DRMProcessor processor(&client, deviceFile, activationFile, devicekeyFile); gourou::DRMProcessor processor(&client, deviceFile, activationFile, devicekeyFile);
std::string filename; std::string filename;
if (!outputFile) if (outputFile)
filename = std::string(inputFile);
else
filename = outputFile; filename = outputFile;
else
{
filename = std::string(inputFile);
if (outputDir) if (outputDir)
{
if (!fileExists(outputDir))
mkpath(outputDir);
filename = std::string(outputDir) + "/" + filename; filename = std::string(outputDir) + "/" + filename;
} }
@ -102,6 +100,8 @@ public:
EXCEPTION(gourou::DRM_FORMAT_NOT_SUPPORTED, "Unsupported file format of " << filename); EXCEPTION(gourou::DRM_FORMAT_NOT_SUPPORTED, "Unsupported file format of " << filename);
} }
createPath(filename.c_str());
if (inputFile != filename) if (inputFile != filename)
{ {
unlink(filename.c_str()); unlink(filename.c_str());
@ -141,22 +141,30 @@ public:
static void usage(const char* cmd) 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 << "Global Options:" << std::endl;
std::cout << " " << "-a|--activation-file" << "\t" << "activation.xml file from eReader" << std::endl; std::cout << " " << "-O|--output-dir" << "\t" << "Optional output directory were to put result (default ./) (not compatible with -o)" << 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-file" << "\t" << "Optional output filename (default inplace DRM removal>) (not compatible with -O)" << std::endl;
std::cout << " " << "-O|--output-dir" << "\t" << "Optional output directory were to put result (default ./)" << std::endl; std::cout << " " << "-f|--input-file" << "\t" << "Backward compatibility: EPUB/PDF file to process" << std::endl;
std::cout << " " << "-o|--output-file" << "\t" << "Optional output filename (default inplace DRM removal>)" << std::endl;
std::cout << " " << "-f|--input-file" << "\t" << "EPUB/PDF file to process" << std::endl;
std::cout << " " << "-v|--verbose" << "\t\t" << "Increase verbosity, can be set multiple times" << std::endl; std::cout << " " << "-v|--verbose" << "\t\t" << "Increase verbosity, can be set multiple times" << std::endl;
std::cout << " " << "-V|--version" << "\t\t" << "Display libgourou version" << std::endl; std::cout << " " << "-V|--version" << "\t\t" << "Display libgourou version" << std::endl;
std::cout << " " << "-h|--help" << "\t\t" << "This help" << std::endl; std::cout << " " << "-h|--help" << "\t\t" << "This help" << std::endl;
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 << 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 << "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 << " * Current directory" << std::endl;
std::cout << " * .adept" << std::endl; std::cout << " * .adept" << std::endl;
std::cout << " * adobe-digital-editions directory" << 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}; const char** files[] = {&devicekeyFile, &deviceFile, &activationFile};
int verbose = gourou::DRMProcessor::getLogLevel(); int verbose = gourou::DRMProcessor::getLogLevel();
std::string _deviceFile, _activationFile, _devicekeyFile;
while (1) { while (1) {
int option_index = 0; int option_index = 0;
static struct option long_options[] = { static struct option long_options[] = {
{"adept-directory", required_argument, 0, 'D' },
{"device-file", required_argument, 0, 'd' }, {"device-file", required_argument, 0, 'd' },
{"activation-file", required_argument, 0, 'a' }, {"activation-file", required_argument, 0, 'a' },
{"device-key-file", required_argument, 0, 'k' }, {"device-key-file", required_argument, 0, 'k' },
@ -186,12 +196,20 @@ int main(int argc, char** argv)
{0, 0, 0, 0 } {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); long_options, &option_index);
if (c == -1) if (c == -1)
break; break;
switch (c) { 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': case 'd':
deviceFile = optarg; deviceFile = optarg;
break; break;
@ -230,6 +248,9 @@ int main(int argc, char** argv)
gourou::DRMProcessor::setLogLevel(verbose); gourou::DRMProcessor::setLogLevel(verbose);
if (optind == argc-1)
inputFile = argv[optind];
if (!inputFile || (outputDir && !outputDir[0]) || if (!inputFile || (outputDir && !outputDir[0]) ||
(outputFile && !outputFile[0])) (outputFile && !outputFile[0]))
{ {
@ -237,6 +258,12 @@ int main(int argc, char** argv)
return -1; return -1;
} }
if (outputDir && outputFile)
{
std::cout << "Error : you cannot use both -o and -O" << std::endl;
return -1;
}
ADEPTRemove remover; ADEPTRemove remover;
int i; int i;

View file

@ -30,6 +30,9 @@
#include <algorithm> #include <algorithm>
#include <cctype> #include <cctype>
#include <locale> #include <locale>
#include <stdlib.h>
#define OPENSSL_NO_DEPRECATED 1
#include <openssl/rand.h> #include <openssl/rand.h>
#include <openssl/pkcs12.h> #include <openssl/pkcs12.h>
@ -44,21 +47,44 @@
#include <zip.h> #include <zip.h>
#include <libgourou_common.h> #include <libgourou_common.h>
#include <libgourou_log.h>
#include "drmprocessorclientimpl.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(): DRMProcessorClientImpl::DRMProcessorClientImpl():
legacy(0), deflt(0) legacy(0), deflt(0)
{ {
#if OPENSSL_VERSION_MAJOR >= 3 #if OPENSSL_VERSION_MAJOR >= 3
legacy = OSSL_PROVIDER_load(NULL, "legacy"); legacy = OSSL_PROVIDER_load(NULL, "legacy");
if (!legacy) if (!legacy)
{
ERR_print_errors_cb(error_cb, NULL);
EXCEPTION(gourou::CLIENT_OSSL_ERROR, "Error, OpenSSL legacy provider not available"); EXCEPTION(gourou::CLIENT_OSSL_ERROR, "Error, OpenSSL legacy provider not available");
}
deflt = OSSL_PROVIDER_load(NULL, "default"); deflt = OSSL_PROVIDER_load(NULL, "default");
if (!deflt) if (!deflt)
EXCEPTION(gourou::CLIENT_OSSL_ERROR, "Error, OpenSSL default provider not available"); EXCEPTION(gourou::CLIENT_OSSL_ERROR, "Error, OpenSSL default provider not available");
OSSL_PROVIDER_load(NULL, "base");
#endif #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() DRMProcessorClientImpl::~DRMProcessorClientImpl()
@ -70,6 +96,8 @@ DRMProcessorClientImpl::~DRMProcessorClientImpl()
if (deflt) if (deflt)
OSSL_PROVIDER_unload(deflt); OSSL_PROVIDER_unload(deflt);
#endif #endif
unlink(cookiejar);
} }
/* Digest interface */ /* Digest interface */
@ -81,32 +109,32 @@ void* DRMProcessorClientImpl::createDigest(const std::string& digestName)
if (EVP_DigestInit(md_ctx, md) != 1) if (EVP_DigestInit(md_ctx, md) != 1)
{ {
EVP_MD_CTX_free(md_ctx); EVP_MD_CTX_free(md_ctx);
return 0; EXCEPTION(gourou::CLIENT_DIGEST_ERROR, ERR_error_string(ERR_get_error(), NULL));
} }
return md_ctx; 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); int res = EVP_DigestFinal((EVP_MD_CTX *)handler, digestOut, NULL);
EVP_MD_CTX_free((EVP_MD_CTX *)handler); EVP_MD_CTX_free((EVP_MD_CTX *)handler);
return (res == 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); void* handler = createDigest(digestName);
if (!handler) digestUpdate(handler, data, length);
return -1; digestFinalize(handler, digestOut);
if (digestUpdate(handler, data, length))
return -1;
return digestFinalize(handler, digestOut);
} }
/* Random interface */ /* Random interface */
@ -119,18 +147,23 @@ void DRMProcessorClientImpl::randBytes(unsigned char* bytesOut, unsigned int len
#define HTTP_REQ_MAX_RETRY 5 #define HTTP_REQ_MAX_RETRY 5
#define DISPLAY_THRESHOLD 10*1024 // Threshold to display download progression #define DISPLAY_THRESHOLD 10*1024 // Threshold to display download progression
static unsigned downloadedBytes; static unsigned downloadedBytes;
static int lastPercent = -1;
static int downloadProgress(void *clientp, curl_off_t dltotal, curl_off_t dlnow, static int downloadProgress(void *clientp, curl_off_t dltotal, curl_off_t dlnow,
curl_off_t ultotal, curl_off_t ulnow) curl_off_t ultotal, curl_off_t ulnow)
{ {
// For "big" files only // 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; int percent = 0;
if (dltotal) if (dltotal)
percent = (dlnow * 100) / dltotal; percent = (dlnow * 100) / dltotal;
if (lastPercent != percent)
{
std::cout << "\rDownload " << percent << "%" << std::flush; std::cout << "\rDownload " << percent << "%" << std::flush;
lastPercent = percent;
}
} }
return 0; return 0;
@ -174,7 +207,7 @@ static size_t curlHeaders(char *buffer, size_t size, size_t nitems, void *userda
(*responseHeaders)[key] = value; (*responseHeaders)[key] = value;
if (gourou::logLevel >= gourou::DEBUG) if (gourou::logLevel >= gourou::LG_LOG_DEBUG)
std::cout << key << " : " << value << std::endl; std::cout << key << " : " << value << std::endl;
} }
@ -189,10 +222,10 @@ std::string DRMProcessorClientImpl::sendHTTPRequest(const std::string& URL, cons
if (!responseHeaders) if (!responseHeaders)
responseHeaders = &localHeaders; responseHeaders = &localHeaders;
GOUROU_LOG(gourou::INFO, "Send request to " << URL); GOUROU_LOG(INFO, "Send request to " << URL);
if (POSTData.size()) if (POSTData.size())
{ {
GOUROU_LOG(gourou::DEBUG, "<<< " << std::endl << POSTData); GOUROU_LOG(DEBUG, "<<< " << std::endl << POSTData);
} }
unsigned prevDownloadedBytes; unsigned prevDownloadedBytes;
@ -202,11 +235,11 @@ std::string DRMProcessorClientImpl::sendHTTPRequest(const std::string& URL, cons
struct stat _stat; struct stat _stat;
if (!fstat(fd, &_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; downloadedBytes = _stat.st_size;
} }
else 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(); CURL *curl = curl_easy_init();
@ -226,6 +259,7 @@ std::string DRMProcessorClientImpl::sendHTTPRequest(const std::string& URL, cons
} }
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, list); curl_easy_setopt(curl, CURLOPT_HTTPHEADER, list);
curl_easy_setopt(curl, CURLOPT_COOKIEJAR, cookiejar);
if (POSTData.size()) if (POSTData.size())
{ {
@ -250,6 +284,7 @@ std::string DRMProcessorClientImpl::sendHTTPRequest(const std::string& URL, cons
curl_easy_setopt(curl, CURLOPT_XFERINFOFUNCTION, downloadProgress); curl_easy_setopt(curl, CURLOPT_XFERINFOFUNCTION, downloadProgress);
curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 0); curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 0);
lastPercent = -1;
for (int i=0; i<HTTP_REQ_MAX_RETRY; i++) for (int i=0; i<HTTP_REQ_MAX_RETRY; i++)
{ {
@ -262,7 +297,7 @@ std::string DRMProcessorClientImpl::sendHTTPRequest(const std::string& URL, cons
// Connexion failed, wait & retry // Connexion failed, wait & retry
if (res == CURLE_COULDNT_CONNECT) 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 // Transfer failed but some data has been received
// --> try again without incrementing tries // --> try again without incrementing tries
@ -270,11 +305,11 @@ std::string DRMProcessorClientImpl::sendHTTPRequest(const std::string& URL, cons
{ {
if (prevDownloadedBytes != downloadedBytes) 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--; i--;
} }
else 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 // Other error --> fail
else else
@ -285,57 +320,120 @@ std::string DRMProcessorClientImpl::sendHTTPRequest(const std::string& URL, cons
} }
curl_slist_free_all(list); curl_slist_free_all(list);
long http_code = 400;
curl_easy_getinfo (curl, CURLINFO_RESPONSE_CODE, &http_code);
curl_easy_cleanup(curl); curl_easy_cleanup(curl);
if (res != CURLE_OK) if (res != CURLE_OK)
EXCEPTION(gourou::CLIENT_NETWORK_ERROR, "Error " << curl_easy_strerror(res)); 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) && if ((downloadedBytes >= DISPLAY_THRESHOLD || replyData.size() >= DISPLAY_THRESHOLD) &&
gourou::logLevel >= gourou::WARN) gourou::logLevel >= gourou::LG_LOG_WARN)
std::cout << std::endl; std::cout << std::endl;
if ((*responseHeaders)["Content-Type"] == "application/vnd.adobe.adept+xml") 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()); 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, void DRMProcessorClientImpl::RSAPrivateEncrypt(const unsigned char* RSAKey, unsigned int RSAKeyLength,
const RSA_KEY_TYPE keyType, const std::string& password, const RSA_KEY_TYPE keyType, const std::string& password,
const unsigned char* data, unsigned dataLength, const unsigned char* data, unsigned dataLength,
unsigned char* res) unsigned char* res)
{ {
PKCS12 * pkcs12; PKCS12 * pkcs12;
EVP_PKEY* pkey; EVP_PKEY_CTX *ctx;
X509* cert; EVP_PKEY* pkey = NULL;
STACK_OF(X509)* ca; size_t outlen;
RSA * rsa; unsigned char* tmp;
int ret;
pkcs12 = d2i_PKCS12(NULL, &RSAKey, RSAKeyLength); pkcs12 = d2i_PKCS12(NULL, &RSAKey, RSAKeyLength);
if (!pkcs12) if (!pkcs12)
EXCEPTION(gourou::CLIENT_INVALID_PKCS12, ERR_error_string(ERR_get_error(), NULL)); EXCEPTION(gourou::CLIENT_INVALID_PKCS12, ERR_error_string(ERR_get_error(), NULL));
PKCS12_parse(pkcs12, password.c_str(), &pkey, &cert, &ca); if (PKCS12_parse(pkcs12, password.c_str(), &pkey, NULL, NULL) <= 0)
if (!pkey)
EXCEPTION(gourou::CLIENT_INVALID_PKCS12, ERR_error_string(ERR_get_error(), NULL)); EXCEPTION(gourou::CLIENT_INVALID_PKCS12, ERR_error_string(ERR_get_error(), NULL));
rsa = EVP_PKEY_get1_RSA(pkey); outlen = EVP_PKEY_get_size(pkey);
int ret = RSA_private_encrypt(dataLength, data, res, rsa, RSA_PKCS1_PADDING); ctx = EVP_PKEY_CTX_new(pkey, NULL);
if (ret < 0) /* Use RSA private key */
if (EVP_PKEY_decrypt_init(ctx) <= 0)
EXCEPTION(gourou::CLIENT_RSA_ERROR, ERR_error_string(ERR_get_error(), NULL)); EXCEPTION(gourou::CLIENT_RSA_ERROR, ERR_error_string(ERR_get_error(), NULL));
if (gourou::logLevel >= gourou::DEBUG) if (EVP_PKEY_CTX_set_rsa_padding(ctx, RSA_NO_PADDING) <= 0)
{ EXCEPTION(gourou::CLIENT_RSA_ERROR, ERR_error_string(ERR_get_error(), NULL));
printf("Encrypted : ");
for(int i=0; i<ret; i++) tmp = (unsigned char*)malloc(outlen);
printf("%02x ", res[i]);
printf("\n"); /* 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, void DRMProcessorClientImpl::RSAPrivateDecrypt(const unsigned char* RSAKey, unsigned int RSAKeyLength,
@ -349,24 +447,30 @@ void DRMProcessorClientImpl::RSAPrivateDecrypt(const unsigned char* RSAKey, unsi
if (!p8inf) if (!p8inf)
EXCEPTION(gourou::CLIENT_INVALID_PKCS8, ERR_error_string(ERR_get_error(), NULL)); EXCEPTION(gourou::CLIENT_INVALID_PKCS8, ERR_error_string(ERR_get_error(), NULL));
EVP_PKEY_CTX *ctx;
EVP_PKEY* pkey = EVP_PKCS82PKEY(p8inf); EVP_PKEY* pkey = EVP_PKCS82PKEY(p8inf);
RSA * rsa; size_t outlen = dataLength;
int ret; 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)); EXCEPTION(gourou::CLIENT_RSA_ERROR, ERR_error_string(ERR_get_error(), NULL));
if (gourou::logLevel >= gourou::DEBUG) if (EVP_PKEY_CTX_set_rsa_padding(ctx, RSA_NO_PADDING) <= 0)
{ EXCEPTION(gourou::CLIENT_RSA_ERROR, ERR_error_string(ERR_get_error(), NULL));
printf("Decrypted : ");
for(int i=0; i<ret; i++) ret = EVP_PKEY_decrypt(ctx, res, &outlen, data, dataLength);
printf("%02x ", res[i]);
printf("\n"); 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, void DRMProcessorClientImpl::RSAPublicEncrypt(const unsigned char* RSAKey, unsigned int RSAKeyLength,
@ -374,18 +478,44 @@ void DRMProcessorClientImpl::RSAPublicEncrypt(const unsigned char* RSAKey, unsig
const unsigned char* data, unsigned dataLength, const unsigned char* data, unsigned dataLength,
unsigned char* res) unsigned char* res)
{ {
size_t outlen;
unsigned char* tmp;
X509 * x509 = d2i_X509(0, &RSAKey, RSAKeyLength); X509 * x509 = d2i_X509(0, &RSAKey, RSAKeyLength);
if (!x509) if (!x509)
EXCEPTION(gourou::CLIENT_INVALID_CERTIFICATE, "Invalid certificate"); EXCEPTION(gourou::CLIENT_INVALID_CERTIFICATE, "Invalid certificate");
EVP_PKEY * evpKey = X509_get_pubkey(x509); EVP_PKEY_CTX *ctx;
RSA* rsa = EVP_PKEY_get1_RSA(evpKey); EVP_PKEY * pkey = X509_get_pubkey(x509);
EVP_PKEY_free(evpKey);
if (!rsa) if (!pkey)
EXCEPTION(gourou::CLIENT_NO_PRIV_KEY, "No private key in certificate"); 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) if (ret < 0)
EXCEPTION(gourou::CLIENT_RSA_ERROR, ERR_error_string(ERR_get_error(), NULL)); EXCEPTION(gourou::CLIENT_RSA_ERROR, ERR_error_string(ERR_get_error(), NULL));
} }
@ -393,42 +523,45 @@ void DRMProcessorClientImpl::RSAPublicEncrypt(const unsigned char* RSAKey, unsig
void* DRMProcessorClientImpl::generateRSAKey(int keyLengthBits) void* DRMProcessorClientImpl::generateRSAKey(int keyLengthBits)
{ {
BIGNUM * bn = BN_new(); 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); 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); BN_free(bn);
return rsa; return key;
} }
void DRMProcessorClientImpl::destroyRSAHandler(void* handler) void DRMProcessorClientImpl::destroyRSAHandler(void* handler)
{ {
RSA_free((RSA*)handler); free(handler);
} }
void DRMProcessorClientImpl::extractRSAPublicKey(void* handler, unsigned char** keyOut, unsigned int* keyOutLength) 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 *x509_pubkey = 0;
X509_PUBKEY_set(&x509_pubkey, evpKey); X509_PUBKEY_set(&x509_pubkey, (EVP_PKEY*)handler);
*keyOutLength = i2d_X509_PUBKEY(x509_pubkey, keyOut); *keyOutLength = i2d_X509_PUBKEY(x509_pubkey, keyOut);
X509_PUBKEY_free(x509_pubkey); X509_PUBKEY_free(x509_pubkey);
EVP_PKEY_free(evpKey);
} }
void DRMProcessorClientImpl::extractRSAPrivateKey(void* handler, unsigned char** keyOut, unsigned int* keyOutLength) void DRMProcessorClientImpl::extractRSAPrivateKey(void* handler, unsigned char** keyOut, unsigned int* keyOutLength)
{ {
EVP_PKEY * evpKey = EVP_PKEY_new(); PKCS8_PRIV_KEY_INFO * privKey = EVP_PKEY2PKCS8((EVP_PKEY*)handler);
EVP_PKEY_set1_RSA(evpKey, (RSA*)handler);
PKCS8_PRIV_KEY_INFO * privKey = EVP_PKEY2PKCS8(evpKey);
*keyOutLength = i2d_PKCS8_PRIV_KEY_INFO(privKey, keyOut); *keyOutLength = i2d_PKCS8_PRIV_KEY_INFO(privKey, keyOut);
PKCS8_PRIV_KEY_INFO_free(privKey); PKCS8_PRIV_KEY_INFO_free(privKey);
EVP_PKEY_free(evpKey);
} }
void DRMProcessorClientImpl::extractCertificate(const unsigned char* RSAKey, unsigned int RSAKeyLength, void DRMProcessorClientImpl::extractCertificate(const unsigned char* RSAKey, unsigned int RSAKeyLength,
@ -438,12 +571,11 @@ void DRMProcessorClientImpl::extractCertificate(const unsigned char* RSAKey, uns
PKCS12 * pkcs12; PKCS12 * pkcs12;
EVP_PKEY* pkey = 0; EVP_PKEY* pkey = 0;
X509* cert = 0; X509* cert = 0;
STACK_OF(X509)* ca;
pkcs12 = d2i_PKCS12(NULL, &RSAKey, RSAKeyLength); pkcs12 = d2i_PKCS12(NULL, &RSAKey, RSAKeyLength);
if (!pkcs12) if (!pkcs12)
EXCEPTION(gourou::CLIENT_INVALID_PKCS12, ERR_error_string(ERR_get_error(), NULL)); 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) if (!cert)
EXCEPTION(gourou::CLIENT_INVALID_PKCS12, ERR_error_string(ERR_get_error(), NULL)); EXCEPTION(gourou::CLIENT_INVALID_PKCS12, ERR_error_string(ERR_get_error(), NULL));
@ -454,24 +586,27 @@ void DRMProcessorClientImpl::extractCertificate(const unsigned char* RSAKey, uns
} }
/* Crypto interface */ /* 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* key, unsigned int keyLength,
const unsigned char* iv, unsigned int ivLength, const unsigned char* iv, unsigned int ivLength,
const unsigned char* dataIn, unsigned int dataInLength, const unsigned char* dataIn, unsigned int dataInLength,
unsigned char* dataOut, unsigned int* dataOutLength) unsigned char* dataOut, unsigned int* dataOutLength)
{ {
void* handler = EncryptInit(algo, chaining, key, keyLength, iv, ivLength); void* handler = encryptInit(algo, chaining, key, keyLength, iv, ivLength);
EncryptUpdate(handler, dataIn, dataInLength, dataOut, dataOutLength); encryptUpdate(handler, dataIn, dataInLength, dataOut, dataOutLength);
EncryptFinalize(handler, dataOut+*dataOutLength, 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* key, unsigned int keyLength,
const unsigned char* iv, unsigned int ivLength) const unsigned char* iv, unsigned int ivLength)
{ {
EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new(); EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new();
int ret = 0;
if (algo == ALGO_AES) switch (algo)
{
case ALGO_AES:
{ {
switch(keyLength) switch(keyLength)
{ {
@ -479,10 +614,10 @@ void* DRMProcessorClientImpl::EncryptInit(CRYPTO_ALGO algo, CHAINING_MODE chaini
switch(chaining) switch(chaining)
{ {
case CHAIN_ECB: case CHAIN_ECB:
EVP_EncryptInit(ctx, EVP_aes_128_ecb(), key, iv); ret = EVP_EncryptInit(ctx, EVP_aes_128_ecb(), key, iv);
break; break;
case CHAIN_CBC: case CHAIN_CBC:
EVP_EncryptInit(ctx, EVP_aes_128_cbc(), key, iv); ret = EVP_EncryptInit(ctx, EVP_aes_128_cbc(), key, iv);
break; break;
default: default:
EXCEPTION(gourou::CLIENT_BAD_CHAINING, "Unknown chaining mode " << chaining); EXCEPTION(gourou::CLIENT_BAD_CHAINING, "Unknown chaining mode " << chaining);
@ -492,26 +627,39 @@ void* DRMProcessorClientImpl::EncryptInit(CRYPTO_ALGO algo, CHAINING_MODE chaini
EVP_CIPHER_CTX_free(ctx); EVP_CIPHER_CTX_free(ctx);
EXCEPTION(gourou::CLIENT_BAD_KEY_SIZE, "Invalid key size " << keyLength); EXCEPTION(gourou::CLIENT_BAD_KEY_SIZE, "Invalid key size " << keyLength);
} }
break;
} }
else if (algo == ALGO_RC4) case ALGO_RC4:
{ {
if (keyLength != 16) if (keyLength != 16)
{ {
EVP_CIPHER_CTX_free(ctx); EVP_CIPHER_CTX_free(ctx);
EXCEPTION(gourou::CLIENT_BAD_KEY_SIZE, "Invalid key size " << keyLength); EXCEPTION(gourou::CLIENT_BAD_KEY_SIZE, "Invalid key size " << keyLength);
} }
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; 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* key, unsigned int keyLength,
const unsigned char* iv, unsigned int ivLength) const unsigned char* iv, unsigned int ivLength)
{ {
EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new(); EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new();
int ret = 0;
if (algo == ALGO_AES) switch(algo)
{
case ALGO_AES:
{ {
switch(keyLength) switch(keyLength)
{ {
@ -519,10 +667,10 @@ void* DRMProcessorClientImpl::DecryptInit(CRYPTO_ALGO algo, CHAINING_MODE chaini
switch(chaining) switch(chaining)
{ {
case CHAIN_ECB: case CHAIN_ECB:
EVP_DecryptInit(ctx, EVP_aes_128_ecb(), key, iv); ret = EVP_DecryptInit(ctx, EVP_aes_128_ecb(), key, iv);
break; break;
case CHAIN_CBC: case CHAIN_CBC:
EVP_DecryptInit(ctx, EVP_aes_128_cbc(), key, iv); ret = EVP_DecryptInit(ctx, EVP_aes_128_cbc(), key, iv);
break; break;
default: default:
EXCEPTION(gourou::CLIENT_BAD_CHAINING, "Unknown chaining mode " << chaining); EXCEPTION(gourou::CLIENT_BAD_CHAINING, "Unknown chaining mode " << chaining);
@ -532,58 +680,81 @@ void* DRMProcessorClientImpl::DecryptInit(CRYPTO_ALGO algo, CHAINING_MODE chaini
EVP_CIPHER_CTX_free(ctx); EVP_CIPHER_CTX_free(ctx);
EXCEPTION(gourou::CLIENT_BAD_KEY_SIZE, "Invalid key size " << keyLength); EXCEPTION(gourou::CLIENT_BAD_KEY_SIZE, "Invalid key size " << keyLength);
} }
break;
} }
else if (algo == ALGO_RC4) case ALGO_RC4:
{ {
if (keyLength != 16) if (keyLength != 16)
{ {
EVP_CIPHER_CTX_free(ctx); EVP_CIPHER_CTX_free(ctx);
EXCEPTION(gourou::CLIENT_BAD_KEY_SIZE, "Invalid key size " << keyLength); EXCEPTION(gourou::CLIENT_BAD_KEY_SIZE, "Invalid key size " << keyLength);
} }
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; 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) 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) unsigned char* dataOut, unsigned int* dataOutLength)
{ {
int len; int len, ret;
EVP_EncryptFinal_ex((EVP_CIPHER_CTX*)handler, dataOut, &len);
ret = EVP_EncryptFinal_ex((EVP_CIPHER_CTX*)handler, dataOut, &len);
*dataOutLength += len; *dataOutLength += len;
EVP_CIPHER_CTX_free((EVP_CIPHER_CTX*)handler); 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* key, unsigned int keyLength,
const unsigned char* iv, unsigned int ivLength, const unsigned char* iv, unsigned int ivLength,
const unsigned char* dataIn, unsigned int dataInLength, const unsigned char* dataIn, unsigned int dataInLength,
unsigned char* dataOut, unsigned int* dataOutLength) unsigned char* dataOut, unsigned int* dataOutLength)
{ {
void* handler = DecryptInit(algo, chaining, key, keyLength, iv, ivLength); void* handler = decryptInit(algo, chaining, key, keyLength, iv, ivLength);
DecryptUpdate(handler, dataIn, dataInLength, dataOut, dataOutLength); decryptUpdate(handler, dataIn, dataInLength, dataOut, dataOutLength);
DecryptFinalize(handler, dataOut+*dataOutLength, 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) 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; int len, ret;
EVP_DecryptFinal_ex((EVP_CIPHER_CTX*)handler, dataOut, &len);
ret = EVP_DecryptFinal_ex((EVP_CIPHER_CTX*)handler, dataOut, &len);
*dataOutLength += len; *dataOutLength += len;
EVP_CIPHER_CTX_free((EVP_CIPHER_CTX*)handler); 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) void* DRMProcessorClientImpl::zipOpen(const std::string& path)

View file

@ -45,9 +45,9 @@ public:
/* Digest interface */ /* Digest interface */
virtual void* createDigest(const std::string& digestName); virtual void* createDigest(const std::string& digestName);
virtual int digestUpdate(void* handler, unsigned char* data, unsigned int length); virtual void digestUpdate(void* handler, unsigned char* data, unsigned int length);
virtual int digestFinalize(void* handler,unsigned char* digestOut); virtual void digestFinalize(void* handler,unsigned char* digestOut);
virtual int digest(const std::string& digestName, unsigned char* data, unsigned int length, unsigned char* digestOut); virtual void digest(const std::string& digestName, unsigned char* data, unsigned int length, unsigned char* digestOut);
/* Random interface */ /* Random interface */
virtual void randBytes(unsigned char* bytesOut, unsigned int length); virtual void randBytes(unsigned char* bytesOut, unsigned int length);
@ -80,34 +80,34 @@ public:
unsigned char** certOut, unsigned int* certOutLength); unsigned char** certOut, unsigned int* certOutLength);
/* Crypto interface */ /* 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* key, unsigned int keyLength,
const unsigned char* iv, unsigned int ivLength, const unsigned char* iv, unsigned int ivLength,
const unsigned char* dataIn, unsigned int dataInLength, const unsigned char* dataIn, unsigned int dataInLength,
unsigned char* dataOut, unsigned int* dataOutLength); unsigned char* dataOut, unsigned int* dataOutLength);
virtual void* 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* key, unsigned int keyLength,
const unsigned char* iv=0, unsigned int ivLength=0); 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); 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* key, unsigned int keyLength,
const unsigned char* iv, unsigned int ivLength, const unsigned char* iv, unsigned int ivLength,
const unsigned char* dataIn, unsigned int dataInLength, const unsigned char* dataIn, unsigned int dataInLength,
unsigned char* dataOut, unsigned int* dataOutLength); unsigned char* dataOut, unsigned int* dataOutLength);
virtual void* 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* key, unsigned int keyLength,
const unsigned char* iv=0, unsigned int ivLength=0); 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); 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 */ /* ZIP Interface */
virtual void* zipOpen(const std::string& path); virtual void* zipOpen(const std::string& path);
@ -127,11 +127,19 @@ public:
int wbits=-15, int compressionLevel=8); int wbits=-15, int compressionLevel=8);
private: 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 #if OPENSSL_VERSION_MAJOR >= 3
OSSL_PROVIDER *legacy, *deflt; OSSL_PROVIDER *legacy, *deflt;
#else #else
void *legacy, *deflt; void *legacy, *deflt;
#endif #endif
char cookiejar[64];
}; };
#endif #endif

40
utils/launcher.cpp Normal file
View file

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

View file

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

View file

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

View file

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

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

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

View file

@ -33,6 +33,7 @@
#include <stdio.h> #include <stdio.h>
#include <fcntl.h> #include <fcntl.h>
#include <limits.h> #include <limits.h>
#include <libgen.h>
#include <iostream> #include <iostream>
@ -51,25 +52,39 @@ void version(void)
std::cout << "Current libgourou version : " << gourou::DRMProcessor::VERSION << std::endl ; std::cout << "Current libgourou version : " << gourou::DRMProcessor::VERSION << std::endl ;
} }
bool fileExists(const char* filename) bool pathExists(const char* path)
{ {
struct stat _stat; struct stat _stat;
int ret = stat(filename, &_stat); int ret = stat(path, &_stat);
return (ret == 0); return (ret == 0);
} }
const char* findFile(const char* filename, bool inDefaultDirs) 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); return strdup(filename);
if (!inDefaultDirs) return 0; if (!inDefaultDirs) return 0;
for (int i=0; i<(int)ARRAY_SIZE(defaultDirs); i++) for (int i=0; i<(int)ARRAY_SIZE(defaultDirs); i++)
{ {
std::string path = std::string(defaultDirs[i]) + filename; path = std::string(defaultDirs[i]) + filename;
if (fileExists(path.c_str())) if (pathExists(path.c_str()))
return strdup(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) void fileCopy(const char* in, const char* out)
{ {
char buffer[4096]; char buffer[4096], *_buffer;
int ret, fdIn, fdOut; int ret, ret2, fdIn, fdOut;
fdIn = open(in, O_RDONLY); fdIn = open(in, O_RDONLY);
@ -119,9 +134,31 @@ void fileCopy(const char* in, const char* out)
ret = ::read(fdIn, buffer, sizeof(buffer)); ret = ::read(fdIn, buffer, sizeof(buffer));
if (ret <= 0) if (ret <= 0)
break; 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 (fdIn);
close (fdOut); close (fdOut);
} }
void createPath(const char* filename)
{
char* basepath = strdup(filename);
char* outputDir = dirname(basepath);
if (outputDir && !pathExists(outputDir))
mkpath(outputDir);
free(basepath);
}

View file

@ -50,9 +50,9 @@ void version(void);
const char* findFile(const char* filename, bool inDefaultDirs=true); 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 * @brief Recursively created dir
@ -64,4 +64,9 @@ void mkpath(const char *dir);
*/ */
void fileCopy(const char* in, const char* out); void fileCopy(const char* in, const char* out);
/**
* @brief Create intermediate directories if it does not exists
*/
void createPath(const char* filename);
#endif #endif