diff --git a/.gitignore b/.gitignore index c806eaa..4c47778 100644 --- a/.gitignore +++ b/.gitignore @@ -1,11 +1,8 @@ obj lib -*.o *.a *.so *~ utils/acsmdownloader -utils/adept_activate -utils/adept_remove -utils/adept_loan_mgt -.adept* +utils/activate +.adept diff --git a/Makefile b/Makefile index d39caa9..e6426f0 100644 --- a/Makefile +++ b/Makefile @@ -1,125 +1,55 @@ -PREFIX ?= /usr/local -LIBDIR ?= /lib -INCDIR ?= /include AR ?= $(CROSS)ar CXX ?= $(CROSS)g++ -UPDFPARSERLIB = ./lib/updfparser/libupdfparser.a - -CXXFLAGS += -Wall -fPIC -I./include -I./usr/include/pugixml -I./lib/updfparser/include -LDFLAGS = -lpugixml - -VERSION := $(shell cat include/libgourou.h |grep LIBGOUROU_VERSION|cut -d '"' -f2) - -UNAME := $(shell uname -s) - -BUILD_STATIC ?= 0 -BUILD_SHARED ?= 1 -BUILD_UTILS ?= 1 - -TARGETS = -TARGET_LIBRARIES = -ifneq ($(STATIC_UTILS),) - BUILD_STATIC=1 -endif -ifneq ($(BUILD_STATIC), 0) - TARGETS += libgourou.a - TARGET_LIBRARIES += libgourou.a - STATIC_UTILS=1 -endif -ifneq ($(BUILD_SHARED), 0) - ifeq ($(UNAME), Darwin) - TARGETS += libgourou.dylib - TARGET_LIBRARIES += libgourou.dylib libgourou.dylib.$(VERSION) - else - TARGETS += libgourou.so - TARGET_LIBRARIES += libgourou.so libgourou.so.$(VERSION) - endif -endif -ifneq ($(BUILD_UTILS), 0) - TARGETS += build_utils -endif - +CXXFLAGS=-Wall -fPIC -I./include -I./lib -I./lib/pugixml/src/ +LDFLAGS= ifneq ($(DEBUG),) -CXXFLAGS += -ggdb -O0 -DDEBUG +CXXFLAGS += -ggdb -O0 else CXXFLAGS += -O2 endif -ifneq ($(STATIC_NONCE),) -CXXFLAGS += -DSTATIC_NONCE=1 -endif - SRCDIR := src +INCDIR := inc BUILDDIR := obj +TARGETDIR := bin SRCEXT := cpp OBJEXT := o -SOURCES = src/libgourou.cpp src/user.cpp src/device.cpp src/fulfillment_item.cpp src/loan_token.cpp src/bytearray.cpp +SOURCES=src/libgourou.cpp src/user.cpp src/device.cpp src/fulfillment_item.cpp src/bytearray.cpp src/pugixml.cpp OBJECTS := $(patsubst $(SRCDIR)/%,$(BUILDDIR)/%,$(SOURCES:.$(SRCEXT)=.$(OBJEXT))) -all: version lib obj $(TARGETS) +.PHONY: utils -version: - @echo "Building libgourou $(VERSION)" +all: lib obj libgourou utils lib: mkdir lib ./scripts/setup.sh -update_lib: - ./scripts/update_lib.sh - obj: mkdir obj $(BUILDDIR)/%.$(OBJEXT): $(SRCDIR)/%.$(SRCEXT) $(CXX) $(CXXFLAGS) -c $^ -o $@ -libgourou: $(TARGET_LIBRARIES) +libgourou: libgourou.a libgourou.so -libgourou.a: $(OBJECTS) $(UPDFPARSERLIB) - $(AR) rcs --thin $@ $^ +libgourou.a: $(OBJECTS) + $(AR) crs $@ obj/*.o -libgourou.so.$(VERSION): $(OBJECTS) $(UPDFPARSERLIB) - $(CXX) $^ -Wl,-soname,$@ $(LDFLAGS) -o $@ -shared +libgourou.so: libgourou.a + $(CXX) obj/*.o $(LDFLAGS) -o $@ -shared -libgourou.so: libgourou.so.$(VERSION) - ln -f -s $^ $@ - -libgourou.dylib.$(VERSION): $(OBJECTS) $(UPDFPARSERLIB) - $(CXX) $^ $(LDFLAGS) -o $@ -shared - -libgourou.dylib: libgourou.dylib.$(VERSION) - ln -f -s $^ $@ - -build_utils: $(TARGET_LIBRARIES) - $(MAKE) -C utils ROOT=$(PWD) CXX=$(CXX) AR=$(AR) DEBUG=$(DEBUG) STATIC_UTILS=$(STATIC_UTILS) DESTDIR=$(DESTDIR) PREFIX=$(PREFIX) - -install: $(TARGET_LIBRARIES) - install -d $(DESTDIR)$(PREFIX)$(LIBDIR) -# Use cp to preserver symlinks - cp --no-dereference $(TARGET_LIBRARIES) $(DESTDIR)$(PREFIX)$(LIBDIR) - $(MAKE) -C utils ROOT=$(PWD) CXX=$(CXX) AR=$(AR) DEBUG=$(DEBUG) STATIC_UTILS=$(STATIC_UTILS) DESTDIR=$(DESTDIR) PREFIX=$(PREFIX) install - -uninstall: - cd $(DESTDIR)$(PREFIX)/$(LIBDIR) - rm -f $(TARGET_LIBRARIES) libgourou.so.$(VERSION) - cd - - -install_headers: - install -d $(DESTDIR)$(PREFIX)/$(INCDIR)/libgourou - cp --no-dereference include/*.h $(DESTDIR)$(PREFIX)/$(INCDIR)/libgourou - -uninstall_headers: - rm -rf $(DESTDIR)$(PREFIX)/$(INCDIR)/libgourou +utils: + make -C utils ROOT=$(PWD) CXX=$(CXX) AR=$(AR) DEBUG=$(DEBUG) STATIC_UTILS=$(STATIC_UTILS) clean: - rm -rf libgourou.a libgourou.so libgourou.so.$(VERSION)* obj - $(MAKE) -C utils clean + rm -rf libgourou.a libgourou.so obj + make -C utils clean ultraclean: clean rm -rf lib - $(MAKE) -C utils ultraclean + make -C utils ultraclean diff --git a/README.md b/README.md index 41c1dbe..a312f2d 100644 --- a/README.md +++ b/README.md @@ -1,62 +1,45 @@ Introduction ------------ -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. +libgourou is a free implementation of Adobe's ADEPT protocol used to add DRM on ePub files. It overcome the lacks of Adobe support for Linux platforms. Architecture ------------ -Like RMSDK, libgourou has a client/server scheme. All platform specific functions (crypto, network...) have to be implemented in a client class (that derives from DRMProcessorClient) while server implements ADEPT protocol. -A reference implementation using cURL, OpenSSL and libzip is provided (in _utils_ directory). +Like RMSDK, libgourou has a client/server scheme. All platform specific functions (crypto, network...) has to be implemented in a client class (that derives from DRMProcessorClient) while server implements ADEPT protocol. +A reference implementation using Qt, OpenSSL and libzip is provided (in _utils_ directory). -Main functions to use from gourou::DRMProcessor are: +Main fucntions to use from gourou::DRMProcessor are : * Get an ePub from an ACSM file : _fulfill()_ and _download()_ * Create a new device : _createDRMProcessor()_ * Register a new device : _signIn()_ and _activateDevice()_ - * Remove DRM : _removeDRM()_ - * Return loaned book : _returnLoan()_ -You can import configuration from (at least): - * Kobo device : .adept/device.xml, .adept/devicesalt and .adept/activation.xml +You can import configuration from (at least) : + + * Kobo device : .adept/device.xml, .adept/devicesalt and .adept/activation.xml * Bookeen device : .adobe-digital-editions/device.xml, root/devkey.bin and .adobe-digital-editions/activation.xml -Or create a new one. Be careful: there is a limited number of devices that can be created by one account. +Or create a new one. Be careful : there is a limited number of devices that can be created bye one account. -ePub are encrypted using a shared key: one account / multiple devices, so you can create and register a device into your computer and read downloaded (and encrypted) ePub file with your eReader configured using the same AdobeID account. - -For those who 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. +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. Dependencies ------------ -For libgourou: +For libgourou : -_externals_ : + * None - * libpugixml +For utils : -_internals_: - - * uPDFParser - -For utils: - - * libcurl - * openssl + * QT5Core + * QT5Network + * OpenSSL * libzip - * libpugixml - - -External & utils dependencies has to be installed by your package manager (_apt_ for example). -Use _-dev_ flavours to get needed headers. -Internal libraries are automatically fetched and statically compiled during the first compilation. -When you update libgourou's repository, **don't forget to update internal libraries** with: - - make update_lib Compilation @@ -64,78 +47,29 @@ Compilation Use _make_ command - make [CROSS=XXX] [DEBUG=(0*|1)] [STATIC_UTILS=(0*|1)] [BUILD_UTILS=(0|1*)] [BUILD_STATIC=(0*|1)] [BUILD_SHARED=(0|1*)] [all*|clean|ultraclean|build_utils|install|uninstall] + make [CROSS=XXX] [DEBUG=1] [STATIC_UTILS=1] CROSS can define a cross compiler prefix (ie arm-linux-gnueabihf-) DEBUG can be set to compile in DEBUG mode -BUILD_UTILS to build utils or not - STATIC_UTILS to build utils with static library (libgourou.a) instead of default dynamic one (libgourou.so) -BUILD_STATIC build libgourou.a if 1, nothing if 0, can be combined with BUILD_SHARED - -BUILD_SHARED build libgourou.so if 1, nothing if 0, can be combined with BUILD_STATIC - -other variables are DESTDIR and PREFIX to handle destination install directory - -* Default value - Utils ----- -First, add libgourou.so to your LD_LIBRARY_PATH +You can import configuration from your eReader or create a new one with utils/activate : export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$PWD + ./utils/activate -u -You can optionaly specify your .adept directory +Then a _./.adept_ directory is created with all configuration file - export ADEPT_DIR=/home/XXX +To download an ePub : -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 - -Then a _/home//.config/adept_ directory is created with all configuration file - -To download an ePub/PDF: - - ./utils/acsmdownloader - -To export your private key (for DeDRM software): - - ./utils/acsmdownloader --export-private-key [-o adobekey_1.der] - -To remove ADEPT DRM: - - ./utils/adept_remove - -To list loaned books: - - ./utils/adept_loan_mgt [-l] - -To return a loaned book: - - ./utils/adept_loan_mgt -r - - -You can get utils full options description with -h or --help switch - - -Binary packages ---------------- - -Compiled version (and AppImage) of libgourou and utils are available in [Release page](https://forge.soutade.fr/soutade/libgourou/releases) - - -Docker ------- - -A docker image (by bcliang) is available at [https://github.com/bcliang/docker-libgourou/](https://github.com/bcliang/docker-libgourou/) + export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$PWD + ./utils/acsmdownloader -f Copyright @@ -144,6 +78,7 @@ Copyright Grégory Soutadé + License ------- @@ -152,24 +87,8 @@ libgourou : LGPL v3 or later utils : BSD + Special thanks -------------- * _Jens_ for all test samples and utils testing - * _Milian_ for debug & code - * _Berwyn H_ for all test samples, feedbacks, patches and kind donation - - -Donation --------- - -https://www.paypal.com/donate/?hosted_button_id=JD3U6XMZCPHKN - - -Donators --------- - - * _Berwyn H_ - * _bwitt_ - * _Ismail_ - * _Radon_ diff --git a/README_package.md b/README_package.md new file mode 100644 index 0000000..0b6fa9d --- /dev/null +++ b/README_package.md @@ -0,0 +1,68 @@ +Introduction +------------ + +libgourou is a free implementation of Adobe's ADEPT protocol used to add DRM on ePub files. It overcome the lacks of Adobe support for Linux platforms. + + + +Dependencies +------------ + +For libgourou : + + * None + +For utils : + + * QT5Core + * QT5Network + * OpenSSL + * libzip + + + +Utils +----- + +You can import configuration from your eReader or create a new one with activate : + + export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$PWD + ./activate -u + +Then a _./.adept_ directory is created with all configuration file + +To download an ePub : + + export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$PWD + ./acsmdownloader -f + + + +Sources +------- + +http://indefero.soutade.fr/p/libgourou + + + +Copyright +--------- + +Grégory Soutadé + + + +License +------- + +libgourou : LGPL v3 or later + +utils : BSD + + + +Special thanks +-------------- + + * _Jens_ for all test samples and utils testing + \ No newline at end of file diff --git a/include/Base64.h b/include/Base64.h deleted file mode 100644 index 68f0341..0000000 --- a/include/Base64.h +++ /dev/null @@ -1,133 +0,0 @@ -#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 -#include - -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(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(input[i++])]; - uint32_t b = input[i] == '=' ? 0 & i++ : kDecodingTable[static_cast(input[i++])]; - uint32_t c = input[i] == '=' ? 0 & i++ : kDecodingTable[static_cast(input[i++])]; - uint32_t d = input[i] == '=' ? 0 & i++ : kDecodingTable[static_cast(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_ */ diff --git a/include/bytearray.h b/include/bytearray.h index 25c6aa1..0a5ed39 100644 --- a/include/bytearray.h +++ b/include/bytearray.h @@ -32,155 +32,112 @@ namespace gourou * * Data handled is first copied in a newly allocated buffer * and then shared between all copies until last object is destroyed - * (internal reference counter == 0) */ class ByteArray { public: - /** - * @brief Create an empty byte array - * - * @param useMalloc If true, use malloc() instead of new[] for allocation - */ - ByteArray(bool useMalloc=false); + /** + * @brief Create an empty byte array + */ + ByteArray(); - /** - * @brief Create an empty byte array of length bytes - * - * @param length Length of data - * @param useMalloc If true, use malloc() instead of new[] for allocation - */ - ByteArray(unsigned int length, bool useMalloc=false); + /** + * @brief Initialize ByteArray with a copy of data + * + * @param data Data to be copied + * @param length Length of data + */ + ByteArray(const unsigned char* data, unsigned int length); - /** - * @brief Initialize ByteArray with a copy of data - * - * @param data Data to be copied - * @param length Length of data - */ - ByteArray(const unsigned char* data, unsigned int length); + /** + * @brief Initialize ByteArray with a copy of data + * + * @param data Data to be copied + * @param length Optional length of data. If length == -1, it use strlen(data) as length + */ + ByteArray(const char* data, int length=-1); - /** - * @brief Initialize ByteArray with a copy of data - * - * @param data Data to be copied - * @param length Optional length of data. If length == -1, it use strlen(data) as length - */ - ByteArray(const char* data, int length=-1); + /** + * @brief Initialize ByteArray with a copy of str + * + * @param str Use internal data of str + */ + ByteArray(const std::string& str); - /** - * @brief Initialize ByteArray with a copy of str - * - * @param str Use internal data of str - */ - ByteArray(const std::string& str); + ByteArray(const ByteArray& other); + ~ByteArray(); - ByteArray(const ByteArray& other); - ~ByteArray(); + /** + * @brief Encode "other" data into base64 and put it into a ByteArray + */ + static ByteArray fromBase64(const ByteArray& other); - /** - * @brief Encode "other" data into base64 and put it into a ByteArray - */ - static ByteArray fromBase64(const ByteArray& other); + /** + * @brief Encode data into base64 and put it into a ByteArray + * + * @param data Data to be encoded + * @param length Optional length of data. If length == -1, it use strlen(data) as length + */ + static ByteArray fromBase64(const char* data, int length=-1); - /** - * @brief Encode data into base64 and put it into a ByteArray - * - * @param data Data to be encoded - * @param length Optional length of data. If length == -1, it use strlen(data) as length - */ - static ByteArray fromBase64(const char* data, int length=-1); + /** + * @brief Encode str into base64 and put it into a ByteArray + * + * @param str Use internal data of str + */ + static ByteArray fromBase64(const std::string& str); - /** - * @brief Encode str into base64 and put it into a ByteArray - * - * @param str Use internal data of str - */ - static ByteArray fromBase64(const std::string& str); + /** + * @brief Return a string with base64 encoded internal data + */ + std::string toBase64(); - /** - * @brief Return a string with base64 encoded internal data - */ - std::string toBase64(); + /** + * @brief Return a string with human readable hex encoded internal data + */ + std::string toHex(); - /** - * @brief Convert hex string into bytes - * - * @param str Hex string - */ - static ByteArray fromHex(const std::string& str); + /** + * @brief Append a byte to internal data + */ + void append(unsigned char c); - /** - * @brief Return a string with human readable hex encoded internal data - */ - std::string toHex(); + /** + * @brief Append data to internal data + */ + void append(const unsigned char* data, unsigned int length); - /** - * @brief Append a byte to internal data - */ - void append(unsigned char c); + /** + * @brief Append str to internal data + */ + void append(const char* str); - /** - * @brief Append data to internal data - */ - void append(const unsigned char* data, unsigned int length); + /** + * @brief Append str to internal data + */ + void append(const std::string& str); - /** - * @brief Append str to internal data - */ - void append(const char* str); + /** + * @brief Get internal data. Must bot be modified nor freed + */ + const unsigned char* data() {return _data;} - /** - * @brief Append str to internal data - */ - void append(const std::string& str); - - /** - * @brief Get internal data. Must not be freed - */ - unsigned char* data() {return _data;} - - /** - * @brief Get internal data and increment internal reference counter. - * Must bot be freed - */ - unsigned char* takeShadowData() {addRef() ; return _data;} - - /** - * @brief Release shadow data. It can now be freed by ByteArray - */ - void releaseShadowData() {delRef();} - - /** - * @brief Get internal data length - */ - unsigned int length() const {return _length;} - - /** - * @brief Get internal data length - */ - unsigned int size() const {return length();} - - /** - * @brief Increase or decrease internal buffer - * @param length New length of internal buffer - * @param keepData If true copy old data on new buffer, if false, - * create a new buffer with random data - */ - void resize(unsigned int length, bool keepData=true); - - ByteArray& operator=(const ByteArray& other); + /** + * @brief Get internal data length + */ + unsigned int length() {return _length;} + ByteArray& operator=(const ByteArray& other); + private: - void initData(const unsigned char* data, unsigned int length); - void addRef(); - void delRef(); - - bool _useMalloc; - unsigned char* _data; - unsigned int _length; - static std::map refCounter; + void initData(const unsigned char* data, unsigned int length); + void addRef(); + void delRef(); + + const unsigned char* _data; + unsigned int _length; + static std::map refCounter; }; } #endif diff --git a/include/device.h b/include/device.h index 3a88abe..2e01b58 100644 --- a/include/device.h +++ b/include/device.h @@ -30,54 +30,54 @@ namespace gourou class Device { public: - static const int DEVICE_KEY_SIZE = 16; - static const int DEVICE_SERIAL_LEN = 10; + static const int DEVICE_KEY_SIZE = 16; + static const int DEVICE_SERIAL_LEN = 10; - /** - * @brief Main Device constructor - * - * @param processor Instance of DRMProcessor - * @param deviceFile Path of device.xml - * @param deviceKeyFile Path of devicesalt - */ - Device(DRMProcessor* processor, const std::string& deviceFile, const std::string& deviceKeyFile); + /** + * @brief Main Device constructor + * + * @param processor Instance of DRMProcessor + * @param deviceFile Path of device.xml + * @param deviceKeyFile Path of devicesalt + */ + Device(DRMProcessor* processor, const std::string& deviceFile, const std::string& deviceKeyFile); - /** - * @brief Return value of devicesalt file (DEVICE_KEY_SIZE len) - */ - const unsigned char* getDeviceKey(); + /** + * @brief Return value of devicesalt file (DEVICE_KEY_SIZE len) + */ + const unsigned char* getDeviceKey(); - /** - * @brief Get one value of device.xml (deviceClass, deviceSerial, deviceName, deviceType, hobbes, clientOS, clientLocale) - */ - std::string getProperty(const std::string& property, const std::string& _default=std::string("")); - std::string operator[](const std::string& property); - - /** - * @brief Create device.xml and devicesalt files when they did not exists - * - * @param processor Instance of DRMProcessor - * @param dirName Directory where to put files (.adept) - * @param hobbes Hobbes (client version) to set - * @param randomSerial Create a random serial (new device each time) or not (serial computed from machine specs) - */ - static Device* createDevice(DRMProcessor* processor, const std::string& dirName, const std::string& hobbes, bool randomSerial); + /** + * @brief Get one value of device.xml (deviceClass, deviceSerial, deviceName, deviceType, hobbes, clientOS, clientLocale) + */ + std::string getProperty(const std::string& property, const std::string& _default=std::string("")); + std::string operator[](const std::string& property); + /** + * @brief Create device.xml and devicesalt files when they did not exists + * + * @param processor Instance of DRMProcessor + * @param dirName Directory where to put files (.adept) + * @param hobbes Hobbes (client version) to set + * @param randomSerial Create a random serial (new device each time) or not (serial computed from machine specs) + */ + static Device* createDevice(DRMProcessor* processor, const std::string& dirName, const std::string& hobbes, bool randomSerial); + private: - DRMProcessor* processor; + DRMProcessor* processor; std::string deviceFile; std::string deviceKeyFile; - unsigned char deviceKey[DEVICE_KEY_SIZE]; - std::map properties; + unsigned char deviceKey[DEVICE_KEY_SIZE]; + std::map properties; - Device(DRMProcessor* processor); - - std::string makeFingerprint(const std::string& serial); - std::string makeSerial(bool random); - void parseDeviceFile(); - void parseDeviceKeyFile(); - void createDeviceFile(const std::string& hobbes, bool randomSerial); - void createDeviceKeyFile(); + Device(DRMProcessor* processor); + + std::string makeFingerprint(const std::string& serial); + std::string makeSerial(bool random); + void parseDeviceFile(); + void parseDeviceKeyFile(); + void createDeviceFile(const std::string& hobbes, bool randomSerial); + void createDeviceKeyFile(); }; } diff --git a/include/drmprocessorclient.h b/include/drmprocessorclient.h index 352e2f7..a5c8ab5 100644 --- a/include/drmprocessorclient.h +++ b/include/drmprocessorclient.h @@ -21,7 +21,6 @@ #define _DRMPROCESSORCLIENT_H_ #include -#include namespace gourou { @@ -34,376 +33,332 @@ namespace gourou class DigestInterface { public: - /** - * @brief Create a digest handler - * - * @param digestName Digest name to instanciate - */ - virtual void* createDigest(const std::string& digestName) = 0; + /** + * @brief Create a digest handler (for now only SHA1 is used) + * + * @param digestName Digest name to instanciate + */ + virtual void* createDigest(const std::string& digestName) = 0; - /** - * @brief Update digest engine with new data - * - * @param handler Digest handler - * @param data Data to digest - * @param length Length of data - */ - virtual void digestUpdate(void* handler, unsigned char* data, unsigned int length) = 0; + /** + * @brief Update digest engine with new data + * + * @param handler Digest handler + * @param data Data to digest + * @param length Length of data + * + * @return OK/KO + */ + virtual int digestUpdate(void* handler, unsigned char* data, unsigned int length) = 0; - /** - * @brief Finalize digest with remained buffered data and destroy handler - * - * @param handler Digest handler - * @param digestOut Digest result (buffer must be pre allocated with right size) - */ - virtual void digestFinalize(void* handler, unsigned char* digestOut) = 0; + /** + * @brief Finalize digest with remained buffered data and destroy handler + * + * @param handler Digest handler + * @param digestOut Digest result (buffer must be pre allocated with right size) + * + * @return OK/KO + */ + virtual int digestFinalize(void* handler, unsigned char* digestOut) = 0; - /** - * @brief Global digest function - * - * @param digestName Digest name to instanciate - * @param data Data to digest - * @param length Length of data - * @param digestOut Digest result (buffer must be pre allocated with right size) - */ - virtual void digest(const std::string& digestName, unsigned char* data, unsigned int length, unsigned char* digestOut) = 0; + /** + * @brief Global digest function + * + * @param digestName Digest name to instanciate + * @param data Data to digest + * @param length Length of data + * @param digestOut Digest result (buffer must be pre allocated with right size) + * + * @return OK/KO + */ + virtual int digest(const std::string& digestName, unsigned char* data, unsigned int length, unsigned char* digestOut) = 0; }; class RandomInterface { public: - /** - * @brief Generate random bytes - * - * @param bytesOut Buffer to fill with random bytes - * @param length Length of bytesOut - */ - virtual void randBytes(unsigned char* bytesOut, unsigned int length) = 0; + /** + * @brief Generate random bytes + * + * @param bytesOut Buffer to fill with random bytes + * @param length Length of bytesOut + */ + virtual void randBytes(unsigned char* bytesOut, unsigned int length) = 0; }; class HTTPInterface { public: - - /** - * @brief Send HTTP (GET or POST) request - * - * @param URL HTTP URL - * @param POSTData POST data if needed, if not set, a GET request is done - * @param contentType Optional content type of POST Data - * @param responseHeaders Optional Response headers of HTTP request - * @param fd Optional file descriptor to write request result - * @param resume false if target file should be truncated, true to try resume download (works only in combination with a valid fd) - * - * @return data of HTTP response - */ - virtual std::string sendHTTPRequest(const std::string& URL, const std::string& POSTData=std::string(""), const std::string& contentType=std::string(""), std::map* responseHeaders=0, int fd=0, bool resume=false) = 0; + + /** + * @brief Send HTTP (GET or POST) request + * + * @param URL HTTP URL + * @param POSTData POST data if needed, if not set, a GET request is done + * @param contentType Optional content type of POST Data + */ + virtual std::string sendHTTPRequest(const std::string& URL, const std::string& POSTData=std::string(""), const std::string& contentType=std::string("")) = 0; }; class RSAInterface { public: - enum RSA_KEY_TYPE { - RSA_KEY_PKCS12 = 0, - RSA_KEY_PKCS8, - RSA_KEY_X509 - }; + enum RSA_KEY_TYPE { + RSA_KEY_PKCS12 = 0, + RSA_KEY_X509 + }; - /** - * @brief Encrypt data with RSA private key. Data is padded using PKCS1.5 - * - * @param RSAKey RSA key in binary form - * @param RSAKeyLength RSA key length - * @param keyType Key type - * @param password Optional password for RSA PKCS12 certificate - * @param data Data to encrypt - * @param dataLength Data length - * @param res Encryption result (pre allocated buffer) - */ - virtual void RSAPrivateEncrypt(const unsigned char* RSAKey, unsigned int RSAKeyLength, - const RSA_KEY_TYPE keyType, const std::string& password, - const unsigned char* data, unsigned dataLength, - unsigned char* res) = 0; + /** + * @brief Encrypt data with RSA private key. Data is padded using PKCS1.5 + * + * @param RSAKey RSA key in binary form + * @param RSAKeyLength RSA key length + * @param keyType Key type + * @param password Optional password for RSA PKCS12 certificate + * @param data Data to encrypt + * @param dataLength Data length + * @param res Encryption result (pre allocated buffer) + */ + virtual void RSAPrivateEncrypt(const unsigned char* RSAKey, unsigned int RSAKeyLength, + const RSA_KEY_TYPE keyType, const std::string& password, + const unsigned char* data, unsigned dataLength, + unsigned char* res) = 0; + + /** + * @brief Encrypt data with RSA public key. Data is padded using PKCS1.5 + * + * @param RSAKey RSA key in binary form + * @param RSAKeyLength RSA key length + * @param keyType Key type + * @param password Optional password for RSA PKCS12 certificate + * @param data Data to encrypt + * @param dataLength Data length + * @param res Encryption result (pre allocated buffer) + */ + virtual void RSAPublicEncrypt(const unsigned char* RSAKey, unsigned int RSAKeyLength, + const RSA_KEY_TYPE keyType, + const unsigned char* data, unsigned dataLength, + unsigned char* res) = 0; - /** - * @brief Decrypt data with RSA private key. Data is padded using PKCS1.5 - * - * @param RSAKey RSA key in binary form - * @param RSAKeyLength RSA key length - * @param keyType Key type - * @param password Optional password for RSA PKCS12 certificate - * @param data Data to encrypt - * @param dataLength Data length - * @param res Encryption result (pre allocated buffer) - */ - virtual void RSAPrivateDecrypt(const unsigned char* RSAKey, unsigned int RSAKeyLength, - const RSA_KEY_TYPE keyType, const std::string& password, - const unsigned char* data, unsigned dataLength, - unsigned char* res) = 0; + /** + * @brief Generate RSA key. Expnonent is fixed (65537 / 0x10001) + * + * @param keyLengthBits Length of key (in bits) to generate + * + * @return generatedKey + */ + virtual void* generateRSAKey(int keyLengthBits) = 0; - /** - * @brief Encrypt data with RSA public key. Data is padded using PKCS1.5 - * - * @param RSAKey RSA key in binary form - * @param RSAKeyLength RSA key length - * @param keyType Key type - * @param password Optional password for RSA PKCS12 certificate - * @param data Data to encrypt - * @param dataLength Data length - * @param res Encryption result (pre allocated buffer) - */ - virtual void RSAPublicEncrypt(const unsigned char* RSAKey, unsigned int RSAKeyLength, - const RSA_KEY_TYPE keyType, - const unsigned char* data, unsigned dataLength, - unsigned char* res) = 0; + /** + * @brief Destroy key previously generated + * + * @param handler Key to destroy + */ + virtual void destroyRSAHandler(void* handler) = 0; - /** - * @brief Generate RSA key. Expnonent is fixed (65537 / 0x10001) - * - * @param keyLengthBits Length of key (in bits) to generate - * - * @return generatedKey - */ - virtual void* generateRSAKey(int keyLengthBits) = 0; + /** + * @brief Extract public key (big number) from RSA handler + * + * @param handler RSA handler (generated key) + * @param keyOut Pre allocated buffer (if *keyOut != 0). If *keyOut is 0, memory is internally allocated (must be freed) + * @param keyOutLength Length of result + */ + virtual void extractRSAPublicKey(void* handler, unsigned char** keyOut, unsigned int* keyOutLength) = 0; - /** - * @brief Destroy key previously generated - * - * @param handler Key to destroy - */ - virtual void destroyRSAHandler(void* handler) = 0; + /** + * @brief Extract private key (big number) from RSA handler + * + * @param handler RSA handler (generated key) + * @param keyOut Pre allocated buffer (if *keyOut != 0). If *keyOut is 0, memory is internally allocated (must be freed) + * @param keyOutLength Length of result + */ + virtual void extractRSAPrivateKey(void* handler, unsigned char** keyOut, unsigned int* keyOutLength) = 0; - /** - * @brief Extract public key (big number) from RSA handler - * - * @param handler RSA handler (generated key) - * @param keyOut Pre allocated buffer (if *keyOut != 0). If *keyOut is 0, memory is internally allocated (must be freed) - * @param keyOutLength Length of result - */ - virtual void extractRSAPublicKey(void* handler, unsigned char** keyOut, unsigned int* keyOutLength) = 0; - - /** - * @brief Extract private key (big number) from RSA handler - * - * @param handler RSA handler (generated key) - * @param keyOut Pre allocated buffer (if *keyOut != 0). If *keyOut is 0, memory is internally allocated (must be freed) - * @param keyOutLength Length of result - */ - virtual void extractRSAPrivateKey(void* handler, unsigned char** keyOut, unsigned int* keyOutLength) = 0; - - /** - * @brief Extract certificate from PKCS12 blob - * - * @param RSAKey RSA key in binary form - * @param RSAKeyLength RSA key length - * @param keyType Key type - * @param password Optional password for RSA PKCS12 certificate - * @param certOut Result certificate - * @param certOutLength Result certificate length - */ - virtual void extractCertificate(const unsigned char* RSAKey, unsigned int RSAKeyLength, - const RSA_KEY_TYPE keyType, const std::string& password, - unsigned char** certOut, unsigned int* certOutLength) = 0; + /** + * @brief Extract certificate from PKCS12 blob + * + * @param RSAKey RSA key in binary form + * @param RSAKeyLength RSA key length + * @param keyType Key type + * @param password Optional password for RSA PKCS12 certificate + * @param certOut Result certificate + * @param certOutLength Result certificate length + */ + virtual void extractCertificate(const unsigned char* RSAKey, unsigned int RSAKeyLength, + const RSA_KEY_TYPE keyType, const std::string& password, + unsigned char** certOut, unsigned int* certOutLength) = 0; }; class CryptoInterface { public: - enum CRYPTO_ALGO { - ALGO_AES=0, - ALGO_RC4 - }; + enum CHAINING_MODE { + CHAIN_ECB=0, + CHAIN_CBC + }; + + /** + * @brief Do AES encryption. If length of data is not multiple of 16, PKCS#5 padding is done + * + * @param chaining Chaining mode + * @param key AES key + * @param keyLength AES key length + * @param iv IV key + * @param ivLength IV key length + * @param dataIn Data to encrypt + * @param dataInLength Data length + * @param dataOut Encrypted data + * @param dataOutLength Length of encrypted data + */ + virtual void AESEncrypt(CHAINING_MODE chaining, + const unsigned char* key, unsigned int keyLength, + const unsigned char* iv, unsigned int ivLength, + const unsigned char* dataIn, unsigned int dataInLength, + unsigned char* dataOut, unsigned int* dataOutLength) = 0; - enum CHAINING_MODE { - CHAIN_ECB=0, - CHAIN_CBC - }; + /** + * @brief Init AES CBC encryption + * + * @param chaining Chaining mode + * @param key AES key + * @param keyLength AES key length + * @param iv IV key + * @param ivLength IV key length + * + * @return AES handler + */ + virtual void* AESEncryptInit(CHAINING_MODE chaining, + const unsigned char* key, unsigned int keyLength, + const unsigned char* iv=0, unsigned int ivLength=0) = 0; - /** - * @brief Do encryption. If length of data is not multiple of block size, PKCS#5 padding is done - * - * @param algo Algorithm to use - * @param chaining Chaining mode - * @param key AES key - * @param keyLength AES key length - * @param iv IV key - * @param ivLength IV key length - * @param dataIn Data to encrypt - * @param dataInLength Data length - * @param dataOut Encrypted data - * @param dataOutLength Length of encrypted data - */ - virtual void encrypt(CRYPTO_ALGO algo, CHAINING_MODE chaining, - const unsigned char* key, unsigned int keyLength, - const unsigned char* iv, unsigned int ivLength, - const unsigned char* dataIn, unsigned int dataInLength, - unsigned char* dataOut, unsigned int* dataOutLength) = 0; + /** + * @brief Encrypt data + * + * @param handler AES handler + * @param dataIn Data to encrypt + * @param dataInLength Data length + * @param dataOut Encrypted data + * @param dataOutLength Length of encrypted data + */ + virtual void AESEncryptUpdate(void* handler, const unsigned char* dataIn, unsigned int dataInLength, + unsigned char* dataOut, unsigned int* dataOutLength) = 0; - /** - * @brief Init encryption - * - * @param chaining Chaining mode - * @param key Key - * @param keyLength Key length - * @param iv Optional IV key - * @param ivLength Optional IV key length - * - * @return AES handler - */ - virtual void* encryptInit(CRYPTO_ALGO algo, CHAINING_MODE chaining, - const unsigned char* key, unsigned int keyLength, - const unsigned char* iv=0, unsigned int ivLength=0) = 0; + /** + * @brief Finalize AES encryption (pad and encrypt last block if needed) + * Destroy handler at the end + * + * @param handler AES handler + * @param dataOut Last block of encrypted data + * @param dataOutLength Length of encrypted data + */ + virtual void AESEncryptFinalize(void* handler, unsigned char* dataOut, unsigned int* dataOutLength) = 0; - /** - * @brief Encrypt data - * - * @param handler Crypto handler - * @param dataIn Data to encrypt - * @param dataInLength Data length - * @param dataOut Encrypted data - * @param dataOutLength Length of encrypted data - */ - virtual void encryptUpdate(void* handler, const unsigned char* dataIn, unsigned int dataInLength, - unsigned char* dataOut, unsigned int* dataOutLength) = 0; + /** + * @brief Do AES decryption. If length of data is not multiple of 16, PKCS#5 padding is done + * + * @param chaining Chaining mode + * @param key AES key + * @param keyLength AES key length + * @param iv IV key + * @param ivLength IV key length + * @param dataIn Data to encrypt + * @param dataInLength Data length + * @param dataOut Encrypted data + * @param dataOutLength Length of encrypted data + */ + virtual void AESDecrypt(CHAINING_MODE chaining, + const unsigned char* key, unsigned int keyLength, + const unsigned char* iv, unsigned int ivLength, + const unsigned char* dataIn, unsigned int dataInLength, + unsigned char* dataOut, unsigned int* dataOutLength) = 0; - /** - * @brief Finalize encryption (pad and encrypt last block if needed) - * Destroy handler at the end - * - * @param handler Crypto handler - * @param dataOut Last block of encrypted data - * @param dataOutLength Length of encrypted data - */ - virtual void encryptFinalize(void* handler, unsigned char* dataOut, unsigned int* dataOutLength) = 0; + /** + * @brief Init AES decryption + * + * @param chaining Chaining mode + * @param key AES key + * @param keyLength AES key length + * @param iv IV key + * @param ivLength IV key length + * + * @return AES handler + */ + virtual void* AESDecryptInit(CHAINING_MODE chaining, + const unsigned char* key, unsigned int keyLength, + const unsigned char* iv=0, unsigned int ivLength=0) = 0; - /** - * @brief Do decryption. If length of data is not multiple of block size, PKCS#5 padding is done - * - * @param algo Algorithm to use - * @param chaining Chaining mode - * @param key AES key - * @param keyLength AES key length - * @param iv IV key - * @param ivLength IV key length - * @param dataIn Data to encrypt - * @param dataInLength Data length - * @param dataOut Encrypted data - * @param dataOutLength Length of encrypted data - */ - virtual void decrypt(CRYPTO_ALGO algo, CHAINING_MODE chaining, - const unsigned char* key, unsigned int keyLength, - const unsigned char* iv, unsigned int ivLength, - const unsigned char* dataIn, unsigned int dataInLength, - unsigned char* dataOut, unsigned int* dataOutLength) = 0; - - /** - * @brief Init decryption - * - * @param chaining Chaining mode - * @param key Key - * @param keyLength Key length - * @param iv IV key - * @param ivLength IV key length - * - * @return AES handler - */ - virtual void* decryptInit(CRYPTO_ALGO algo, CHAINING_MODE chaining, - const unsigned char* key, unsigned int keyLength, - const unsigned char* iv=0, unsigned int ivLength=0) = 0; - - /** - * @brief Decrypt data - * - * @param handler Crypto handler - * @param dataIn Data to decrypt - * @param dataInLength Data length - * @param dataOut Decrypted data - * @param dataOutLength Length of decrypted data - */ - virtual void decryptUpdate(void* handler, const unsigned char* dataIn, unsigned int dataInLength, - unsigned char* dataOut, unsigned int* dataOutLength) = 0; - /** - * @brief Finalize decryption (decrypt last block and remove padding if it is set). - * Destroy handler at the end - * - * @param handler Crypto handler - * @param dataOut Last block decrypted data - * @param dataOutLength Length of decrypted data - */ - virtual void decryptFinalize(void* handler, unsigned char* dataOut, unsigned int* dataOutLength) = 0; + /** + * @brief Decrypt data + * + * @param handler AES handler + * @param dataIn Data to decrypt + * @param dataInLength Data length + * @param dataOut Decrypted data + * @param dataOutLength Length of decrypted data + */ + virtual void AESDecryptUpdate(void* handler, const unsigned char* dataIn, unsigned int dataInLength, + unsigned char* dataOut, unsigned int* dataOutLength) = 0; + /** + * @brief Finalize AES decryption (decrypt last block and remove padding if it is set). + * Destroy handler at the end + * + * @param handler AES handler + * @param dataOut Last block decrypted data + * @param dataOutLength Length of decrypted data + */ + virtual void AESDecryptFinalize(void* handler, unsigned char* dataOut, unsigned int* dataOutLength) = 0; }; class ZIPInterface { public: - /** - * @brief Open a zip file and return an handler - * - * @param path Path of zip file - * - * @return ZIP file handler - */ - virtual void* zipOpen(const std::string& path) = 0; + /** + * @brief Open a zip file and return an handler + * + * @param path Path of zip file + * + * @return ZIP file handler + */ + virtual void* zipOpen(const std::string& path) = 0; + + /** + * @brief Read zip internal file + * + * @param handler ZIP file handler + * @param path Internal path inside zip file + * + * @return File content + */ + virtual std::string zipReadFile(void* handler, const std::string& path) = 0; + + /** + * @brief Write zip internal file + * + * @param handler ZIP file handler + * @param path Internal path inside zip file + * @param content Internal file content + */ + virtual void zipWriteFile(void* handler, const std::string& path, const std::string& content) = 0; - /** - * @brief Read zip internal file - * - * @param handler ZIP file handler - * @param path Internal path inside zip file - * @param result Result buffer - * @param decompress If false, don't decompress read data - */ - virtual void zipReadFile(void* handler, const std::string& path, ByteArray& result, bool decompress=true) = 0; + /** + * @brief Delete zip internal file + * + * @param handler ZIP file handler + * @param path Internal path inside zip file + */ + virtual void zipDeleteFile(void* handler, const std::string& path) = 0; - /** - * @brief Write zip internal file - * - * @param handler ZIP file handler - * @param path Internal path inside zip file - * @param content File content - */ - virtual void zipWriteFile(void* handler, const std::string& path, ByteArray& content) = 0; - - /** - * @brief Delete zip internal file - * - * @param handler ZIP file handler - * @param path Internal path inside zip file - */ - virtual void zipDeleteFile(void* handler, const std::string& path) = 0; - - /** - * @brief Close ZIP file handler - * - * @param handler ZIP file handler - */ - virtual void zipClose(void* handler) = 0; - - /** - * @brief Inflate algorithm - * - * @param data Data to inflate - * @param result Zipped data - * @param wbits Window bits value for libz - */ - virtual void inflate(gourou::ByteArray& data, gourou::ByteArray& result, - int wbits=-15) = 0; - - /** - * @brief Deflate algorithm - * - * @param data Data to deflate - * @param result Unzipped data - * @param wbits Window bits value for libz - * @param compressionLevel Compression level for libz - */ - virtual void deflate(gourou::ByteArray& data, gourou::ByteArray& result, - int wbits=-15, int compressionLevel=8) = 0; + /** + * @brief Close ZIP file handler + * + * @param handler ZIP file handler + */ + virtual void zipClose(void* handler) = 0; }; - + class DRMProcessorClient: public DigestInterface, public RandomInterface, public HTTPInterface, \ - public RSAInterface, public CryptoInterface, public ZIPInterface + public RSAInterface, public CryptoInterface, public ZIPInterface {}; } #endif diff --git a/include/fulfillment_item.h b/include/fulfillment_item.h index 32d2fe8..4e12d67 100644 --- a/include/fulfillment_item.h +++ b/include/fulfillment_item.h @@ -20,7 +20,7 @@ #ifndef _FULFILLMENT_ITEM_H_ #define _FULFILLMENT_ITEM_H_ -#include "loan_token.h" +#include "bytearray.h" #include @@ -34,52 +34,31 @@ namespace gourou class FulfillmentItem { public: - /** - * @brief Main constructor. Not to be called by user - * - * @param doc Fulfill reply - * @param user User pointer - */ - FulfillmentItem(pugi::xml_document& doc, User* user); + FulfillmentItem(pugi::xml_document& doc, User* user); - ~FulfillmentItem(); + /** + * @brief Return metadata value from ACSM metadata section + * + * @param name Name of key to return + */ + std::string getMetadata(std::string name); - /** - * @brief Return metadata value from ACSM metadata section - * - * @param name Name of key to return - */ - std::string getMetadata(std::string name); + /** + * @brief Return rights generated by ACS server (XML format) + */ + std::string getRights(); - /** - * @brief Return rights generated by ACS server (XML format) - */ - std::string getRights(); - - /** - * @brief Return epub download URL - */ - std::string getDownloadURL(); - - /** - * @brief Return resource value - */ - std::string getResource(); - - /** - * @brief Return loan token if there is one - */ - LoanToken* getLoanToken(); + /** + * @brief Return epub download URL + */ + std::string getDownloadURL(); private: - pugi::xml_document fulfillDoc; - pugi::xml_node metadatas; - pugi::xml_document rights; - std::string downloadURL; - std::string resource; - LoanToken* loanToken; + pugi::xml_node metadatas; + pugi::xml_document rights; + std::string downloadURL; - void buildRights(const pugi::xml_node& licenseToken, User* user); + void buildRights(const pugi::xml_node& licenseToken, User* user); }; } diff --git a/include/libgourou.h b/include/libgourou.h index 921937b..0d62700 100644 --- a/include/libgourou.h +++ b/include/libgourou.h @@ -27,17 +27,20 @@ #include "drmprocessorclient.h" #include -#include #ifndef HOBBES_DEFAULT_VERSION #define HOBBES_DEFAULT_VERSION "10.0.4" #endif +#ifndef DEFAULT_ADEPT_DIR +#define DEFAULT_ADEPT_DIR "./.adept" +#endif + #ifndef ACS_SERVER #define ACS_SERVER "http://adeactivate.adobe.com/adept" #endif -#define LIBGOUROU_VERSION "0.8.8" +#define LIBGOUROU_VERSION "0.3.2" namespace gourou { @@ -48,204 +51,149 @@ namespace gourou { public: - static const std::string VERSION; + static const std::string VERSION; - enum ITEM_TYPE { EPUB=0, PDF }; - /** - * @brief Main constructor. To be used once all is configured (user has signedIn, device is activated) - * - * @param client Client processor - * @param deviceFile Path of device.xml - * @param activationFile Path of activation.xml - * @param deviceKeyFile Path of devicesalt - */ + /** + * @brief Main constructor. To be used once all is configured (user has signedIn, device is activated) + * + * @param client Client processor + * @param deviceFile Path of device.xml + * @param activationFile Path of activation.xml + * @param deviceKeyFile Path of devicesalt + */ DRMProcessor(DRMProcessorClient* client, const std::string& deviceFile, const std::string& activationFile, const std::string& deviceKeyFile); + + ~DRMProcessor(); - ~DRMProcessor(); + /** + * @brief Fulfill ACSM file to server in order to retrieve ePub fulfillment item + * + * @param ACSMFile Path of ACSMFile + * + * @return a FulfillmentItem if all is OK + */ + FulfillmentItem* fulfill(const std::string& ACSMFile); - /** - * @brief Fulfill ACSM file to server in order to retrieve ePub fulfillment item - * - * @param ACSMFile Path of ACSMFile - * @param notify Notify server if requested by response - * - * @return a FulfillmentItem if all is OK - */ - FulfillmentItem* fulfill(const std::string& ACSMFile, bool notify=true); + /** + * @brief Once fulfilled, ePub file needs to be downloaded. + * During this operation, DRM information is added into downloaded file + * + * @param item Item from fulfill() method + * @param path Output file path + */ + void download(FulfillmentItem* item, std::string path); - /** - * @brief Once fulfilled, ePub file needs to be downloaded. - * During this operation, DRM information is added into downloaded file - * - * @param item Item from fulfill() method - * @param path Output file path - * @param resume false if target file should be truncated, true to try resume download - * - * @return Type of downloaded item - */ - ITEM_TYPE download(FulfillmentItem* item, std::string path, bool resume=false); + /** + * @brief SignIn into ACS Server (required to activate device) + * + * @param adobeID AdobeID username + * @param adobePassword Adobe password + */ + void signIn(const std::string& adobeID, const std::string& adobePassword); - /** - * @brief SignIn into ACS Server (required to activate device) - * - * @param adobeID AdobeID username - * @param adobePassword Adobe password - */ - void signIn(const std::string& adobeID, const std::string& adobePassword); + /** + * @brief Activate newly created device (user must have successfuly signedIn before) + */ + void activateDevice(); - /** - * @brief Activate newly created device (user must have successfuly signedIn before) - */ - void activateDevice(); - - /** - * @brief Return loaned book to server - * - * @param loanID Loan ID received during fulfill - * @param operatorURL URL of operator that loans this book - * @param notify Notify server if requested by response - */ - void returnLoan(const std::string& loanID, const std::string& operatorURL, bool notify=true); - - /** - * @brief Return default ADEPT directory (ie /home//.config/adept) - */ - static std::string getDefaultAdeptDir(void); - - /** - * @brief Create a new ADEPT environment (device.xml, devicesalt and activation.xml). - * - * @param client Client processor - * @param randomSerial Always generate a new device (or not) - * @param dirName Directory where to put generated files (.adept) - * @param hobbes Override hobbes default version - * @param ACSServer Override main ACS server (default adeactivate.adobe.com) - */ + /** + * @brief Create a new ADEPT environment (device.xml, devicesalt and activation.xml). + * + * @param client Client processor + * @param randomSerial Always generate a new device (or not) + * @param dirName Directory where to put generated files (.adept) + * @param hobbes Override hobbes default version + * @param ACSServer Override main ACS server (default adeactivate.adobe.com) + */ static DRMProcessor* createDRMProcessor(DRMProcessorClient* client, - bool randomSerial=false, std::string dirName=std::string(""), - const std::string& hobbes=std::string(HOBBES_DEFAULT_VERSION), - const std::string& ACSServer=ACS_SERVER); + bool randomSerial=false, const std::string& dirName=std::string(DEFAULT_ADEPT_DIR), + const std::string& hobbes=std::string(HOBBES_DEFAULT_VERSION), + const std::string& ACSServer=ACS_SERVER); - /** - * @brief Get current log level - */ - static int getLogLevel(); + /** + * @brief Get current log level + */ + static int getLogLevel(); - /** - * @brief Set log level (higher number for verbose output) - */ - static void setLogLevel(int logLevel); + /** + * @brief Set log level (higher number for verbose output) + */ + static void setLogLevel(int logLevel); - /** - * Functions used internally, should not be called by user - */ + /** + * Functions used internally, should not be called by user + */ - /** - * @brief Send HTTP (GET or POST) request - * - * @param URL HTTP URL - * @param POSTData POST data if needed, if not set, a GET request is done - * @param contentType Optional content type of POST Data - * @param responseHeaders Optional Response headers of HTTP request - * @param fd Optional File descriptor to write received data - * @param resume false if target file should be truncated, true to try resume download (works only in combination of a valid fd) - * - * @return data of HTTP response - */ - ByteArray sendRequest(const std::string& URL, const std::string& POSTData=std::string(), const char* contentType=0, std::map* responseHeaders=0, int fd=0, bool resume=false); + /** + * @brief Send HTTP (GET or POST) request + * + * @param URL HTTP URL + * @param POSTData POST data if needed, if not set, a GET request is done + * @param contentType Optional content type of POST Data + */ + ByteArray sendRequest(const std::string& URL, const std::string& POSTData=std::string(), const char* contentType=0); - /** - * @brief Send HTTP POST request to URL with document as POSTData - */ - ByteArray sendRequest(const pugi::xml_document& document, const std::string& url); + /** + * @brief Send HTTP POST request to URL with document as POSTData + */ + ByteArray sendRequest(const pugi::xml_document& document, const std::string& url); - /** - * @brief In place encrypt data with private device key - */ - ByteArray encryptWithDeviceKey(const unsigned char* data, unsigned int len); + /** + * @brief In place encrypt data with private device key + */ + ByteArray encryptWithDeviceKey(const unsigned char* data, unsigned int len); - /** - * @brief In place decrypt data with private device key - */ - ByteArray decryptWithDeviceKey(const unsigned char* data, unsigned int len); + /** + * @brief In place decrypt data with private device key + */ + ByteArray decryptWithDeviceKey(const unsigned char* data, unsigned int len); - /** - * @brief Return base64 encoded value of RSA public key - */ - std::string serializeRSAPublicKey(void* rsa); + /** + * @brief Return base64 encoded value of RSA public key + */ + std::string serializeRSAPublicKey(void* rsa); - /** - * @brief Return base64 encoded value of RSA private key encrypted with private device key - */ - std::string serializeRSAPrivateKey(void* rsa); + /** + * @brief Return base64 encoded value of RSA private key encrypted with private device key + */ + std::string serializeRSAPrivateKey(void* rsa); - /** - * @brief Export clear private license key into path - */ - void exportPrivateLicenseKey(std::string path); + /** + * @brief Get current user + */ + User* getUser() { return user; } - /** - * @brief Get current user - */ - User* getUser() { return user; } - - /** - * @brief Get current device - */ - Device* getDevice() { return device; } - - /** - * @brief Get current client - */ - DRMProcessorClient* getClient() { return client; } - - /** - * @brief Remove ADEPT DRM - * Warning: for PDF format, filenameIn must be different than filenameOut - * - * @param filenameIn Input file (with ADEPT DRM) - * @param filenameOut Output file (without ADEPT DRM) - * @param type Type of file (ePub or PDF) - * @param encryptionKey Optional encryption key, do not try to decrypt the one inside input file - * @param encryptionKeySize Size of encryption key (if provided) - */ - void removeDRM(const std::string& filenameIn, const std::string& filenameOut, ITEM_TYPE type, const unsigned char* encryptionKey=0, unsigned encryptionKeySize=0); + /** + * @brief Get current device + */ + Device* getDevice() { return device; } + /** + * @brief Get current client + */ + DRMProcessorClient* getClient() { return client; } + private: - gourou::DRMProcessorClient* client; + gourou::DRMProcessorClient* client; gourou::Device* device; gourou::User* user; - + DRMProcessor(DRMProcessorClient* client); - - void pushString(void* sha_ctx, const std::string& string); - void pushTag(void* sha_ctx, uint8_t tag); - void hashNode(const pugi::xml_node& root, void *sha_ctx, std::map nsHash); - void hashNode(const pugi::xml_node& root, unsigned char* sha_out); - void signNode(pugi::xml_node& rootNode); - void addNonce(pugi::xml_node& root); - void buildAuthRequest(pugi::xml_document& authReq); - void buildInitLicenseServiceRequest(pugi::xml_document& initLicReq, std::string operatorURL); - void doOperatorAuth(std::string operatorURL); - void operatorAuth(std::string operatorURL); - void buildFulfillRequest(pugi::xml_document& acsmDoc, pugi::xml_document& fulfillReq); - void buildActivateReq(pugi::xml_document& activateReq); - void buildReturnReq(pugi::xml_document& returnReq, const std::string& loanID, const std::string& operatorURL); - ByteArray sendFulfillRequest(const pugi::xml_document& document, const std::string& url); - void buildSignInRequest(pugi::xml_document& signInRequest, const std::string& adobeID, const std::string& adobePassword, const std::string& authenticationCertificate); - void fetchLicenseServiceCertificate(const std::string& licenseURL, - const std::string& operatorURL); - void buildNotifyReq(pugi::xml_document& returnReq, pugi::xml_node& body); - void notifyServer(pugi::xml_node& notifyRoot); - void notifyServer(pugi::xml_document& fulfillReply); - std::string encryptedKeyFirstPass(pugi::xml_document& rightsDoc, const std::string& encryptedKey, const std::string& keyType); - void decryptADEPTKey(pugi::xml_document& rightsDoc, unsigned char* decryptedKey, const unsigned char* encryptionKey=0, unsigned encryptionKeySize=0); - void removeEPubDRM(const std::string& filenameIn, const std::string& filenameOut, const unsigned char* encryptionKey, unsigned encryptionKeySize); - void generatePDFObjectKey(int version, - const unsigned char* masterKey, unsigned int masterKeyLength, - int objectId, int objectGenerationNumber, - unsigned char* keyOut); - void removePDFDRM(const std::string& filenameIn, const std::string& filenameOut, const unsigned char* encryptionKey, unsigned encryptionKeySize); + + void pushString(void* sha_ctx, const std::string& string); + void pushTag(void* sha_ctx, uint8_t tag); + void hashNode(const pugi::xml_node& root, void *sha_ctx, std::map nsHash); + void hashNode(const pugi::xml_node& root, unsigned char* sha_out); + std::string signNode(const pugi::xml_node& rootNode); + void addNonce(pugi::xml_node& root); + void buildAuthRequest(pugi::xml_document& authReq); + void buildInitLicenseServiceRequest(pugi::xml_document& initLicReq, std::string operatorURL); + void doOperatorAuth(std::string operatorURL); + void operatorAuth(std::string operatorURL); + void buildFulfillRequest(pugi::xml_document& acsmDoc, pugi::xml_document& fulfillReq); + void buildActivateReq(pugi::xml_document& activateReq); + ByteArray sendFulfillRequest(const pugi::xml_document& document, const std::string& url); + void buildSignInRequest(pugi::xml_document& signInRequest, const std::string& adobeID, const std::string& adobePassword, const std::string& authenticationCertificate); }; } diff --git a/include/libgourou_common.h b/include/libgourou_common.h index db03653..cd597b9 100644 --- a/include/libgourou_common.h +++ b/include/libgourou_common.h @@ -43,152 +43,118 @@ namespace gourou /** * Some common utilities */ - -#define ADOBE_ADEPT_NS "http://ns.adobe.com/adept" + + #define ADOBE_ADEPT_NS "http://ns.adobe.com/adept" static const int SHA1_LEN = 20; static const int RSA_KEY_SIZE = 128; static const int RSA_KEY_SIZE_BITS = (RSA_KEY_SIZE*8); - + enum GOUROU_ERROR { - GOUROU_DEVICE_DOES_NOT_MATCH = 0x1000, - GOUROU_INVALID_CLIENT, - GOUROU_TAG_NOT_FOUND, - GOUROU_ADEPT_ERROR, - GOUROU_FILE_ERROR, - GOUROU_INVALID_PROPERTY + GOUROU_DEVICE_DOES_NOT_MATCH = 0x1000, + GOUROU_INVALID_CLIENT, + GOUROU_TAG_NOT_FOUND, + GOUROU_ADEPT_ERROR, + GOUROU_FILE_ERROR }; enum FULFILL_ERROR { - FF_ACSM_FILE_NOT_EXISTS = 0x1100, - FF_INVALID_ACSM_FILE, - FF_NO_HMAC_IN_ACSM_FILE, - FF_NOT_ACTIVATED, - FF_NO_OPERATOR_URL, - FF_SERVER_INTERNAL_ERROR + FF_ACSM_FILE_NOT_EXISTS = 0x1100, + FF_INVALID_ACSM_FILE, + FF_NO_HMAC_IN_ACSM_FILE, + FF_NOT_ACTIVATED, + FF_NO_OPERATOR_URL }; enum DOWNLOAD_ERROR { - DW_NO_ITEM = 0x1200, - DW_NO_EBX_HANDLER, + DW_NO_ITEM = 0x1200, }; enum SIGNIN_ERROR { - SIGN_INVALID_CREDENTIALS = 0x1300, + SIGN_INVALID_CREDENTIALS = 0x1300, }; - + enum ACTIVATE_ERROR { - ACTIVATE_NOT_SIGNEDIN = 0x1400 + ACTIVATE_NOT_SIGNEDIN = 0x1400 }; - + enum DEV_ERROR { - DEV_MKPATH = 0x2000, - DEV_MAC_ERROR, - DEV_INVALID_DEVICE_FILE, - DEV_INVALID_DEVICE_KEY_FILE, - DEV_INVALID_DEV_PROPERTY, + DEV_MKPATH = 0x2000, + DEV_MAC_ERROR, + DEV_INVALID_DEVICE_FILE, + DEV_INVALID_DEVICE_KEY_FILE, + DEV_INVALID_DEV_PROPERTY, }; enum USER_ERROR { - USER_MKPATH = 0x3000, - USER_INVALID_ACTIVATION_FILE, - USER_NO_AUTHENTICATION_URL, - USER_NO_PROPERTY, - USER_INVALID_INPUT, + USER_MKPATH = 0x3000, + USER_INVALID_ACTIVATION_FILE, + USER_NO_AUTHENTICATION_URL, + USER_NO_PROPERTY, }; enum FULFILL_ITEM_ERROR { - FFI_INVALID_FULFILLMENT_DATA = 0x4000, - FFI_INVALID_LOAN_TOKEN + FFI_INVALID_FULFILLMENT_DATA = 0x4000 }; - + enum CLIENT_ERROR { - CLIENT_BAD_PARAM = 0x5000, - CLIENT_INVALID_PKCS12, - CLIENT_INVALID_CERTIFICATE, - CLIENT_NO_PRIV_KEY, - CLIENT_NO_PUB_KEY, - CLIENT_RSA_ERROR, - CLIENT_BAD_CHAINING, - CLIENT_BAD_KEY_SIZE, - CLIENT_BAD_ZIP_FILE, - CLIENT_ZIP_ERROR, - CLIENT_GENERIC_EXCEPTION, - CLIENT_NETWORK_ERROR, - CLIENT_INVALID_PKCS8, - CLIENT_FILE_ERROR, - CLIENT_OSSL_ERROR, - CLIENT_CRYPT_ERROR, - CLIENT_DIGEST_ERROR, - CLIENT_HTTP_ERROR + CLIENT_BAD_PARAM = 0x5000, + CLIENT_INVALID_PKCS12, + CLIENT_INVALID_CERTIFICATE, + CLIENT_NO_PRIV_KEY, + CLIENT_RSA_ERROR, + CLIENT_BAD_CHAINING, + CLIENT_BAD_KEY_SIZE, + CLIENT_BAD_ZIP_FILE, + CLIENT_ZIP_ERROR, + CLIENT_GENERIC_EXCEPTION, + CLIENT_NETWORK_ERROR, }; - enum DRM_REMOVAL_ERROR { - DRM_ERR_ENCRYPTION_KEY = 0x6000, - DRM_VERSION_NOT_SUPPORTED, - DRM_FILE_ERROR, - DRM_FORMAT_NOT_SUPPORTED, - DRM_IN_OUT_EQUALS, - DRM_MISSING_PARAMETER, - 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 */ class Exception : public std::exception { public: - Exception(int code, const char* message, const char* file, int line): - code(code), line(line), file(file) - { - std::stringstream msg; - msg << "Exception code : 0x" << std::setbase(16) << code << std::endl; - msg << "Message : " << message << std::endl; - if (logLevel >= LG_LOG_DEBUG) - msg << "File : " << file << ":" << std::setbase(10) << line << std::endl; - fullmessage = strdup(msg.str().c_str()); - } + Exception(int code, const char* message, const char* file, int line): + code(code), line(line), file(file) + { + std::stringstream msg; + msg << "Exception code : 0x" << std::setbase(16) << code << std::endl; + msg << "Message : " << message << std::endl; + if (logLevel >= DEBUG) + msg << "File : " << file << ":" << std::setbase(10) << line << std::endl; + fullmessage = strdup(msg.str().c_str()); + } - Exception(const Exception& other) - { - this->code = other.code; - this->line = other.line; - this->file = other.file; - this->fullmessage = strdup(other.fullmessage); - } + Exception(const Exception& other) + { + this->code = other.code; + this->line = line; + this->file = file; + this->fullmessage = strdup(other.fullmessage); + } - ~Exception() _NOEXCEPT - { - free(fullmessage); - } + ~Exception() + { + free(fullmessage); + } - const char * what () const throw () { return fullmessage; } - - int getErrorCode() {return code;} - - private: - int code, line; - const char* file; - char* fullmessage; + const char * what () const throw () { return fullmessage; } + + int getErrorCode() {return code;} + + private: + int code, line; + const char* message, *file; + char* fullmessage; }; /** * @brief Throw an exception */ -#define EXCEPTION(code, message) \ +#define EXCEPTION(code, message) \ {std::stringstream __msg;__msg << message; throw gourou::Exception(code, __msg.str().c_str(), __FILE__, __LINE__);} /** @@ -197,15 +163,15 @@ namespace gourou class StringXMLWriter : public pugi::xml_writer { public: - virtual void write(const void* data, size_t size) - { - result.append(static_cast(data), size); - } + virtual void write(const void* data, size_t size) + { + result.append(static_cast(data), size); + } - const std::string& getResult() {return result;} + const std::string& getResult() {return result;} private: - std::string result; + std::string result; }; static const char* ws = " \t\n\r\f\v"; @@ -215,8 +181,8 @@ namespace gourou */ inline std::string& rtrim(std::string& s, const char* t = ws) { - s.erase(s.find_last_not_of(t) + 1); - return s; + s.erase(s.find_last_not_of(t) + 1); + return s; } /** @@ -224,8 +190,8 @@ namespace gourou */ inline std::string& ltrim(std::string& s, const char* t = ws) { - s.erase(0, s.find_first_not_of(t)); - return s; + s.erase(0, s.find_first_not_of(t)); + return s; } /** @@ -233,23 +199,7 @@ namespace gourou */ inline std::string& trim(std::string& s, const char* t = ws) { - return ltrim(rtrim(s, t), t); - } - - static inline pugi::xml_node getNode(const pugi::xml_node& root, const char* tagName, bool throwOnNull=true) - { - pugi::xpath_node xpath_node = root.select_node(tagName); - - if (!xpath_node) - { - if (throwOnNull) - EXCEPTION(GOUROU_TAG_NOT_FOUND, "Tag " << tagName << " not found"); - - return pugi::xml_node(); - } - - return xpath_node.node(); - + return ltrim(rtrim(s, t), t); } /** @@ -257,68 +207,29 @@ namespace gourou * 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) + static inline std::string extractTextElem(const pugi::xml_document& doc, const char* tagName, bool throwOnNull=true) { - pugi::xml_node node = getNode(root, tagName, throwOnNull); - - node = node.first_child(); - - if (!node) - { - if (throwOnNull) - EXCEPTION(GOUROU_TAG_NOT_FOUND, "Text element for tag " << tagName << " not found"); + pugi::xpath_node xpath_node = doc.select_node(tagName); + if (!xpath_node) + { + if (throwOnNull) + EXCEPTION(GOUROU_TAG_NOT_FOUND, "Tag " << tagName << " not found"); + return ""; - } + } - std::string res = node.value(); - return trim(res); - } - - /** - * @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::xml_node node = getNode(root, tagName, throwOnNull); - - if (!node) - { - if (throwOnNull) - EXCEPTION(GOUROU_TAG_NOT_FOUND, "Text element for tag " << tagName << " not found"); - return; - } - - node = node.first_child(); - - if (!node) - node.append_child(pugi::node_pcdata).set_value(value.c_str()); - else - node.set_value(value.c_str()); - } - - /** - * @brief Extract text attribute from tag in document - * It can throw an exception if attribute does not exists - * or just return an empty value - */ - static inline std::string extractTextAttribute(const pugi::xml_node& root, const char* tagName, const char* attributeName, bool throwOnNull=true) - { - pugi::xml_node node = getNode(root, tagName, throwOnNull); - - pugi::xml_attribute attr = node.attribute(attributeName); - - if (!attr) - { - if (throwOnNull) - EXCEPTION(GOUROU_TAG_NOT_FOUND, "Attribute element " << attributeName << " for tag " << tagName << " not found"); + pugi::xml_node node = xpath_node.node().first_child(); + if (!node) + { + if (throwOnNull) + EXCEPTION(GOUROU_TAG_NOT_FOUND, "Text element for tag " << tagName << " not found"); + return ""; - } + } - std::string res = attr.value(); + std::string res = node.value(); return trim(res); } @@ -331,52 +242,8 @@ namespace gourou */ static inline void appendTextElem(pugi::xml_node& root, const std::string& name, const std::string& value) { - pugi::xml_node node = root.append_child(name.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. -*/ - -#ifndef _LOAN_TOKEN_H_ -#define _LOAN_TOKEN_H_ - -#include - -#include - -namespace gourou -{ - /** - * @brief This class is a container for a fulfillment object - */ - class LoanToken - { - public: - /** - * @brief Main constructor. Not to be called by user - * - * @param doc Fulfill reply - */ - LoanToken(pugi::xml_document& doc); - - /** - * @brief Get a property (id, operatorURL, validity) - */ - std::string getProperty(const std::string& property, const std::string& _default=std::string("")); - std::string operator[](const std::string& property); - - private: - std::map properties; - }; -} - - -#endif diff --git a/include/user.h b/include/user.h index 8240dab..625862e 100644 --- a/include/user.h +++ b/include/user.h @@ -14,15 +14,13 @@ GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License - along with libgourou. If not, see . + along with libgourou. If not, see . */ #ifndef _USER_H_ #define _USER_H_ #include -#include - #include "bytearray.h" #include @@ -30,7 +28,7 @@ namespace gourou { class DRMProcessor; - + /** * @brief This class is a container for activation.xml (activation info). It should not be used by user. */ @@ -39,73 +37,73 @@ namespace gourou public: User(DRMProcessor* processor, const std::string& activationFile); - /** - * @brief Retrieve some values from activation.xml - */ - std::string& getUUID(); - std::string& getPKCS12(); - std::string& getDeviceUUID(); - std::string& getDeviceFingerprint(); - std::string& getUsername(); - std::string& getLoginMethod(); - std::string getLicenseServiceCertificate(std::string url); - std::string& getAuthenticationCertificate(); - std::string& getPrivateLicenseKey(); + /** + * @brief Retrieve some values from activation.xml + */ + std::string& getUUID(); + std::string& getPKCS12(); + std::string& getDeviceUUID(); + std::string& getDeviceFingerprint(); + std::string& getUsername(); + std::string& getLoginMethod(); + std::string& getCertificate(); + std::string& getAuthenticationCertificate(); + std::string& getPrivateLicenseKey(); - /** - * @brief Read activation.xml and put result into doc - */ - void readActivation(pugi::xml_document& doc); + /** + * @brief Read activation.xml and put result into doc + */ + void readActivation(pugi::xml_document& doc); - /** - * @brief Update activation.xml with new data - */ - void updateActivationFile(const char* data); + /** + * @brief Update activation.xml with new data + */ + void updateActivationFile(const char* data); - /** - * @brief Update activation.xml with doc data - */ - void updateActivationFile(const pugi::xml_document& doc); + /** + * @brief Update activation.xml with doc data + */ + void updateActivationFile(const pugi::xml_document& doc); - /** - * @brief Get one value of activation.xml - */ - std::string getProperty(const std::string property); - - /** - * @brief Get all nodes with property name - */ - pugi::xpath_node_set getProperties(const std::string property); - - /** - * @brief Create activation.xml and devicesalt files if they did not exists - * - * @param processor Instance of DRMProcessor - * @param dirName Directory where to put files (.adept) - * @param ACSServer Server used for signIn - */ - static User* createUser(DRMProcessor* processor, const std::string& dirName, const std::string& ACSServer); + /** + * @brief Get one value of activation.xml + */ + std::string getProperty(const std::string property); + + /** + * @brief Get all nodes with property name + */ + pugi::xpath_node_set getProperties(const std::string property); + + /** + * @brief Create activation.xml and devicesalt files if they did not exists + * + * @param processor Instance of DRMProcessor + * @param dirName Directory where to put files (.adept) + * @param ACSServer Server used for signIn + */ + static User* createUser(DRMProcessor* processor, const std::string& dirName, const std::string& ACSServer); private: - DRMProcessor* processor; - pugi::xml_document activationDoc; - + DRMProcessor* processor; + pugi::xml_document activationDoc; + std::string activationFile; - std::string pkcs12; - std::string uuid; - std::string deviceUUID; - std::string deviceFingerprint; - std::string username; - std::string loginMethod; - std::map licenseServiceCertificates; - std::string authenticationCertificate; - std::string privateLicenseKey; + std::string pkcs12; + std::string uuid; + std::string deviceUUID; + std::string deviceFingerprint; + std::string username; + std::string loginMethod; + std::string certificate; + std::string authenticationCertificate; + std::string privateLicenseKey; - User(DRMProcessor* processor); - - void parseActivationFile(bool throwOnNull=true); - ByteArray signIn(const std::string& adobeID, const std::string& adobePassword, - ByteArray authenticationCertificate); + User(DRMProcessor* processor); + + void parseActivationFile(bool throwOnNull=true); + ByteArray signIn(const std::string& adobeID, const std::string& adobePassword, + ByteArray authenticationCertificate); }; } diff --git a/scripts/setup.sh b/scripts/setup.sh index 271361b..a53779d 100755 --- a/scripts/setup.sh +++ b/scripts/setup.sh @@ -1,9 +1,14 @@ #!/bin/bash -# uPDFParser -if [ ! -d lib/updfparser ] ; then - git clone https://forge.soutade.fr/soutade/uPDFParser.git lib/updfparser - pushd lib/updfparser - make BUILD_STATIC=1 BUILD_SHARED=0 +# 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 diff --git a/scripts/update_lib.sh b/scripts/update_lib.sh deleted file mode 100755 index 1290b0c..0000000 --- a/scripts/update_lib.sh +++ /dev/null @@ -1,13 +0,0 @@ -#!/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 diff --git a/src/bytearray.cpp b/src/bytearray.cpp index f84230a..11cb21c 100644 --- a/src/bytearray.cpp +++ b/src/bytearray.cpp @@ -17,241 +17,157 @@ along with libgourou. If not, see . */ #include -#include -#include +#include #include namespace gourou { - std::map ByteArray::refCounter; - - ByteArray::ByteArray(bool useMalloc):_useMalloc(useMalloc), _data(0), _length(0) + std::map ByteArray::refCounter; + + ByteArray::ByteArray():_data(0), _length(0) {} - ByteArray::ByteArray(unsigned int length, bool useMalloc): - _useMalloc(useMalloc) + ByteArray::ByteArray(const unsigned char* data, unsigned int length) { - initData(0, length); + initData(data, length); } - - ByteArray::ByteArray(const unsigned char* data, unsigned int length): - _useMalloc(false) + + ByteArray::ByteArray(const char* data, int length) { - initData(data, length); + if (length == -1) + length = strlen(data) + 1; + + initData((const unsigned char*)data, (unsigned int) length); } - - ByteArray::ByteArray(const char* data, int length): - _useMalloc(false) + + ByteArray::ByteArray(const std::string& str) { - if (length == -1) - length = strlen(data); - - initData((unsigned char*)data, (unsigned int) length); - } - - ByteArray::ByteArray(const std::string& str): - _useMalloc(false) - { - initData((unsigned char*)str.c_str(), (unsigned int)str.length()); + initData((unsigned char*)str.c_str(), (unsigned int)str.length() + 1); } void ByteArray::initData(const unsigned char* data, unsigned int length) { - if (_useMalloc) - _data = (unsigned char*)malloc(length); - else - _data = new unsigned char[length]; + _data = new unsigned char[length]; + memcpy((void*)_data, data, length); + _length = length; - if (data) - memcpy((void*)_data, data, length); - - _length = length; - - addRef(); + addRef(); } - + ByteArray::ByteArray(const ByteArray& other) { - this->_useMalloc = other._useMalloc; - this->_data = other._data; - this->_length = other._length; + this->_data = other._data; + this->_length = other._length; - addRef(); + addRef(); } ByteArray& ByteArray::operator=(const ByteArray& other) { - delRef(); + delRef(); + + this->_data = other._data; + this->_length = other._length; - this->_useMalloc = other._useMalloc; - this->_data = other._data; - this->_length = other._length; - - addRef(); - - return *this; + addRef(); + + return *this; } - + ByteArray::~ByteArray() { - delRef(); + delRef(); } - + void ByteArray::addRef() { - if (!_data) return; + if (!_data) return; - if (refCounter.count(_data) == 0) - refCounter[_data] = 1; - else - refCounter[_data]++; + if (refCounter.count(_data) == 0) + refCounter[_data] = 1; + else + refCounter[_data]++; } - + void ByteArray::delRef() { - if (!_data) return; - - if (refCounter[_data] == 1) - { - if (_useMalloc) - free(_data); - else - delete[] _data; - refCounter.erase(_data); - } - else - refCounter[_data]--; + if (!_data) return; + + if (refCounter[_data] == 1) + { + delete[] _data; + refCounter.erase(_data); + } + else + refCounter[_data]--; } ByteArray ByteArray::fromBase64(const ByteArray& other) { - std::string b64; + std::string b64; - macaron::Base64::Decode(std::string((char*)other._data, other._length), b64); + macaron::Base64::Decode(std::string((char*)other._data, other._length), b64); - return ByteArray(b64); + return ByteArray(b64); } - + ByteArray ByteArray::fromBase64(const char* data, int length) { - std::string b64; + std::string b64; - if (length == -1) - length = strlen(data); + if (length == -1) + length = strlen(data); + + macaron::Base64::Decode(std::string(data, length), b64); - macaron::Base64::Decode(std::string(data, length), b64); - - return ByteArray(b64); + return ByteArray(b64); } - + ByteArray ByteArray::fromBase64(const std::string& str) { - return ByteArray::fromBase64(str.c_str(), str.length()); + return ByteArray::fromBase64(str.c_str(), str.length()); } - + std::string ByteArray::toBase64() { - 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= '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; + return macaron::Base64::Encode(std::string((char*)_data, _length)); } 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++) - snprintf(&tmp[i*2], (_length-i)*2+1, "%02x", _data[i]); + for(int i=0; i<(int)_length; i++) + sprintf(&tmp[i*2], "%02x", _data[i]); - tmp[_length*2] = 0; + tmp[_length*2] = 0; - std::string res = tmp; - delete[] tmp; - - return res; + std::string res = tmp; + delete[] tmp; + + return res; } void ByteArray::append(const unsigned char* data, unsigned int length) { - if (!length) - return; + const unsigned char* oldData = _data; + unsigned char* newData = new unsigned char[_length+length]; - unsigned int oldLength = _length; + memcpy(newData, oldData, _length); + + delRef(); - resize(_length+length, true); - - memcpy(&_data[oldLength], data, length); + memcpy(&newData[_length], data, length); + _length += length; + + _data = newData; + + addRef(); } - + void ByteArray::append(unsigned char c) { append(&c, 1);} void ByteArray::append(const char* str) { append((const unsigned char*)str, strlen(str));} void ByteArray::append(const std::string& str) { append((const unsigned char*)str.c_str(), str.length()); } - - void ByteArray::resize(unsigned length, bool keepData) - { - if (length == _length) - return; - else if (length < _length) - _length = length ; // Don't touch data - else // New size > - { - unsigned char* newData; - - if (_useMalloc) - newData = (unsigned char*)malloc(_length+length); - else - newData = new unsigned char[_length+length]; - - if (keepData) - memcpy(newData, _data, _length); - - delRef(); - - _length = length; - _data = newData; - - addRef(); - } - } } diff --git a/src/device.cpp b/src/device.cpp index 75e603e..481a99b 100644 --- a/src/device.cpp +++ b/src/device.cpp @@ -29,23 +29,13 @@ #include #include -#include -#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 #include -#include +#include #include #include -#elif (defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__) \ - || defined(__bsdi__) || defined(__DragonFly__) || defined(__APPLE__)) -#include -#include -#include +#include -#define BSD_HEADERS 1 -#endif - -#if defined(__linux__) || defined(linux) || defined(__linux) -// From https://stackoverflow.com/questions/1779715/how-to-get-mac-address-of-your-machine-using-a-c-program/35242525 int get_mac_address(unsigned char* mac_address) { struct ifreq ifr; @@ -78,275 +68,238 @@ int get_mac_address(unsigned char* mac_address) if (success) { - memcpy(mac_address, ifr.ifr_hwaddr.sa_data, 6); - return 0; + memcpy(mac_address, ifr.ifr_hwaddr.sa_data, 6); + return 0; } 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 { Device::Device(DRMProcessor* processor): - processor(processor) + processor(processor) {} - + Device::Device(DRMProcessor* processor, const std::string& deviceFile, const std::string& deviceKeyFile): - processor(processor), deviceFile(deviceFile), deviceKeyFile(deviceKeyFile) + processor(processor), deviceFile(deviceFile), deviceKeyFile(deviceKeyFile) { - parseDeviceKeyFile(); - parseDeviceFile(); + parseDeviceKeyFile(); + parseDeviceFile(); } /* SHA1(uid ":" username ":" macaddress ":" */ std::string Device::makeSerial(bool random) { - unsigned char sha_out[SHA1_LEN]; - DRMProcessorClient* client = processor->getClient(); + unsigned char sha_out[SHA1_LEN]; + DRMProcessorClient* client = processor->getClient(); + + if (!random) + { + uid_t uid = getuid(); + struct passwd * passwd = getpwuid(uid); + // Default mac address in case of failure + unsigned char mac_address[6] = {0x01, 0x02, 0x03, 0x04, 0x05}; - if (!random) - { - uid_t uid = getuid(); - struct passwd * passwd = getpwuid(uid); - // Default mac address in case of failure - unsigned char mac_address[6] = {0x01, 0x02, 0x03, 0x04, 0x05}; + get_mac_address(mac_address); - get_mac_address(mac_address); + int dataToHashLen = 10 /* UID */ + strlen(passwd->pw_name) + sizeof(mac_address)*2 /*mac address*/ + 1 /* \0 */; + dataToHashLen += 8; /* Separators */ + unsigned char* dataToHash = new unsigned char[dataToHashLen]; + dataToHashLen = snprintf((char*)dataToHash, dataToHashLen, "%d:%s:%02x:%02x:%02x:%02x:%02x:%02x:", + uid, passwd->pw_name, + mac_address[0], mac_address[1], mac_address[2], + mac_address[3], mac_address[4], mac_address[5]); + + client->digest("SHA1", dataToHash, dataToHashLen+1, sha_out); - int dataToHashLen = 10 /* UID */ + strlen(passwd->pw_name) + sizeof(mac_address)*2 /*mac address*/ + 1 /* \0 */; - dataToHashLen += 8; /* Separators */ - unsigned char* dataToHash = new unsigned char[dataToHashLen]; - dataToHashLen = snprintf((char*)dataToHash, dataToHashLen, "%d:%s:%02x:%02x:%02x:%02x:%02x:%02x:", - uid, passwd->pw_name, - mac_address[0], mac_address[1], mac_address[2], - mac_address[3], mac_address[4], mac_address[5]); + delete[] dataToHash; + } + else + { + client->randBytes(sha_out, sizeof(sha_out)); + } - client->digest("SHA1", dataToHash, dataToHashLen+1, sha_out); - - delete[] dataToHash; - } - else - { - client->randBytes(sha_out, sizeof(sha_out)); - } - - std::string res = ByteArray((const char*)sha_out, DEVICE_SERIAL_LEN).toHex(); - GOUROU_LOG(DEBUG, "Serial : " << res); - return res; + std::string res = ByteArray((const char*)sha_out, DEVICE_SERIAL_LEN).toHex(); + GOUROU_LOG(DEBUG, "Serial : " << res); + return res; } /* base64(SHA1 (serial + privateKey)) */ std::string Device::makeFingerprint(const std::string& serial) { - DRMProcessorClient* client = processor->getClient(); - unsigned char sha_out[SHA1_LEN]; + DRMProcessorClient* client = processor->getClient(); + unsigned char sha_out[SHA1_LEN]; - void* handler = client->createDigest("SHA1"); - client->digestUpdate(handler, (unsigned char*) serial.c_str(), serial.length()); - client->digestUpdate(handler, deviceKey, sizeof(deviceKey)); - client->digestFinalize(handler, sha_out); + void* handler = client->createDigest("SHA1"); + client->digestUpdate(handler, (unsigned char*) serial.c_str(), serial.length()); + client->digestUpdate(handler, deviceKey, sizeof(deviceKey)); + client->digestFinalize(handler, sha_out); - std::string res = ByteArray(sha_out, sizeof(sha_out)).toBase64(); - GOUROU_LOG(DEBUG, "Fingerprint : " << res); - return res; + std::string res = ByteArray(sha_out, sizeof(sha_out)).toBase64(); + GOUROU_LOG(DEBUG, "Fingerprint : " << res); + return res; } - + void Device::createDeviceFile(const std::string& hobbes, bool randomSerial) { - struct utsname sysname; - uname(&sysname); + struct utsname sysname; + uname(&sysname); - std::string serial = makeSerial(randomSerial); - std::string fingerprint = makeFingerprint(serial); + std::string serial = makeSerial(randomSerial); + std::string fingerprint = makeFingerprint(serial); + + pugi::xml_document deviceDoc; + pugi::xml_node decl = deviceDoc.append_child(pugi::node_declaration); + decl.append_attribute("version") = "1.0"; + + pugi::xml_node root = deviceDoc.append_child("adept:deviceInfo"); + root.append_attribute("xmlns:adept") = ADOBE_ADEPT_NS; - pugi::xml_document deviceDoc; - pugi::xml_node decl = deviceDoc.append_child(pugi::node_declaration); - decl.append_attribute("version") = "1.0"; + appendTextElem(root, "adept:deviceClass", "Desktop"); + appendTextElem(root, "adept:deviceSerial", serial); + appendTextElem(root, "adept:deviceName", sysname.nodename); + appendTextElem(root, "adept:deviceType", "standalone"); - pugi::xml_node root = deviceDoc.append_child("adept:deviceInfo"); - root.append_attribute("xmlns:adept") = ADOBE_ADEPT_NS; + pugi::xml_node version = root.append_child("adept:version"); + version.append_attribute("name") = "hobbes"; + version.append_attribute("value") = hobbes.c_str(); + + version = root.append_child("adept:version"); + version.append_attribute("name") = "clientOS"; + std::string os = std::string(sysname.sysname) + " " + std::string(sysname.release); + version.append_attribute("value") = os.c_str(); + + version = root.append_child("adept:version"); + version.append_attribute("name") = "clientLocale"; + version.append_attribute("value") = setlocale(LC_ALL, NULL); - appendTextElem(root, "adept:deviceClass", "Desktop"); - appendTextElem(root, "adept:deviceSerial", serial); - appendTextElem(root, "adept:deviceName", sysname.nodename); - appendTextElem(root, "adept:deviceType", "standalone"); + appendTextElem(root, "adept:fingerprint", fingerprint); - pugi::xml_node version = root.append_child("adept:version"); - version.append_attribute("name") = "hobbes"; - version.append_attribute("value") = hobbes.c_str(); + StringXMLWriter xmlWriter; + deviceDoc.save(xmlWriter, " "); - version = root.append_child("adept:version"); - version.append_attribute("name") = "clientOS"; - std::string os = std::string(sysname.sysname) + " " + std::string(sysname.release); - version.append_attribute("value") = os.c_str(); + GOUROU_LOG(DEBUG, "Create device file " << deviceFile); - version = root.append_child("adept:version"); - version.append_attribute("name") = "clientLocale"; - version.append_attribute("value") = setlocale(LC_ALL, NULL); - - appendTextElem(root, "adept:fingerprint", fingerprint); - - StringXMLWriter xmlWriter; - deviceDoc.save(xmlWriter, " "); - - GOUROU_LOG(DEBUG, "Create device file " << deviceFile); - - writeFile(deviceFile, xmlWriter.getResult()); + writeFile(deviceFile, xmlWriter.getResult()); } void Device::createDeviceKeyFile() { - unsigned char key[DEVICE_KEY_SIZE]; + unsigned char key[DEVICE_KEY_SIZE]; - GOUROU_LOG(DEBUG, "Create device key file " << deviceKeyFile); + GOUROU_LOG(DEBUG, "Create device key file " << deviceKeyFile); - processor->getClient()->randBytes(key, sizeof(key)); + processor->getClient()->randBytes(key, sizeof(key)); - writeFile(deviceKeyFile, key, sizeof(key)); + writeFile(deviceKeyFile, key, sizeof(key)); } - + Device* Device::createDevice(DRMProcessor* processor, const std::string& dirName, const std::string& hobbes, bool randomSerial) { - struct stat _stat; + struct stat _stat; - if (stat(dirName.c_str(), &_stat) != 0) - { - if (mkdir_p(dirName.c_str(), S_IRWXU)) - EXCEPTION(DEV_MKPATH, "Unable to create " << dirName) - } + if (stat(dirName.c_str(), &_stat) != 0) + { + if (mkdir_p(dirName.c_str(), S_IRWXU)) + EXCEPTION(DEV_MKPATH, "Unable to create " << dirName) + } - Device* device = new Device(processor); + Device* device = new Device(processor); - device->deviceFile = dirName + "/device.xml"; - device->deviceKeyFile = dirName + "/devicesalt"; + device->deviceFile = dirName + "/device.xml"; + device->deviceKeyFile = dirName + "/devicesalt"; - try - { - device->parseDeviceKeyFile(); - } - catch (...) - { - device->createDeviceKeyFile(); - device->parseDeviceKeyFile(); - } + try + { + device->parseDeviceKeyFile(); + } + catch (...) + { + device->createDeviceKeyFile(); + device->parseDeviceKeyFile(); + } - try - { - device->parseDeviceFile(); - } - catch (...) - { - device->createDeviceFile(hobbes, randomSerial); - device->parseDeviceFile(); - } - - return device; + try + { + device->parseDeviceFile(); + } + catch (...) + { + device->createDeviceFile(hobbes, randomSerial); + device->parseDeviceFile(); + } + + return device; } - + const unsigned char* Device::getDeviceKey() { - return deviceKey; + return deviceKey; } void Device::parseDeviceFile() { - pugi::xml_document doc; + pugi::xml_document doc; - if (!doc.load_file(deviceFile.c_str())) - EXCEPTION(DEV_INVALID_DEVICE_FILE, "Invalid device file"); + if (!doc.load_file(deviceFile.c_str())) + EXCEPTION(DEV_INVALID_DEVICE_FILE, "Invalid device file"); - try - { - properties["deviceClass"] = gourou::extractTextElem(doc, "/adept:deviceInfo/adept:deviceClass"); - properties["deviceSerial"] = gourou::extractTextElem(doc, "/adept:deviceInfo/adept:deviceSerial"); - properties["deviceName"] = gourou::extractTextElem(doc, "/adept:deviceInfo/adept:deviceName"); - properties["deviceType"] = gourou::extractTextElem(doc, "/adept:deviceInfo/adept:deviceType"); - properties["fingerprint"] = gourou::extractTextElem(doc, "/adept:deviceInfo/adept:fingerprint"); + try + { + properties["deviceClass"] = gourou::extractTextElem(doc, "/adept:deviceInfo/adept:deviceClass"); + properties["deviceSerial"] = gourou::extractTextElem(doc, "/adept:deviceInfo/adept:deviceSerial"); + properties["deviceName"] = gourou::extractTextElem(doc, "/adept:deviceInfo/adept:deviceName"); + properties["deviceType"] = gourou::extractTextElem(doc, "/adept:deviceInfo/adept:deviceType"); + properties["fingerprint"] = gourou::extractTextElem(doc, "/adept:deviceInfo/adept:fingerprint"); - pugi::xpath_node_set nodeSet = doc.select_nodes("/adept:deviceInfo/adept:version"); + pugi::xpath_node_set nodeSet = doc.select_nodes("/adept:deviceInfo/adept:version"); - for (pugi::xpath_node_set::const_iterator it = nodeSet.begin(); - it != nodeSet.end(); ++it) - { - pugi::xml_node node = it->node(); - pugi::xml_attribute name = node.attribute("name"); - pugi::xml_attribute value = node.attribute("value"); + for (pugi::xpath_node_set::const_iterator it = nodeSet.begin(); + it != nodeSet.end(); ++it) + { + pugi::xml_node node = it->node(); + pugi::xml_attribute name = node.attribute("name"); + pugi::xml_attribute value = node.attribute("value"); - properties[name.value()] = value.value(); - } - } - catch (gourou::Exception& e) - { - EXCEPTION(DEV_INVALID_DEVICE_FILE, "Invalid device file"); - } + properties[name.value()] = value.value(); + } + } + catch (gourou::Exception& e) + { + EXCEPTION(DEV_INVALID_DEVICE_FILE, "Invalid device file"); + } } void Device::parseDeviceKeyFile() { - struct stat _stat; + struct stat _stat; - if (stat(deviceKeyFile.c_str(), &_stat) == 0 && - _stat.st_size == DEVICE_KEY_SIZE) - { - readFile(deviceKeyFile, deviceKey, sizeof(deviceKey)); - } - else - EXCEPTION(DEV_INVALID_DEVICE_KEY_FILE, "Invalid device key file"); + if (stat(deviceKeyFile.c_str(), &_stat) == 0 && + _stat.st_size == DEVICE_KEY_SIZE) + { + readFile(deviceKeyFile, deviceKey, sizeof(deviceKey)); + } + else + EXCEPTION(DEV_INVALID_DEVICE_KEY_FILE, "Invalid device key file"); } std::string Device::getProperty(const std::string& property, const std::string& _default) { - if (properties.find(property) == properties.end()) - { - if (_default == "") - EXCEPTION(DEV_INVALID_DEV_PROPERTY, "Invalid property " << property); + if (properties.find(property) == properties.end()) + { + if (_default == "") + EXCEPTION(DEV_INVALID_DEV_PROPERTY, "Invalid property " << property); - return _default; - } + return _default; + } - return properties[property]; + return properties[property]; } std::string Device::operator[](const std::string& property) { - return getProperty(property); + return getProperty(property); } } diff --git a/src/fulfillment_item.cpp b/src/fulfillment_item.cpp index c3ac17f..e69e446 100644 --- a/src/fulfillment_item.cpp +++ b/src/fulfillment_item.cpp @@ -17,7 +17,6 @@ along with libgourou. If not, see . */ -#include #include #include #include "user.h" @@ -25,109 +24,68 @@ namespace gourou { FulfillmentItem::FulfillmentItem(pugi::xml_document& doc, User* user) - : fulfillDoc(), loanToken(0) { - fulfillDoc.reset(doc); /* We must keep a copy */ - metadatas = fulfillDoc.select_node("//metadata").node(); + metadatas = doc.select_node("//metadata").node(); - if (!metadatas) - EXCEPTION(FFI_INVALID_FULFILLMENT_DATA, "No metadata tag in document"); + if (!metadatas) + EXCEPTION(FFI_INVALID_FULFILLMENT_DATA, "No metadata tag in document"); + + pugi::xml_node node = doc.select_node("/envelope/fulfillmentResult/resourceItemInfo/src").node(); + downloadURL = node.first_child().value(); - pugi::xml_node node = doc.select_node("/envelope/fulfillmentResult/resourceItemInfo/src").node(); - downloadURL = node.first_child().value(); + if (downloadURL == "") + EXCEPTION(FFI_INVALID_FULFILLMENT_DATA, "No download URL in document"); + + pugi::xml_node licenseToken = doc.select_node("/envelope/fulfillmentResult/resourceItemInfo/licenseToken").node(); - if (downloadURL == "") - EXCEPTION(FFI_INVALID_FULFILLMENT_DATA, "No download URL in document"); - - node = doc.select_node("/envelope/fulfillmentResult/resourceItemInfo/resource").node(); - resource = node.first_child().value(); - - if (resource == "") - EXCEPTION(FFI_INVALID_FULFILLMENT_DATA, "No resource in document"); - - pugi::xml_node licenseToken = doc.select_node("/envelope/fulfillmentResult/resourceItemInfo/licenseToken").node(); - - if (!licenseToken) - EXCEPTION(FFI_INVALID_FULFILLMENT_DATA, "Any license token in document"); - - buildRights(licenseToken, user); - - node = doc.select_node("/envelope/fulfillmentResult/returnable").node(); - try - { - if (node && node.first_child().value() == std::string("true")) - loanToken = new LoanToken(doc); - } - catch(std::exception& e) - { - GOUROU_LOG(ERROR, "Book is returnable, but contains invalid loan token"); - GOUROU_LOG(ERROR, e.what()); - } - } - - FulfillmentItem::~FulfillmentItem() - { - if (loanToken) delete loanToken; + if (!licenseToken) + EXCEPTION(FFI_INVALID_FULFILLMENT_DATA, "Any license token in document"); + + buildRights(licenseToken, user); } void FulfillmentItem::buildRights(const pugi::xml_node& licenseToken, User* user) { - pugi::xml_node decl = rights.append_child(pugi::node_declaration); - decl.append_attribute("version") = "1.0"; - - pugi::xml_node root = rights.append_child("adept:rights"); - root.append_attribute("xmlns:adept") = ADOBE_ADEPT_NS; - - pugi::xml_node newLicenseToken = root.append_copy(licenseToken); - if (!newLicenseToken.attribute("xmlns")) - newLicenseToken.append_attribute("xmlns") = ADOBE_ADEPT_NS; - - pugi::xml_node licenseServiceInfo = root.append_child("adept:licenseServiceInfo"); - pugi::xml_node licenseURL = licenseToken.select_node("licenseURL").node(); - licenseURL.set_name("adept:licenseURL"); - licenseServiceInfo.append_copy(licenseURL); - pugi::xml_node certificate = licenseServiceInfo.append_child("adept:certificate"); - std::string certificateValue = user->getLicenseServiceCertificate(licenseURL.first_child().value()); - certificate.append_child(pugi::node_pcdata).set_value(certificateValue.c_str()); + pugi::xml_node decl = rights.append_child(pugi::node_declaration); + decl.append_attribute("version") = "1.0"; + + pugi::xml_node root = rights.append_child("adept:rights"); + root.append_attribute("xmlns:adept") = ADOBE_ADEPT_NS; + + pugi::xml_node newLicenseToken = root.append_copy(licenseToken); + if (!newLicenseToken.attribute("xmlns")) + newLicenseToken.append_attribute("xmlns") = ADOBE_ADEPT_NS; + + pugi::xml_node licenseServiceInfo = root.append_child("licenseServiceInfo"); + licenseServiceInfo.append_attribute("xmlns") = ADOBE_ADEPT_NS; + licenseServiceInfo.append_copy(licenseToken.select_node("licenseURL").node()); + pugi::xml_node certificate = licenseServiceInfo.append_child("certificate"); + certificate.append_child(pugi::node_pcdata).set_value(user->getCertificate().c_str()); } - + std::string FulfillmentItem::getMetadata(std::string name) { - // https://stackoverflow.com/questions/313970/how-to-convert-an-instance-of-stdstring-to-lower-case -#if __STDC_VERSION__ >= 201112L - std::transform(name.begin(), name.end(), name.begin(), - [](unsigned char c){ return std::tolower(c); }); -#else - std::transform(name.begin(), name.end(), name.begin(), tolower); -#endif - name = std::string("dc:") + name; - pugi::xpath_node path = metadatas.select_node(name.c_str()); + // https://stackoverflow.com/questions/313970/how-to-convert-an-instance-of-stdstring-to-lower-case + std::transform(name.begin(), name.end(), name.begin(), + [](unsigned char c){ return std::tolower(c); }); + name = std::string("dc:") + name; + pugi::xpath_node path = metadatas.select_node(name.c_str()); - if (!path) - return ""; + if (!path) + return ""; - return path.node().first_child().value(); + return path.node().first_child().value(); } - + std::string FulfillmentItem::getRights() { - StringXMLWriter xmlWriter; - rights.save(xmlWriter, " "); - return xmlWriter.getResult(); + StringXMLWriter xmlWriter; + rights.save(xmlWriter, " "); + return xmlWriter.getResult(); } - + std::string FulfillmentItem::getDownloadURL() { - return downloadURL; - } - - std::string FulfillmentItem::getResource() - { - return resource; - } - - LoanToken* FulfillmentItem::getLoanToken() - { - return loanToken; + return downloadURL; } } diff --git a/src/libgourou.cpp b/src/libgourou.cpp index e34b676..65d961d 100644 --- a/src/libgourou.cpp +++ b/src/libgourou.cpp @@ -21,16 +21,11 @@ #include #include #include -#include - -#include #include #include #include -#define LOCAL_ADEPT_DIR "./.adept" - #define ASN_NONE 0x00 #define ASN_NS_TAG 0x01 #define ASN_CHILD 0x02 @@ -40,1518 +35,771 @@ namespace gourou { - GOUROU_LOG_LEVEL logLevel = LG_LOG_WARN; + GOUROU_LOG_LEVEL logLevel = WARN; const std::string DRMProcessor::VERSION = LIBGOUROU_VERSION; - + DRMProcessor::DRMProcessor(DRMProcessorClient* client):client(client), device(0), user(0) { - if (!client) - EXCEPTION(GOUROU_INVALID_CLIENT, "DRMProcessorClient is NULL"); + if (!client) + EXCEPTION(GOUROU_INVALID_CLIENT, "DRMProcessorClient is NULL"); } DRMProcessor::DRMProcessor(DRMProcessorClient* client, - const std::string& deviceFile, const std::string& activationFile, - const std::string& deviceKeyFile): - client(client), device(0), user(0) + const std::string& deviceFile, const std::string& activationFile, + const std::string& deviceKeyFile): + client(client), device(0), user(0) { - if (!client) - EXCEPTION(GOUROU_INVALID_CLIENT, "DRMProcessorClient is NULL"); + if (!client) + EXCEPTION(GOUROU_INVALID_CLIENT, "DRMProcessorClient is NULL"); + + device = new Device(this, deviceFile, deviceKeyFile); + user = new User(this, activationFile); - device = new Device(this, deviceFile, deviceKeyFile); - user = new User(this, activationFile); - - if (user->getDeviceFingerprint() != "" && - (*device)["fingerprint"] != user->getDeviceFingerprint()) - EXCEPTION(GOUROU_DEVICE_DOES_NOT_MATCH, "User and device fingerprint does not match"); + if (user->getDeviceFingerprint() != "" && + (*device)["fingerprint"] != user->getDeviceFingerprint()) + EXCEPTION(GOUROU_DEVICE_DOES_NOT_MATCH, "User and device fingerprint does not match"); } DRMProcessor::~DRMProcessor() { - if (device) delete device; - if (user) delete user; + if (device) delete device; + if (user) delete user; } - // function to parse a date or time string. - // https://www.geeksforgeeks.org/cpp/date-and-time-parsing-in-cpp/ - static time_t parseDateTime(const char* datetimeString, const char* format) + DRMProcessor* DRMProcessor::createDRMProcessor(DRMProcessorClient* client, bool randomSerial, const std::string& dirName, + const std::string& hobbes, const std::string& ACSServer) { - struct tm tmStruct; - strptime(datetimeString, format, &tmStruct); - return mktime(&tmStruct); + DRMProcessor* processor = new DRMProcessor(client); + + Device* device = Device::createDevice(processor, dirName, hobbes, randomSerial); + processor->device = device; + + User* user = User::createUser(processor, dirName, ACSServer); + processor->user = user; + + return processor; } - DRMProcessor* DRMProcessor::createDRMProcessor(DRMProcessorClient* client, bool randomSerial, std::string dirName, - const std::string& hobbes, const std::string& ACSServer) - { - DRMProcessor* processor = new DRMProcessor(client); - - if (dirName == "") - dirName = getDefaultAdeptDir(); - - Device* device = Device::createDevice(processor, dirName, hobbes, randomSerial); - processor->device = device; - - User* user = User::createUser(processor, dirName, ACSServer); - processor->user = user; - - return processor; - } - - + void DRMProcessor::pushString(void* sha_ctx, const std::string& string) { - int length = string.length(); - uint16_t nlength = htons(length); - char c; + int length = string.length(); + uint16_t nlength = htons(length); + char c; - if (logLevel >= LG_LOG_TRACE) - printf("%02x %02x ", ((uint8_t*)&nlength)[0], ((uint8_t*)&nlength)[1]); + if (logLevel >= TRACE) + 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)); - - for(int i=0; idigestUpdate(sha_ctx, (unsigned char*)&c, 1); - if (logLevel >= LG_LOG_TRACE) - printf("%c", c); - } - if (logLevel >= LG_LOG_TRACE) - printf("\n"); + for(int i=0; idigestUpdate(sha_ctx, (unsigned char*)&c, 1); + if (logLevel >= TRACE) + printf("%c", c); + } + if (logLevel >= TRACE) + printf("\n"); } void DRMProcessor::pushTag(void* sha_ctx, uint8_t tag) { - client->digestUpdate(sha_ctx, &tag, sizeof(tag)); - if (logLevel >= LG_LOG_TRACE) - printf("%02x ", tag); + client->digestUpdate(sha_ctx, &tag, sizeof(tag)); + if (logLevel >= TRACE) + printf("%02x ", tag); } void DRMProcessor::hashNode(const pugi::xml_node& root, void *sha_ctx, std::map nsHash) { - switch(root.type()) - { - case pugi::node_element: - { - std::string name = root.name(); + switch(root.type()) + { + case pugi::node_element: + { + std::string name = root.name(); - // Look for "xmlns[:]" attribute - for (pugi::xml_attribute_iterator ait = root.attributes_begin(); - ait != root.attributes_end(); ++ait) - { - std::string attrName(ait->name()); + // Look for "xmlns[:]" attribute + for (pugi::xml_attribute_iterator ait = root.attributes_begin(); + ait != root.attributes_end(); ++ait) + { + std::string attrName(ait->name()); - if (attrName.find("xmlns") == 0) - { - std::string ns("GENERICNS"); - // Compound xmlns:Name attribute - if (attrName.find(':') != std::string::npos) - ns = attrName.substr(attrName.find(':')+1); + if (attrName.find("xmlns") == 0) + { + std::string ns("GENERICNS"); + // Compound xmlns:Name attribute + if (attrName.find(':') != std::string::npos) + ns = attrName.substr(attrName.find(':')+1); - nsHash[ns] = ait->value(); - // Don't break here because we may multiple xmlns definitions - // break; - } - } + nsHash[ns] = ait->value(); + // Don't break here because we may multiple xmlns definitions + // break; + } + } - // Remove namespace from tag - // If we have a namespace for the first time, put it to hash - if (name.find(':') != std::string::npos) - { - size_t nsIndex = name.find(':'); - std::string nodeNS = name.substr(0, nsIndex); + // Remove namespace from tag + // If we have a namespace for the first time, put it to hash + if (name.find(':') != std::string::npos) + { + size_t nsIndex = name.find(':'); + std::string nodeNS = name.substr(0, nsIndex); - pushTag(sha_ctx, ASN_NS_TAG); - pushString(sha_ctx, nsHash[nodeNS]); + pushTag(sha_ctx, ASN_NS_TAG); + pushString(sha_ctx, nsHash[nodeNS]); + + name = name.substr(nsIndex+1); + } + // Global xmlns, always send to hash + else if (nsHash.find("GENERICNS") != nsHash.end()) + { + pushTag(sha_ctx, ASN_NS_TAG); + pushString(sha_ctx, nsHash["GENERICNS"]); + } + + pushString(sha_ctx, name); - name = name.substr(nsIndex+1); - } - // Global xmlns, always send to hash - else if (nsHash.find("GENERICNS") != nsHash.end()) - { - pushTag(sha_ctx, ASN_NS_TAG); - pushString(sha_ctx, nsHash["GENERICNS"]); - } + std::vector attributes; + pugi::xml_attribute attr; + + for (attr = root.first_attribute(); + attr; attr = attr.next_attribute()) + { + if (std::string(attr.name()).find("xmlns") != std::string::npos) + continue; - pushString(sha_ctx, name); + attributes.push_back(attr.name()); + } - std::vector attributes; - pugi::xml_attribute attr; + // Attributes must be handled in alphabetical order + std::sort(attributes.begin(), attributes.end()); - for (attr = root.first_attribute(); - attr; attr = attr.next_attribute()) - { - if (std::string(attr.name()).find("xmlns") != std::string::npos) - continue; + std::vector::iterator attributesIt; + for(attributesIt = attributes.begin(); + attributesIt != attributes.end(); + attributesIt++) + { + attr = root.attribute(attributesIt->c_str()); + + pushTag(sha_ctx, ASN_ATTRIBUTE); + pushString(sha_ctx, ""); + + pushString(sha_ctx, attr.name()); + pushString(sha_ctx, attr.value()); + } - attributes.push_back(attr.name()); - } + pushTag(sha_ctx, ASN_CHILD); - // Attributes must be handled in alphabetical order - std::sort(attributes.begin(), attributes.end()); + for (pugi::xml_node child : root.children()) + hashNode(child, sha_ctx, nsHash); - std::vector::iterator attributesIt; - for(attributesIt = attributes.begin(); - attributesIt != attributes.end(); - attributesIt++) - { - attr = root.attribute(attributesIt->c_str()); + pushTag(sha_ctx, ASN_END_TAG); + + break; + } + case pugi::node_pcdata: + { + std::string trimmed = root.value(); + trimmed = trim(trimmed); - pushTag(sha_ctx, ASN_ATTRIBUTE); - pushString(sha_ctx, ""); + if (trimmed.length()) + { + pushTag(sha_ctx, ASN_TEXT); + pushString(sha_ctx, trimmed); + } - pushString(sha_ctx, attr.name()); - pushString(sha_ctx, attr.value()); - } - - pushTag(sha_ctx, ASN_CHILD); - - for (pugi::xml_node child : root.children()) - hashNode(child, sha_ctx, nsHash); - - pushTag(sha_ctx, ASN_END_TAG); - - break; - } - case pugi::node_pcdata: - { - std::string trimmed = root.value(); - trimmed = trim(trimmed); - - if (trimmed.length()) - { - pushTag(sha_ctx, ASN_TEXT); - pushString(sha_ctx, trimmed); - } - - break; - } - default: - break; - } + break; + } + default: + break; + } } - + void DRMProcessor::hashNode(const pugi::xml_node& root, unsigned char* sha_out) { - void* sha_ctx = client->createDigest("SHA1"); + void* sha_ctx = client->createDigest("SHA1"); + + std::map nsHash; - std::map nsHash; + hashNode(root, sha_ctx, nsHash); - hashNode(root, sha_ctx, nsHash); + client->digestFinalize(sha_ctx, sha_out); + + if (logLevel >= DEBUG) + { + printf("\nSHA OUT : "); + for(int i=0; i<(int)SHA1_LEN; i++) + printf("%02x ", sha_out[i]); + printf("\n"); - client->digestFinalize(sha_ctx, sha_out); - - dumpBuffer(gourou::LG_LOG_DEBUG, "\nSHA OUT : ", sha_out, SHA1_LEN); + } } - void DRMProcessor::signNode(pugi::xml_node& rootNode) + std::string DRMProcessor::signNode(const pugi::xml_node& rootNode) { - // Compute hash - unsigned char sha_out[SHA1_LEN]; + // Compute hash + unsigned char sha_out[SHA1_LEN]; - hashNode(rootNode, sha_out); + hashNode(rootNode, sha_out); + + // Sign with private key + unsigned char res[RSA_KEY_SIZE]; + ByteArray deviceKey(device->getDeviceKey(), Device::DEVICE_KEY_SIZE); + std::string pkcs12 = user->getPKCS12(); + ByteArray privateRSAKey = ByteArray::fromBase64(pkcs12); + + client->RSAPrivateEncrypt(privateRSAKey.data(), privateRSAKey.length(), + RSAInterface::RSA_KEY_PKCS12, deviceKey.toBase64().data(), + sha_out, sizeof(sha_out), res); + if (logLevel >= DEBUG) + { + printf("Sig : "); + for(int i=0; i<(int)sizeof(res); i++) + printf("%02x ", res[i]); + printf("\n"); + } - // Sign with private key - unsigned char res[RSA_KEY_SIZE]; - ByteArray deviceKey(device->getDeviceKey(), Device::DEVICE_KEY_SIZE); - std::string pkcs12 = user->getPKCS12(); - ByteArray privateRSAKey = ByteArray::fromBase64(pkcs12); + ByteArray signature(res, sizeof(res)); - client->RSAPrivateEncrypt(privateRSAKey.data(), privateRSAKey.length(), - RSAInterface::RSA_KEY_PKCS12, deviceKey.toBase64().data(), - sha_out, sizeof(sha_out), res); - - dumpBuffer(gourou::LG_LOG_DEBUG, "Sig : ", res, sizeof(res)); - - std::string signature = ByteArray(res, sizeof(res)).toBase64(); - appendTextElem(rootNode, "adept:signature", signature); + return signature.toBase64(); } void DRMProcessor::addNonce(pugi::xml_node& root) { - /* - r4 = tp->time - r3 = 0 - r2 = tm->militime - r0 = 0x6f046000 - r1 = 0x388a + /* + r4 = tp->time + r3 = 0 + r2 = tm->militime + r0 = 0x6f046000 + r1 = 0x388a + + r3 += high(r4*1000) + r2 += low(r4*1000) + + r0 += r2 + r1 += r3 + */ + struct timeval tv; + gettimeofday(&tv, 0); + uint32_t nonce32[2] = {0x6f046000, 0x388a}; + uint64_t bigtime = tv.tv_sec*1000; + nonce32[0] += (bigtime & 0xFFFFFFFF) + (tv.tv_usec/1000); + nonce32[1] += ((bigtime >> 32) & 0xFFFFFFFF); + + ByteArray nonce((const unsigned char*)&nonce32, sizeof(nonce32)); + uint32_t tmp = 0; + nonce.append((const unsigned char*)&tmp, sizeof(tmp)); + appendTextElem(root, "adept:nonce", nonce.toBase64().data()); - r3 += high(r4*1000) - r2 += low(r4*1000) + time_t _time = time(0) + 10*60; // Cur time + 10 minutes + struct tm* tm_info = gmtime(&_time); + char buffer[32]; - r0 += r2 - r1 += r3 - */ - struct timeval tv; - gettimeofday(&tv, 0); - uint32_t nonce32[2] = {0x6f046000, 0x388a}; -#ifdef STATIC_NONCE - uint64_t bigtime = 0xAA001122BBCCAAULL; -#else - uint64_t bigtime = tv.tv_sec*1000; -#endif - nonce32[0] += (bigtime & 0xFFFFFFFF) + (tv.tv_usec/1000); - nonce32[1] += ((bigtime >> 32) & 0xFFFFFFFF); - - ByteArray nonce((const unsigned char*)&nonce32, sizeof(nonce32)); - uint32_t tmp = 0; - nonce.append((const unsigned char*)&tmp, sizeof(tmp)); - appendTextElem(root, "adept:nonce", nonce.toBase64().data()); - - time_t _time = time(0) + 10*60; // Cur time + 10 minutes - struct tm* tm_info = gmtime(&_time); - char buffer[32]; - - strftime(buffer, sizeof(buffer), "%Y-%m-%dT%H:%M:%SZ", tm_info); - appendTextElem(root, "adept:expiration", buffer); + strftime(buffer, sizeof(buffer), "%Y-%m-%dT%H:%M:%SZ", tm_info); + appendTextElem(root, "adept:expiration", buffer); } - - ByteArray DRMProcessor::sendRequest(const std::string& URL, const std::string& POSTdata, const char* contentType, std::map* responseHeaders, int fd, bool resume) + + ByteArray DRMProcessor::sendRequest(const std::string& URL, const std::string& POSTdata, const char* contentType) { - if (contentType == 0) - contentType = ""; - std::string reply = client->sendHTTPRequest(URL, POSTdata, contentType, responseHeaders, fd, resume); + if (contentType == 0) + contentType = ""; + std::string reply = client->sendHTTPRequest(URL, POSTdata, contentType); - if (fd) return ByteArray(); + pugi::xml_document replyDoc; + replyDoc.load_buffer(reply.c_str(), reply.length()); - pugi::xml_document replyDoc; - replyDoc.load_buffer(reply.c_str(), reply.length()); - - pugi::xml_node root = replyDoc.first_child(); - if (std::string(root.name()) == "error") - { - EXCEPTION(GOUROU_ADEPT_ERROR, root.attribute("data").value()); - } - - return ByteArray(reply); + pugi::xml_node root = replyDoc.first_child(); + if (std::string(root.name()) == "error") + { + EXCEPTION(GOUROU_ADEPT_ERROR, root.attribute("data").value()); + } + + return ByteArray(reply); } ByteArray DRMProcessor::sendRequest(const pugi::xml_document& document, const std::string& url) { - StringXMLWriter xmlWriter; - document.save(xmlWriter, " "); - std::string xmlStr = xmlWriter.getResult(); + StringXMLWriter xmlWriter; + document.save(xmlWriter, " "); + std::string xmlStr = xmlWriter.getResult(); - return sendRequest(url, xmlStr, (const char*)"application/vnd.adobe.adept+xml"); + return sendRequest(url, xmlStr, (const char*)"application/vnd.adobe.adept+xml"); } - + void DRMProcessor::buildAuthRequest(pugi::xml_document& authReq) { - pugi::xml_node decl = authReq.append_child(pugi::node_declaration); - decl.append_attribute("version") = "1.0"; + pugi::xml_node decl = authReq.append_child(pugi::node_declaration); + decl.append_attribute("version") = "1.0"; + + pugi::xml_node root = authReq.append_child("adept:credentials"); + root.append_attribute("xmlns:adept") = ADOBE_ADEPT_NS; - pugi::xml_node root = authReq.append_child("adept:credentials"); - root.append_attribute("xmlns:adept") = ADOBE_ADEPT_NS; + appendTextElem(root, "adept:user", user->getUUID()); - appendTextElem(root, "adept:user", user->getUUID()); + ByteArray deviceKey(device->getDeviceKey(), Device::DEVICE_KEY_SIZE); + unsigned char* pkcs12 = 0; + unsigned int pkcs12Length; + ByteArray pkcs12Cert = ByteArray::fromBase64(user->getPKCS12()); + + client->extractCertificate(pkcs12Cert.data(), pkcs12Cert.length(), + RSAInterface::RSA_KEY_PKCS12, deviceKey.toBase64().data(), + &pkcs12, &pkcs12Length); + ByteArray privateCertificate(pkcs12, pkcs12Length); + free(pkcs12); - ByteArray deviceKey(device->getDeviceKey(), Device::DEVICE_KEY_SIZE); - unsigned char* pkcs12 = 0; - unsigned int pkcs12Length; - ByteArray pkcs12Cert = ByteArray::fromBase64(user->getPKCS12()); - - client->extractCertificate(pkcs12Cert.data(), pkcs12Cert.length(), - RSAInterface::RSA_KEY_PKCS12, deviceKey.toBase64().data(), - &pkcs12, &pkcs12Length); - ByteArray privateCertificate(pkcs12, pkcs12Length); - free(pkcs12); - - appendTextElem(root, "adept:certificate", privateCertificate.toBase64()); - appendTextElem(root, "adept:licenseCertificate", user->getProperty("//adept:licenseCertificate")); - appendTextElem(root, "adept:authenticationCertificate", user->getProperty("//adept:authenticationCertificate")); + appendTextElem(root, "adept:certificate", privateCertificate.toBase64()); + appendTextElem(root, "adept:licenseCertificate", user->getProperty("//adept:licenseCertificate")); + appendTextElem(root, "adept:authenticationCertificate", user->getProperty("//adept:authenticationCertificate")); } - + void DRMProcessor::buildInitLicenseServiceRequest(pugi::xml_document& initLicReq, std::string operatorURL) { - pugi::xml_node decl = initLicReq.append_child(pugi::node_declaration); - decl.append_attribute("version") = "1.0"; + pugi::xml_node decl = initLicReq.append_child(pugi::node_declaration); + decl.append_attribute("version") = "1.0"; + + pugi::xml_node root = initLicReq.append_child("adept:licenseServiceRequest"); + root.append_attribute("xmlns:adept") = ADOBE_ADEPT_NS; + root.append_attribute("identity") = "user"; - pugi::xml_node root = initLicReq.append_child("adept:licenseServiceRequest"); - root.append_attribute("xmlns:adept") = ADOBE_ADEPT_NS; - root.append_attribute("identity") = "user"; + appendTextElem(root, "adept:operatorURL", operatorURL); + addNonce(root); + appendTextElem(root, "adept:user", user->getUUID()); - appendTextElem(root, "adept:operatorURL", operatorURL); - addNonce(root); - appendTextElem(root, "adept:user", user->getUUID()); - - signNode(root); + std::string signature = signNode(root); + appendTextElem(root, "adept:signature", signature); } - + void DRMProcessor::doOperatorAuth(std::string operatorURL) { - pugi::xml_document authReq; - buildAuthRequest(authReq); - std::string authURL = operatorURL; - unsigned int fulfillPos = authURL.rfind("Fulfill"); - if (fulfillPos == (authURL.size() - (sizeof("Fulfill")-1))) - authURL = authURL.substr(0, fulfillPos-1); - ByteArray replyData = sendRequest(authReq, authURL + "/Auth"); + pugi::xml_document authReq; + buildAuthRequest(authReq); + std::string authURL = operatorURL; + unsigned int fulfillPos = authURL.rfind("Fulfill"); + if (fulfillPos == (authURL.size() - (sizeof("Fulfill")-1))) + authURL = authURL.substr(0, fulfillPos-1); + ByteArray replyData = sendRequest(authReq, authURL + "/Auth"); - pugi::xml_document initLicReq; - std::string activationURL = user->getProperty("//adept:activationURL"); - buildInitLicenseServiceRequest(initLicReq, authURL); - sendRequest(initLicReq, activationURL + "/InitLicenseService"); + pugi::xml_document initLicReq; + std::string activationURL = user->getProperty("//adept:activationURL"); + buildInitLicenseServiceRequest(initLicReq, authURL); + sendRequest(initLicReq, activationURL + "/InitLicenseService"); } - + void DRMProcessor::operatorAuth(std::string operatorURL) { - pugi::xpath_node_set operatorList = user->getProperties("//adept:operatorURL"); + pugi::xpath_node_set operatorList = user->getProperties("//adept:operatorURL"); + + for (pugi::xpath_node_set::const_iterator operatorIt = operatorList.begin(); + operatorIt != operatorList.end(); ++operatorIt) + { + std::string value = operatorIt->node().first_child().value(); + if (trim(value) == operatorURL) + { + GOUROU_LOG(DEBUG, "Already authenticated to operator " << operatorURL); + return; + } + } + + doOperatorAuth(operatorURL); + + // Add new operatorURL to list + pugi::xml_document activationDoc; + user->readActivation(activationDoc); - for (pugi::xpath_node_set::const_iterator operatorIt = operatorList.begin(); - operatorIt != operatorList.end(); ++operatorIt) - { - std::string value = operatorIt->node().first_child().value(); - if (trim(value) == operatorURL) - { - GOUROU_LOG(DEBUG, "Already authenticated to operator " << operatorURL); - return; - } - } + pugi::xml_node root; + pugi::xpath_node xpathRes = activationDoc.select_node("//adept:operatorURLList"); - doOperatorAuth(operatorURL); + // 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) { - pugi::xml_node decl = fulfillReq.append_child(pugi::node_declaration); - decl.append_attribute("version") = "1.0"; + pugi::xml_node decl = fulfillReq.append_child(pugi::node_declaration); + decl.append_attribute("version") = "1.0"; + + pugi::xml_node root = fulfillReq.append_child("adept:fulfill"); + root.append_attribute("xmlns:adept") = ADOBE_ADEPT_NS; - pugi::xml_node root = fulfillReq.append_child("adept:fulfill"); - root.append_attribute("xmlns:adept") = ADOBE_ADEPT_NS; + appendTextElem(root, "adept:user", user->getUUID()); + appendTextElem(root, "adept:device", user->getDeviceUUID()); + appendTextElem(root, "adept:deviceType", (*device)["deviceType"]); - appendTextElem(root, "adept:user", user->getUUID()); - appendTextElem(root, "adept:device", user->getDeviceUUID()); - appendTextElem(root, "adept:deviceType", (*device)["deviceType"]); + root.append_copy(acsmDoc.first_child()); - root.append_copy(acsmDoc.first_child()); - - pugi::xml_node targetDevice = root.append_child("adept:targetDevice"); - appendTextElem(targetDevice, "adept:softwareVersion", (*device)["hobbes"]); - appendTextElem(targetDevice, "adept:clientOS", (*device)["clientOS"]); - appendTextElem(targetDevice, "adept:clientLocale", (*device)["clientLocale"]); - appendTextElem(targetDevice, "adept:clientVersion", (*device)["deviceClass"]); - appendTextElem(targetDevice, "adept:deviceType", (*device)["deviceType"]); - appendTextElem(targetDevice, "adept:fingerprint", (*device)["fingerprint"]); - - pugi::xml_node activationToken = targetDevice.append_child("adept:activationToken"); - appendTextElem(activationToken, "adept:user", user->getUUID()); - appendTextElem(activationToken, "adept:device", user->getDeviceUUID()); + pugi::xml_node targetDevice = root.append_child("adept:targetDevice"); + appendTextElem(targetDevice, "adept:softwareVersion", (*device)["hobbes"]); + appendTextElem(targetDevice, "adept:clientOS", (*device)["clientOS"]); + appendTextElem(targetDevice, "adept:clientLocale", (*device)["clientLocale"]); + appendTextElem(targetDevice, "adept:clientVersion", (*device)["deviceClass"]); + appendTextElem(targetDevice, "adept:deviceType", (*device)["deviceType"]); + appendTextElem(targetDevice, "adept:fingerprint", (*device)["fingerprint"]); + + pugi::xml_node activationToken = targetDevice.append_child("adept:activationToken"); + appendTextElem(activationToken, "adept:user", user->getUUID()); + appendTextElem(activationToken, "adept:device", user->getDeviceUUID()); } - void DRMProcessor::fetchLicenseServiceCertificate(const std::string& licenseURL, - const std::string& operatorURL) + FulfillmentItem* DRMProcessor::fulfill(const std::string& ACSMFile) { - if (user->getLicenseServiceCertificate(licenseURL) != "") - return; + if (!user->getPKCS12().length()) + EXCEPTION(FF_NOT_ACTIVATED, "Device not activated"); + + pugi::xml_document acsmDoc; - std::string licenseServiceInfoReq = operatorURL + "/LicenseServiceInfo?licenseURL=" + licenseURL; + 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); - ByteArray replyData; - replyData = sendRequest(licenseServiceInfoReq); + GOUROU_LOG(INFO, "Fulfill " << ACSMFile); - pugi::xml_document licenseServicesDoc; - licenseServicesDoc.load_buffer(replyData.data(), replyData.length()); + // Build req file + pugi::xml_document fulfillReq; - // Add new license certificate - pugi::xml_document activationDoc; - user->readActivation(activationDoc); + buildFulfillRequest(acsmDoc, fulfillReq); + pugi::xpath_node root = fulfillReq.select_node("//adept:fulfill"); + pugi::xml_node rootNode = root.node(); - pugi::xml_node root; - pugi::xpath_node xpathRes = activationDoc.select_node("//adept:licenseServices"); + // Remove HMAC + pugi::xpath_node xpathRes = fulfillReq.select_node("//hmac"); - // Create adept:licenseServices if it doesn't exists - if (!xpathRes) - { - xpathRes = activationDoc.select_node("/activationInfo"); - root = xpathRes.node(); - root = root.append_child("adept:licenseServices"); - root.append_attribute("xmlns:adept") = ADOBE_ADEPT_NS; - } - else - root = xpathRes.node(); + if (!xpathRes) + EXCEPTION(FF_NO_HMAC_IN_ACSM_FILE, "hmac tag not found in ACSM file"); - root = root.append_child("adept:licenseServiceInfo"); + pugi::xml_node hmacNode = xpathRes.node(); + pugi::xml_node hmacParentNode = hmacNode.parent(); + + hmacParentNode.remove_child(hmacNode); - std::string certificate = extractTextElem(licenseServicesDoc, - "/licenseServiceInfo/certificate"); + std::string signature = signNode(rootNode); + + // Add removed HMAC + appendTextElem(hmacParentNode, hmacNode.name(), hmacNode.first_child().value()); + + appendTextElem(rootNode, "adept:signature", signature); - appendTextElem(root, "adept:licenseURL", licenseURL); - appendTextElem(root, "adept:certificate", certificate); + pugi::xpath_node node = acsmDoc.select_node("//operatorURL"); + if (!node) + EXCEPTION(FF_NO_OPERATOR_URL, "OperatorURL not found in ACSM document"); + + std::string operatorURL = node.node().first_child().value(); + operatorURL = trim(operatorURL) + "/Fulfill"; - // Add new operatorURL to list - xpathRes = activationDoc.select_node("//adept:operatorURLList"); + operatorAuth(operatorURL); + + ByteArray replyData; - // 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; + try + { + replyData = sendRequest(fulfillReq, operatorURL); + } + catch (gourou::Exception& e) + { + /* + Operator requires authentication even if it's already in + our operator list + */ + std::string errorMsg(e.what()); + if (e.getErrorCode() == GOUROU_ADEPT_ERROR && + errorMsg.find("E_ADEPT_DISTRIBUTOR_AUTH") != std::string::npos) + { + doOperatorAuth(operatorURL); + replyData = sendRequest(fulfillReq, operatorURL); + } + else + { + throw e; + } + } - appendTextElem(root, "adept:user", user->getUUID()); - } - else - root = xpathRes.node(); + pugi::xml_document fulfillReply; - appendTextElem(root, "adept:operatorURL", operatorURL); - - user->updateActivationFile(activationDoc); + fulfillReply.load_string((const char*)replyData.data()); + + return new FulfillmentItem(fulfillReply, user); } - FulfillmentItem* DRMProcessor::fulfill(const std::string& ACSMFile, bool notify) + void DRMProcessor::download(FulfillmentItem* item, std::string path) { - if (!user->getPKCS12().length()) - EXCEPTION(FF_NOT_ACTIVATED, "Device not activated"); + if (!item) + EXCEPTION(DW_NO_ITEM, "No item"); + + ByteArray replyData = sendRequest(item->getDownloadURL()); - pugi::xml_document acsmDoc; + writeFile(path, replyData); - 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); + GOUROU_LOG(INFO, "Download into " << path); - // 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()); - } + std::string rightsStr = item->getRights(); - GOUROU_LOG(INFO, "Fulfill " << ACSMFile); - - std::string expiration = extractTextElem(rootNode, "expiration", false); - - if (expiration != "") - { - time_t expirationTime = parseDateTime(expiration.c_str(), "%Y-%m-%dT%H:%M:%S"); - - if (time(NULL) > expirationTime) - GOUROU_LOG(WARN, "WARNING: ACSM file expired (" << expiration << "), It may not work."); - } - - // Build req file - pugi::xml_document fulfillReq; - - buildFulfillRequest(acsmDoc, fulfillReq); - pugi::xpath_node root = fulfillReq.select_node("//adept:fulfill"); - rootNode = root.node(); - - // Remove HMAC - pugi::xpath_node xpathRes = fulfillReq.select_node("//hmac"); - - if (!xpathRes) - EXCEPTION(FF_NO_HMAC_IN_ACSM_FILE, "hmac tag not found in ACSM file"); - - pugi::xml_node hmacNode = xpathRes.node(); - pugi::xml_node hmacParentNode = hmacNode.parent(); - - hmacParentNode.remove_child(hmacNode); - - signNode(rootNode); - - // Add removed HMAC - appendTextElem(hmacParentNode, hmacNode.name(), hmacNode.first_child().value()); - - pugi::xpath_node node = acsmDoc.select_node("//operatorURL"); - if (!node) - EXCEPTION(FF_NO_OPERATOR_URL, "OperatorURL not found in ACSM document"); - - std::string operatorURL = node.node().first_child().value(); - operatorURL = trim(operatorURL); - std::string fulfillURL = operatorURL + "/Fulfill"; - - operatorAuth(fulfillURL); - - ByteArray replyData; - - try - { - replyData = sendRequest(fulfillReq, fulfillURL); - } - catch (gourou::Exception& e) - { - /* - Operator requires authentication even if it's already in - our operator list - */ - std::string errorMsg(e.what()); - if (e.getErrorCode() == GOUROU_ADEPT_ERROR && - errorMsg.find("E_ADEPT_DISTRIBUTOR_AUTH") != std::string::npos) - { - doOperatorAuth(fulfillURL); - replyData = sendRequest(fulfillReq, fulfillURL); - } - else - { - throw e; - } - } - - pugi::xml_document fulfillReply; - - fulfillReply.load_string((const char*)replyData.data()); - - std::string licenseURL = extractTextElem(fulfillReply, "//licenseToken/licenseURL"); - - fetchLicenseServiceCertificate(licenseURL, operatorURL); - - FulfillmentItem* item = new FulfillmentItem(fulfillReply, user); - - if (notify) - notifyServer(fulfillReply); - - return item; - } - - DRMProcessor::ITEM_TYPE DRMProcessor::download(FulfillmentItem* item, std::string path, bool resume) - { - ITEM_TYPE res = EPUB; - - if (!item) - EXCEPTION(DW_NO_ITEM, "No item"); - - std::map headers; - - int fd = createNewFile(path, !resume); - - sendRequest(item->getDownloadURL(), "", 0, &headers, fd, resume); - - close(fd); - - GOUROU_LOG(INFO, "Download into " << path); - - ByteArray rightsStr(item->getRights()); - - if (item->getMetadata("format").find("application/pdf") != std::string::npos) - res = PDF; - - if (headers.count("Content-Type") && - headers["Content-Type"].find("application/pdf") != std::string::npos) - res = PDF; - - if (res == EPUB) - { - void* handler = client->zipOpen(path); - client->zipWriteFile(handler, "META-INF/rights.xml", rightsStr); - client->zipClose(handler); - } - else if (res == PDF) - { - uPDFParser::Parser parser; - bool EBXHandlerFound = false; - - try - { - GOUROU_LOG(DEBUG, "Parse PDF"); - parser.parse(path); - } - catch(std::invalid_argument& e) - { - GOUROU_LOG(ERROR, "Invalid PDF"); - return res; - } - - std::vector objects = parser.objects(); - std::vector::reverse_iterator it; - - for(it = objects.rbegin(); it != objects.rend(); it++) - { - // Update EBX_HANDLER with rights - if ((*it)->hasKey("Filter") && (**it)["Filter"]->str() == "/EBX_HANDLER") - { - EBXHandlerFound = true; - uPDFParser::Object* ebx = (*it)->clone(); - (*ebx)["ADEPT_ID"] = new uPDFParser::String(item->getResource()); - (*ebx)["EBX_BOOKID"] = new uPDFParser::String(item->getResource()); - ByteArray zipped; - client->deflate(rightsStr, zipped); - (*ebx)["ADEPT_LICENSE"] = new uPDFParser::String(zipped.toBase64()); - parser.addObject(ebx); - break; - } - } - - if (EBXHandlerFound) - parser.write(path, true); - else - { - EXCEPTION(DW_NO_EBX_HANDLER, "EBX_HANDLER not found"); - } - } - - return res; + void* handler = client->zipOpen(path); + client->zipWriteFile(handler, "META-INF/rights.xml", rightsStr); + client->zipClose(handler); } void DRMProcessor::buildSignInRequest(pugi::xml_document& signInRequest, - const std::string& adobeID, const std::string& adobePassword, - const std::string& authenticationCertificate) + const std::string& adobeID, const std::string& adobePassword, + const std::string& authenticationCertificate) { - pugi::xml_node decl = signInRequest.append_child(pugi::node_declaration); - decl.append_attribute("version") = "1.0"; - pugi::xml_node signIn = signInRequest.append_child("adept:signIn"); - signIn.append_attribute("xmlns:adept") = ADOBE_ADEPT_NS; - std::string loginMethod = user->getLoginMethod(); + pugi::xml_node decl = signInRequest.append_child(pugi::node_declaration); + decl.append_attribute("version") = "1.0"; + pugi::xml_node signIn = signInRequest.append_child("adept:signIn"); + signIn.append_attribute("xmlns:adept") = ADOBE_ADEPT_NS; + std::string loginMethod = user->getLoginMethod(); + if (loginMethod.size()) + signIn.append_attribute("method") = loginMethod.c_str(); + else + signIn.append_attribute("method") = "AdobeID"; - if (adobeID == "anonymous") - signIn.append_attribute("method") = "anonymous"; - else if (loginMethod.size()) - signIn.append_attribute("method") = loginMethod.c_str(); - else - signIn.append_attribute("method") = "AdobeID"; + unsigned char encryptedSignInData[RSA_KEY_SIZE]; + const unsigned char* deviceKey = device->getDeviceKey(); - unsigned char encryptedSignInData[RSA_KEY_SIZE]; - const unsigned char* deviceKey = device->getDeviceKey(); + ByteArray _authenticationCertificate = ByteArray::fromBase64(authenticationCertificate); - ByteArray _authenticationCertificate = ByteArray::fromBase64(authenticationCertificate); + // Build buffer + ByteArray ar(deviceKey, Device::DEVICE_KEY_SIZE); + ar.append((unsigned char)adobeID.length()); + ar.append(adobeID); + ar.append((unsigned char)adobePassword.length()); + ar.append(adobePassword); - // Build buffer - ByteArray ar(deviceKey, Device::DEVICE_KEY_SIZE); - ar.append((unsigned char)adobeID.length()); - ar.append(adobeID); - ar.append((unsigned char)adobePassword.length()); - ar.append(adobePassword); + // Encrypt with authentication certificate (public part) + client->RSAPublicEncrypt(_authenticationCertificate.data(), + _authenticationCertificate.length(), + RSAInterface::RSA_KEY_X509, + ar.data(), ar.length(), encryptedSignInData); - // Encrypt with authentication certificate (public part) - client->RSAPublicEncrypt(_authenticationCertificate.data(), - _authenticationCertificate.length(), - RSAInterface::RSA_KEY_X509, - ar.data(), ar.length(), encryptedSignInData); + ar = ByteArray(encryptedSignInData, sizeof(encryptedSignInData)); + appendTextElem(signIn, "adept:signInData", ar.toBase64()); + + // Generate Auth key and License Key + void* rsaAuth = client->generateRSAKey(RSA_KEY_SIZE_BITS); + void* rsaLicense = client->generateRSAKey(RSA_KEY_SIZE_BITS); - ar = ByteArray(encryptedSignInData, sizeof(encryptedSignInData)); - appendTextElem(signIn, "adept:signInData", ar.toBase64()); + std::string serializedData = serializeRSAPublicKey(rsaAuth); + appendTextElem(signIn, "adept:publicAuthKey", serializedData); + serializedData = serializeRSAPrivateKey(rsaAuth); + appendTextElem(signIn, "adept:encryptedPrivateAuthKey", serializedData.data()); - // Generate Auth key and License Key - void* rsaAuth = client->generateRSAKey(RSA_KEY_SIZE_BITS); - void* rsaLicense = client->generateRSAKey(RSA_KEY_SIZE_BITS); + serializedData = serializeRSAPublicKey(rsaLicense); + appendTextElem(signIn, "adept:publicLicenseKey", serializedData.data()); + serializedData = serializeRSAPrivateKey(rsaLicense); + appendTextElem(signIn, "adept:encryptedPrivateLicenseKey", serializedData.data()); - std::string serializedData = serializeRSAPublicKey(rsaAuth); - appendTextElem(signIn, "adept:publicAuthKey", serializedData); - serializedData = serializeRSAPrivateKey(rsaAuth); - appendTextElem(signIn, "adept:encryptedPrivateAuthKey", serializedData.data()); - - serializedData = serializeRSAPublicKey(rsaLicense); - appendTextElem(signIn, "adept:publicLicenseKey", serializedData.data()); - serializedData = serializeRSAPrivateKey(rsaLicense); - appendTextElem(signIn, "adept:encryptedPrivateLicenseKey", serializedData.data()); - - client->destroyRSAHandler(rsaAuth); - client->destroyRSAHandler(rsaLicense); + client->destroyRSAHandler(rsaAuth); + client->destroyRSAHandler(rsaLicense); } - + void DRMProcessor::signIn(const std::string& adobeID, const std::string& adobePassword) { - pugi::xml_document signInRequest; - std::string authenticationCertificate = user->getAuthenticationCertificate(); + pugi::xml_document signInRequest; + std::string authenticationCertificate = user->getAuthenticationCertificate(); + + buildSignInRequest(signInRequest, adobeID, adobePassword, authenticationCertificate); - buildSignInRequest(signInRequest, adobeID, adobePassword, authenticationCertificate); + GOUROU_LOG(INFO, "SignIn " << adobeID); + + std::string signInURL = user->getProperty("//adept:authURL"); + signInURL += "/SignInDirect"; - GOUROU_LOG(INFO, "SignIn " << adobeID); + ByteArray credentials = sendRequest(signInRequest, signInURL); + + pugi::xml_document credentialsDoc; + if (!credentialsDoc.load_buffer(credentials.data(), credentials.length())) + EXCEPTION(SIGN_INVALID_CREDENTIALS, "Invalid credentials reply"); - std::string signInURL = user->getProperty("//adept:authURL"); - signInURL += "/SignInDirect"; + struct adeptWalker: pugi::xml_tree_walker + { + void changeName(pugi::xml_node& node) + { + std::string name = std::string("adept:") + node.name(); + node.set_name(name.c_str()); + } + + bool begin(pugi::xml_node& node) + { + changeName(node); + return true; + } + + virtual bool for_each(pugi::xml_node& node) + { + if (node.type() == pugi::node_element) + changeName(node); + return true; // continue traversal + } + } adeptWalker; - ByteArray credentials = sendRequest(signInRequest, signInURL); + pugi::xml_node credentialsNode = credentialsDoc.first_child(); - pugi::xml_document credentialsDoc; - if (!credentialsDoc.load_buffer(credentials.data(), credentials.length())) - EXCEPTION(SIGN_INVALID_CREDENTIALS, "Invalid credentials reply"); + if (std::string(credentialsNode.name()) != "credentials") + EXCEPTION(SIGN_INVALID_CREDENTIALS, "Invalid credentials reply"); + + pugi::xpath_node encryptedPrivateLicenseKey = credentialsNode.select_node("encryptedPrivateLicenseKey"); + const char* privateKeyData = encryptedPrivateLicenseKey.node().first_child().value(); + ByteArray privateKeyDataStr = ByteArray::fromBase64(privateKeyData); + ByteArray privateKey = decryptWithDeviceKey(privateKeyDataStr.data(), privateKeyDataStr.length()); + credentialsNode.remove_child(encryptedPrivateLicenseKey.node()); + appendTextElem(credentialsNode, "privateLicenseKey", privateKey.toBase64().data()); - struct adeptWalker: pugi::xml_tree_walker - { - void changeName(pugi::xml_node& node) - { - std::string name = std::string("adept:") + node.name(); - node.set_name(name.c_str()); - } + // Add "adept:" prefix to all nodes + credentialsNode.remove_attribute("xmlns"); + credentialsNode.append_attribute("xmlns:adept") = ADOBE_ADEPT_NS; + credentialsNode.traverse(adeptWalker); + + appendTextElem(credentialsNode, "adept:authenticationCertificate", authenticationCertificate.data()); - bool begin(pugi::xml_node& node) - { - changeName(node); - return true; - } + pugi::xml_document activationDoc; + user->readActivation(activationDoc); + pugi::xml_node activationInfo = activationDoc.select_node("activationInfo").node(); + activationInfo.append_copy(credentialsNode); - virtual bool for_each(pugi::xml_node& node) - { - if (node.type() == pugi::node_element) - changeName(node); - return true; // continue traversal - } - } adeptWalker; - - pugi::xml_node credentialsNode = credentialsDoc.first_child(); - - if (std::string(credentialsNode.name()) != "credentials") - EXCEPTION(SIGN_INVALID_CREDENTIALS, "Invalid credentials reply"); - - pugi::xpath_node encryptedPrivateLicenseKey = credentialsNode.select_node("encryptedPrivateLicenseKey"); - const char* privateKeyData = encryptedPrivateLicenseKey.node().first_child().value(); - ByteArray privateKeyDataStr = ByteArray::fromBase64(privateKeyData); - ByteArray privateKey = decryptWithDeviceKey(privateKeyDataStr.data(), privateKeyDataStr.length()); - credentialsNode.remove_child(encryptedPrivateLicenseKey.node()); - appendTextElem(credentialsNode, "privateLicenseKey", privateKey.toBase64().data()); - - // Add "adept:" prefix to all nodes - credentialsNode.remove_attribute("xmlns"); - credentialsNode.append_attribute("xmlns:adept") = ADOBE_ADEPT_NS; - credentialsNode.traverse(adeptWalker); - - appendTextElem(credentialsNode, "adept:authenticationCertificate", authenticationCertificate.data()); - - pugi::xml_document activationDoc; - user->readActivation(activationDoc); - pugi::xml_node activationInfo = activationDoc.select_node("activationInfo").node(); - activationInfo.append_copy(credentialsNode); - - user->updateActivationFile(activationDoc); + user->updateActivationFile(activationDoc); } - + void DRMProcessor::buildActivateReq(pugi::xml_document& activateReq) { - pugi::xml_node decl = activateReq.append_child(pugi::node_declaration); - decl.append_attribute("version") = "1.0"; + pugi::xml_node decl = activateReq.append_child(pugi::node_declaration); + decl.append_attribute("version") = "1.0"; + + pugi::xml_node root = activateReq.append_child("adept:activate"); + root.append_attribute("xmlns:adept") = ADOBE_ADEPT_NS; + root.append_attribute("requestType") = "initial"; - pugi::xml_node root = activateReq.append_child("adept:activate"); - root.append_attribute("xmlns:adept") = ADOBE_ADEPT_NS; - root.append_attribute("requestType") = "initial"; + appendTextElem(root, "adept:fingerprint", (*device)["fingerprint"]); + appendTextElem(root, "adept:deviceType", (*device)["deviceType"]); + appendTextElem(root, "adept:clientOS", (*device)["clientOS"]); + appendTextElem(root, "adept:clientLocale", (*device)["clientLocale"]); + appendTextElem(root, "adept:clientVersion", (*device)["deviceClass"]); - appendTextElem(root, "adept:fingerprint", (*device)["fingerprint"]); - appendTextElem(root, "adept:deviceType", (*device)["deviceType"]); - appendTextElem(root, "adept:clientOS", (*device)["clientOS"]); - appendTextElem(root, "adept:clientLocale", (*device)["clientLocale"]); - appendTextElem(root, "adept:clientVersion", (*device)["deviceClass"]); + pugi::xml_node targetDevice = root.append_child("adept:targetDevice"); + appendTextElem(targetDevice, "adept:softwareVersion", (*device)["hobbes"]); + appendTextElem(targetDevice, "adept:clientOS", (*device)["clientOS"]); + appendTextElem(targetDevice, "adept:clientLocale", (*device)["clientLocale"]); + appendTextElem(targetDevice, "adept:clientVersion", (*device)["deviceClass"]); + appendTextElem(targetDevice, "adept:deviceType", (*device)["deviceType"]); + appendTextElem(targetDevice, "adept:fingerprint", (*device)["fingerprint"]); - pugi::xml_node targetDevice = root.append_child("adept:targetDevice"); - appendTextElem(targetDevice, "adept:softwareVersion", (*device)["hobbes"]); - appendTextElem(targetDevice, "adept:clientOS", (*device)["clientOS"]); - appendTextElem(targetDevice, "adept:clientLocale", (*device)["clientLocale"]); - appendTextElem(targetDevice, "adept:clientVersion", (*device)["deviceClass"]); - appendTextElem(targetDevice, "adept:deviceType", (*device)["deviceType"]); - appendTextElem(targetDevice, "adept:fingerprint", (*device)["fingerprint"]); - - addNonce(root); - - appendTextElem(root, "adept:user", user->getUUID()); + addNonce(root); + + appendTextElem(root, "adept:user", user->getUUID()); } - + void DRMProcessor::activateDevice() { - pugi::xml_document activateReq; + pugi::xml_document activateReq; - GOUROU_LOG(INFO, "Activate device"); + GOUROU_LOG(INFO, "Activate device"); - buildActivateReq(activateReq); + buildActivateReq(activateReq); - pugi::xml_node root = activateReq.select_node("adept:activate").node(); + pugi::xml_node root = activateReq.select_node("adept:activate").node(); - signNode(root); + std::string signature = signNode(root); - pugi::xml_document activationDoc; - user->readActivation(activationDoc); + root = activateReq.select_node("adept:activate").node(); + appendTextElem(root, "adept:signature", signature); - std::string activationURL = user->getProperty("//adept:activationURL"); - activationURL += "/Activate"; + pugi::xml_document activationDoc; + user->readActivation(activationDoc); - ByteArray reply = sendRequest(activateReq, activationURL); + std::string activationURL = user->getProperty("//adept:activationURL"); + activationURL += "/Activate"; + + ByteArray reply = sendRequest(activateReq, activationURL); - pugi::xml_document activationToken; - activationToken.load_buffer(reply.data(), reply.length()); - - root = activationDoc.select_node("activationInfo").node(); - root.append_copy(activationToken.first_child()); - user->updateActivationFile(activationDoc); + pugi::xml_document activationToken; + activationToken.load_buffer(reply.data(), reply.length()); + + root = activationDoc.select_node("activationInfo").node(); + root.append_copy(activationToken.first_child()); + user->updateActivationFile(activationDoc); } - - void DRMProcessor::buildReturnReq(pugi::xml_document& returnReq, const std::string& loanID, const std::string& operatorURL) - { - pugi::xml_node decl = returnReq.append_child(pugi::node_declaration); - decl.append_attribute("version") = "1.0"; - - pugi::xml_node root = returnReq.append_child("adept:loanReturn"); - root.append_attribute("xmlns:adept") = ADOBE_ADEPT_NS; - - appendTextElem(root, "adept:user", user->getUUID()); - appendTextElem(root, "adept:device", user->getDeviceUUID()); - appendTextElem(root, "adept:loan", loanID); - - addNonce(root); - signNode(root); - } - - 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; - - GOUROU_LOG(INFO, "Return loan " << loanID); - - buildReturnReq(returnReq, loanID, operatorURL); - - 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) { - const unsigned char* deviceKey = device->getDeviceKey(); - unsigned int outLen; - int remain = 0; - if ((len % 16)) - remain = 16 - (len%16); - int encrypted_data_len = 16 + len + remain; // IV + data + pad - unsigned char* encrypted_data = new unsigned char[encrypted_data_len]; + const unsigned char* deviceKey = device->getDeviceKey(); + unsigned int outLen; + int remain = 0; + if ((len % 16)) + remain = 16 - (len%16); + int encrypted_data_len = 16 + len + remain; // IV + data + pad + unsigned char* encrypted_data = new unsigned char[encrypted_data_len]; + + // Generate IV in front + client->randBytes(encrypted_data, 16); + + client->AESEncrypt(CryptoInterface::CHAIN_CBC, + deviceKey, 16, encrypted_data, 16, + data, len, + encrypted_data+16, &outLen); - // Generate IV in front - client->randBytes(encrypted_data, 16); + ByteArray res(encrypted_data, outLen+16); - client->encrypt(CryptoInterface::ALGO_AES, CryptoInterface::CHAIN_CBC, - deviceKey, 16, encrypted_data, 16, - data, len, - encrypted_data+16, &outLen); + delete[] encrypted_data; - ByteArray res(encrypted_data, outLen+16); - - delete[] encrypted_data; - - return res; + return res; } /* First 16 bytes of data is IV for CBC chaining */ ByteArray DRMProcessor::decryptWithDeviceKey(const unsigned char* data, unsigned int len) { - unsigned int outLen; - const unsigned char* deviceKey = device->getDeviceKey(); - unsigned char* decrypted_data = new unsigned char[len-16]; + unsigned int outLen; + const unsigned char* deviceKey = device->getDeviceKey(); + unsigned char* decrypted_data = new unsigned char[len-16]; - client->decrypt(CryptoInterface::ALGO_AES, CryptoInterface::CHAIN_CBC, - deviceKey, 16, data, 16, - data+16, len-16, - decrypted_data, &outLen); + client->AESDecrypt(CryptoInterface::CHAIN_CBC, + deviceKey, 16, data, 16, + data+16, len-16, + decrypted_data, &outLen); - ByteArray res(decrypted_data, outLen); + ByteArray res(decrypted_data, outLen); - delete[] decrypted_data; + delete[] decrypted_data; - return res; + return res; } std::string DRMProcessor::serializeRSAPublicKey(void* rsa) { - unsigned char* data = 0; - unsigned int len; + unsigned char* data = 0; + unsigned int len; + + client->extractRSAPublicKey(rsa, &data, &len); - client->extractRSAPublicKey(rsa, &data, &len); + ByteArray res(data, len); - ByteArray res(data, len); - - free(data); - - return res.toBase64(); + free(data); + + return res.toBase64(); } - + std::string DRMProcessor::serializeRSAPrivateKey(void* rsa) { - unsigned char* data = 0; - unsigned int len; + unsigned char* data = 0; + unsigned int len; + + client->extractRSAPrivateKey(rsa, &data, &len); - client->extractRSAPrivateKey(rsa, &data, &len); + ByteArray res = encryptWithDeviceKey(data, len); - ByteArray res = encryptWithDeviceKey(data, len); - - free(data); - - return res.toBase64(); - } - - void DRMProcessor::exportPrivateLicenseKey(std::string path) - { - int fd = open(path.c_str(), O_CREAT|O_TRUNC|O_WRONLY, S_IRWXU); - int ret; - - if (fd <= 0) - EXCEPTION(GOUROU_FILE_ERROR, "Unable to open " << path); - - ByteArray privateLicenseKey = ByteArray::fromBase64(user->getPrivateLicenseKey()); - /* In adobekey.py, we get base64 decoded data [26:] */ - ret = write(fd, privateLicenseKey.data()+26, privateLicenseKey.length()-26); - close(fd); - if (ret != (int)(privateLicenseKey.length()-26)) - { - EXCEPTION(gourou::GOUROU_FILE_ERROR, "Error writing " << path); - } + free(data); + + return res.toBase64(); } int DRMProcessor::getLogLevel() {return (int)gourou::logLevel;} void DRMProcessor::setLogLevel(int logLevel) {gourou::logLevel = (GOUROU_LOG_LEVEL)logLevel;} - - /** - * 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; idecrypt(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; iuser->getUUID() != user) - { - EXCEPTION(DRM_INVALID_USER, "This book has been downloaded for another user (" << user << ")"); - } - - std::string encryptedKey = extractTextElem(rightsDoc, "/adept:rights/licenseToken/encryptedKey"); - std::string keyType = extractTextAttribute(rightsDoc, "/adept:rights/licenseToken/encryptedKey", "keyType", false); - - if (keyType != "") - encryptedKey = encryptedKeyFirstPass(rightsDoc, encryptedKey, keyType); - - if (encryptedKey.size() != 172) - EXCEPTION(DRM_INVALID_KEY_SIZE, "Invalid encrypted key size (" << encryptedKey.size() << "). DRM version not supported"); - - ByteArray arrayEncryptedKey = ByteArray::fromBase64(encryptedKey); - - std::string privateKeyData = this->user->getPrivateLicenseKey(); - ByteArray privateRSAKey = ByteArray::fromBase64(privateKeyData); - - dumpBuffer(gourou::LG_LOG_DEBUG, "To decrypt : ", arrayEncryptedKey.data(), arrayEncryptedKey.length()); - - client->RSAPrivateDecrypt(privateRSAKey.data(), privateRSAKey.length(), - RSAInterface::RSA_KEY_PKCS8, "", - arrayEncryptedKey.data(), arrayEncryptedKey.length(), rsaKey); - - dumpBuffer(gourou::LG_LOG_DEBUG, "Decrypted : ", rsaKey, sizeof(rsaKey)); - - if (rsaKey[0] != 0x00 || rsaKey[1] != 0x02 || - rsaKey[RSA_KEY_SIZE-16-1] != 0x00) - EXCEPTION(DRM_ERR_ENCRYPTION_KEY, "Unable to retrieve encryption key"); - - memcpy(decryptedKey, &rsaKey[sizeof(rsaKey)-16], 16); - } - else - { - GOUROU_LOG(DEBUG, "Use provided encryption key"); - if (encryptionKeySize != 16) - EXCEPTION(DRM_ERR_ENCRYPTION_KEY, "Provided encryption key must be 16 bytes"); - - memcpy(decryptedKey, encryptionKey, encryptionKeySize); - } - } - - void DRMProcessor::removeEPubDRM(const std::string& filenameIn, const std::string& filenameOut, - const unsigned char* encryptionKey, unsigned encryptionKeySize) - { - ByteArray zipData; - bool removeEncryptionXML = true; - void* zipHandler = client->zipOpen(filenameOut); - - client->zipReadFile(zipHandler, "META-INF/rights.xml", zipData); - pugi::xml_document rightsDoc; - rightsDoc.load_string((const char*)zipData.data()); - - unsigned char decryptedKey[16]; - - decryptADEPTKey(rightsDoc, decryptedKey, encryptionKey, encryptionKeySize); - - client->zipReadFile(zipHandler, "META-INF/encryption.xml", zipData); - pugi::xml_document encryptionDoc; - encryptionDoc.load_string((const char*)zipData.data()); - - pugi::xpath_node_set nodeSet = encryptionDoc.select_nodes("//EncryptedData"); - - for (pugi::xpath_node_set::const_iterator it = nodeSet.begin(); - it != nodeSet.end(); ++it) - { - pugi::xml_node encryptionMethod = it->node().child("EncryptionMethod"); - pugi::xml_node cipherReference = it->node().child("CipherData").child("CipherReference"); - - std::string encryptionType = encryptionMethod.attribute("Algorithm").value(); - std::string encryptedFile = cipherReference.attribute("URI").value(); - - if (encryptionType == "") - { - EXCEPTION(DRM_MISSING_PARAMETER, "Missing Algorithm attribute in encryption.xml"); - } - else if (encryptionType == "http://www.w3.org/2001/04/xmlenc#aes128-cbc") - { - if (encryptedFile == "") - { - EXCEPTION(DRM_MISSING_PARAMETER, "Missing URI attribute in encryption.xml"); - } - - GOUROU_LOG(DEBUG, "Encrypted file " << encryptedFile); - - client->zipReadFile(zipHandler, encryptedFile, zipData, false); - - unsigned char* _data = zipData.data(); - ByteArray clearData(zipData.length()-16+1, true); /* Reserve 1 byte for 'Z' */ - unsigned char* _clearData = clearData.data(); - gourou::ByteArray inflateData(true); - unsigned int dataOutLength; - - client->decrypt(CryptoInterface::ALGO_AES, CryptoInterface::CHAIN_CBC, - decryptedKey, sizeof(decryptedKey), /* Key */ - _data, 16, /* IV */ - &_data[16], zipData.length()-16, - _clearData, &dataOutLength); - - // Add 'Z' at the end, done in ineptepub.py - _clearData[dataOutLength] = 'Z'; - clearData.resize(dataOutLength+1); - - try - { - client->inflate(clearData, inflateData); - client->zipWriteFile(zipHandler, encryptedFile, inflateData); - } - catch(gourou::Exception& e) - { - if (e.getErrorCode() == CLIENT_ZIP_ERROR) - { - GOUROU_LOG(ERROR, e.what() << std::endl << "Skip file " << encryptedFile); - } - else - throw e; - } - - it->node().parent().remove_child(it->node()); - } - else - { - GOUROU_LOG(WARN, "Unsupported encryption algorithm " << encryptionType << ", for file " << encryptedFile); - removeEncryptionXML = false; - } - } - - client->zipDeleteFile(zipHandler, "META-INF/rights.xml"); - if (removeEncryptionXML) - client->zipDeleteFile(zipHandler, "META-INF/encryption.xml"); - else - { - StringXMLWriter xmlWriter; - encryptionDoc.save(xmlWriter, " "); - std::string xmlStr = xmlWriter.getResult(); - ByteArray ba(xmlStr); - client->zipWriteFile(zipHandler, "META-INF/encryption.xml", ba); - } - - client->zipClose(zipHandler); - } - - void DRMProcessor::generatePDFObjectKey(int version, - const unsigned char* masterKey, unsigned int masterKeyLength, - int objectId, int objectGenerationNumber, - unsigned char* keyOut) - { - switch(version) - { - case 4: - ByteArray toHash(masterKey, masterKeyLength); - uint32_t _objectId = objectId; - uint32_t _objectGenerationNumber = objectGenerationNumber; - toHash.append((const unsigned char*)&_objectId, 3); // Fill 3 bytes - toHash.append((const unsigned char*)&_objectGenerationNumber, 2); // Fill 2 bytes - - client->digest("md5", toHash.data(), toHash.length(), keyOut); - break; - } - } - - void DRMProcessor::removePDFDRM(const std::string& filenameIn, const std::string& filenameOut, - const unsigned char* encryptionKey, unsigned encryptionKeySize) - { - uPDFParser::Parser parser; - bool EBXHandlerFound = false; - - if (filenameIn == filenameOut) - { - EXCEPTION(DRM_IN_OUT_EQUALS, "PDF IN must be different of PDF OUT"); - } - - try - { - GOUROU_LOG(DEBUG, "Parse PDF"); - parser.parse(filenameIn); - } - catch(std::invalid_argument& e) - { - GOUROU_LOG(ERROR, "Invalid PDF"); - return; - } - - uPDFParser::Integer* ebxVersion; - std::vector objects = parser.objects(); - std::vector::iterator it, ebxIt; - std::vector::reverse_iterator rIt; - std::vector ebxObjects; - unsigned char decryptedKey[16]; - int ebxId; - - for(rIt = objects.rbegin(); rIt != objects.rend(); rIt++) - { - // Update EBX_HANDLER with rights - if ((*rIt)->hasKey("Filter") && (**rIt)["Filter"]->str() == "/EBX_HANDLER") - { - EBXHandlerFound = true; - uPDFParser::Object* ebx = *rIt; - - ebxVersion = (uPDFParser::Integer*)(*ebx)["V"]; - if (ebxVersion->value() != 4) - { - EXCEPTION(DRM_VERSION_NOT_SUPPORTED, "EBX encryption version not supported " << ebxVersion->value()); - } - - if (!(ebx->hasKey("ADEPT_LICENSE"))) - { - EXCEPTION(DRM_ERR_ENCRYPTION_KEY, "No ADEPT_LICENSE found"); - } - - uPDFParser::String* licenseObject = (uPDFParser::String*)(*ebx)["ADEPT_LICENSE"]; - - std::string value = licenseObject->value(); - // Pad with '=' - while ((value.size() % 4)) - value += "="; - ByteArray zippedData = ByteArray::fromBase64(value); - - if (zippedData.size() == 0) - EXCEPTION(DRM_ERR_ENCRYPTION_KEY, "Invalid ADEPT_LICENSE"); - - ByteArray rightsStr; - client->inflate(zippedData, rightsStr); - - pugi::xml_document rightsDoc; - rightsDoc.load_string((const char*)rightsStr.data()); - - decryptADEPTKey(rightsDoc, decryptedKey, encryptionKey, encryptionKeySize); - - ebxId = ebx->objectId(); - - break; - } - } - - if (!EBXHandlerFound) - { - EXCEPTION(DRM_ERR_ENCRYPTION_KEY, "EBX_HANDLER not found"); - } - - for(it = objects.begin(); it != objects.end(); it++) - { - uPDFParser::Object* object = *it; - - if (object->objectId() == ebxId) - { - ebxObjects.push_back(object); - continue; - } - - // Should not decrypt XRef stream - if (object->hasKey("Type") && (*object)["Type"]->str() == "/XRef") - { - GOUROU_LOG(DEBUG, "XRef stream at " << object->offset()); - continue; - } - - GOUROU_LOG(DEBUG, "Obj " << object->objectId()); - - unsigned char tmpKey[sizeof(decryptedKey)]; - - generatePDFObjectKey(ebxVersion->value(), - decryptedKey, sizeof(decryptedKey), - object->objectId(), object->generationNumber(), - tmpKey); - - uPDFParser::Dictionary& dictionary = object->dictionary(); - std::map& dictValues = dictionary.value(); - std::map::iterator dictIt; - std::map decodedStrings; - std::string string; - - /* Parse dictionary */ - for (dictIt = dictValues.begin(); dictIt != dictValues.end(); dictIt++) - { - uPDFParser::DataType* dictData = dictIt->second; - if (dictData->type() == uPDFParser::DataType::STRING) - { - string = ((uPDFParser::String*) dictData)->unescapedValue(); - - unsigned char* encryptedData = (unsigned char*)string.c_str(); - unsigned int dataLength = string.size(); - unsigned char* clearData = new unsigned char[dataLength]; - unsigned int dataOutLength; - - GOUROU_LOG(DEBUG, "Decrypt string " << dictIt->first << " " << dataLength); - - client->decrypt(CryptoInterface::ALGO_RC4, CryptoInterface::CHAIN_ECB, - tmpKey, sizeof(tmpKey), /* Key */ - NULL, 0, /* IV */ - encryptedData, dataLength, - clearData, &dataOutLength); - - decodedStrings[dictIt->first] = new uPDFParser::String( - std::string((const char*)clearData, dataOutLength)); - - 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++) - dictionary.replace(dictIt->first, dictIt->second); - - std::vector::iterator datasIt; - std::vector& datas = object->data(); - uPDFParser::Stream* stream; - - for (datasIt = datas.begin(); datasIt != datas.end(); datasIt++) - { - if ((*datasIt)->type() != uPDFParser::DataType::STREAM) - continue; - - stream = (uPDFParser::Stream*) (*datasIt); - unsigned char* encryptedData = stream->data(); - unsigned int dataLength = stream->dataLength(); - unsigned char* clearData = new unsigned char[dataLength]; - unsigned int dataOutLength; - - GOUROU_LOG(DEBUG, "Decrypt stream id " << object->objectId() << ", size " << stream->dataLength()); - - client->decrypt(CryptoInterface::ALGO_RC4, CryptoInterface::CHAIN_ECB, - tmpKey, sizeof(tmpKey), /* Key */ - NULL, 0, /* IV */ - encryptedData, dataLength, - clearData, &dataOutLength); - - stream->setData(clearData, dataOutLength, true); - if (dataOutLength != dataLength) - GOUROU_LOG(DEBUG, "New size " << dataOutLength); - } - } - - /* Delete objects that reference EBX objects, except in trailer */ - for(it = objects.begin(); it != objects.end(); it++) - { - uPDFParser::Object* object = *it; - - if (object->hasKey("Encrypt") && (*object)["Encrypt"]->type() == uPDFParser::DataType::REFERENCE) - { - uPDFParser::Reference* encrypt = (uPDFParser::Reference*)(*object)["Encrypt"]; - - /* Delete EBX objects */ - for(ebxIt = ebxObjects.begin(); ebxIt != ebxObjects.end(); ebxIt++) - { - if (encrypt->value() == (*ebxIt)->objectId()) - { - GOUROU_LOG(ERROR, "Delete stream id " << object->objectId()); - - parser.removeObject(object); - break; - } - } - } - } - - /* Delete EBX objects */ - for(it = ebxObjects.begin(); it != ebxObjects.end(); it++) - parser.removeObject(*it); - - uPDFParser::Object& trailer = parser.getTrailer(); - trailer.deleteKey("Encrypt"); - - parser.write(filenameOut); - } - - void DRMProcessor::removeDRM(const std::string& filenameIn, const std::string& filenameOut, - ITEM_TYPE type, const unsigned char* encryptionKey, unsigned encryptionKeySize) - { - if (type == PDF) - removePDFDRM(filenameIn, filenameOut, encryptionKey, encryptionKeySize); - else - removeEPubDRM(filenameIn, filenameOut, encryptionKey, encryptionKeySize); - } } diff --git a/src/loan_token.cpp b/src/loan_token.cpp deleted file mode 100644 index fe320aa..0000000 --- a/src/loan_token.cpp +++ /dev/null @@ -1,80 +0,0 @@ -/* - Copyright 2022 Grégory Soutadé - - This file is part of libgourou. - - libgourou is free software: you can redistribute it and/or modify - it under the terms of the GNU Lesser General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - libgourou is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public License - along with libgourou. If not, see . -*/ - -#include "libgourou_common.h" -#include "loan_token.h" - -namespace gourou -{ - LoanToken::LoanToken(pugi::xml_document& doc) - { - pugi::xml_node node = doc.select_node("/envelope/loanToken").node(); - - if (!node) - EXCEPTION(FFI_INVALID_LOAN_TOKEN, "No loanToken element in document"); - - node = doc.select_node("/envelope/fulfillmentResult/fulfillment").node(); - - if (node) - properties["id"] = node.first_child().value(); - else - { - EXCEPTION(FFI_INVALID_LOAN_TOKEN, "No fulfillment element in document"); - } - - node = doc.select_node("/envelope/loanToken/operatorURL").node(); - - if (!node) - EXCEPTION(FFI_INVALID_LOAN_TOKEN, "No loanToken/operatorURL element in document"); - - properties["operatorURL"] = node.first_child().value(); - - node = doc.select_node("/envelope/fulfillmentResult/resourceItemInfo/licenseToken/permissions/display/until").node(); - - if (node) - properties["validity"] = node.first_child().value(); - else - { - node = doc.select_node("/envelope/fulfillmentResult/resourceItemInfo/licenseToken/permissions/play/until").node(); - - if (node) - properties["validity"] = node.first_child().value(); - else - EXCEPTION(FFI_INVALID_LOAN_TOKEN, "No loanToken/operatorURL element in document"); - } - } - - std::string LoanToken::getProperty(const std::string& property, const std::string& _default) - { - if (properties.find(property) == properties.end()) - { - if (_default == "") - EXCEPTION(GOUROU_INVALID_PROPERTY, "Invalid property " << property); - - return _default; - } - - return properties[property]; - } - - std::string LoanToken::operator[](const std::string& property) - { - return getProperty(property); - } -} diff --git a/src/pugixml.cpp b/src/pugixml.cpp new file mode 120000 index 0000000..5049a58 --- /dev/null +++ b/src/pugixml.cpp @@ -0,0 +1 @@ +../lib/pugixml/src/pugixml.cpp \ No newline at end of file diff --git a/src/user.cpp b/src/user.cpp index 14251b0..b90e3e8 100644 --- a/src/user.cpp +++ b/src/user.cpp @@ -24,60 +24,48 @@ namespace gourou { User::User(DRMProcessor* processor):processor(processor) {} - + User::User(DRMProcessor* processor, const std::string& activationFile): - processor(processor), activationFile(activationFile) + processor(processor), activationFile(activationFile) { - parseActivationFile(); + parseActivationFile(); } void User::parseActivationFile(bool throwOnNull) { - GOUROU_LOG(DEBUG, "Parse activation file " << activationFile); + GOUROU_LOG(DEBUG, "Parse activation file " << activationFile); + + if (!activationDoc.load_file(activationFile.c_str())) + { + if (throwOnNull) + EXCEPTION(USER_INVALID_ACTIVATION_FILE, "Invalid activation file"); + return; + } - if (!activationDoc.load_file(activationFile.c_str())) - { - if (throwOnNull) - EXCEPTION(USER_INVALID_ACTIVATION_FILE, "Invalid activation file"); - return; - } + try + { + pkcs12 = gourou::extractTextElem(activationDoc, "//adept:pkcs12", throwOnNull); + uuid = gourou::extractTextElem(activationDoc, "//adept:user", throwOnNull); + deviceUUID = gourou::extractTextElem(activationDoc, "//device", throwOnNull); + deviceFingerprint = gourou::extractTextElem(activationDoc, "//fingerprint", throwOnNull); + certificate = gourou::extractTextElem(activationDoc, "//adept:certificate", throwOnNull); + authenticationCertificate = gourou::extractTextElem(activationDoc, "//adept:authenticationCertificate", throwOnNull); + privateLicenseKey = gourou::extractTextElem(activationDoc, "//adept:privateLicenseKey", throwOnNull); + username = gourou::extractTextElem(activationDoc, "//adept:username", throwOnNull); - try - { - pkcs12 = gourou::extractTextElem(activationDoc, "//adept:pkcs12", throwOnNull); - uuid = gourou::extractTextElem(activationDoc, "//adept:user", throwOnNull); - deviceUUID = gourou::extractTextElem(activationDoc, "//device", throwOnNull); - deviceFingerprint = gourou::extractTextElem(activationDoc, "//fingerprint", throwOnNull); - authenticationCertificate = gourou::extractTextElem(activationDoc, "//adept:authenticationCertificate", throwOnNull); - privateLicenseKey = gourou::extractTextElem(activationDoc, "//adept:privateLicenseKey", throwOnNull); - - pugi::xpath_node xpath_node = activationDoc.select_node("//adept:username"); - if (xpath_node) - loginMethod = xpath_node.node().attribute("method").value(); - else - { - if (throwOnNull) - EXCEPTION(USER_INVALID_ACTIVATION_FILE, "Invalid activation file"); - } - - if (loginMethod == "anonymous") - username = "anonymous"; - else - username = gourou::extractTextElem(activationDoc, "//adept:username", throwOnNull); - - pugi::xpath_node_set nodeSet = activationDoc.select_nodes("//adept:licenseServices/adept:licenseServiceInfo"); - for (pugi::xpath_node_set::const_iterator it = nodeSet.begin(); - it != nodeSet.end(); ++it) - { - std::string url = gourou::extractTextElem(it->node(), "adept:licenseURL"); - std::string certificate = gourou::extractTextElem(it->node(), "adept:certificate"); - licenseServiceCertificates[url] = certificate; - } - } - catch(gourou::Exception& e) - { - EXCEPTION(USER_INVALID_ACTIVATION_FILE, "Invalid activation file"); - } + pugi::xpath_node xpath_node = activationDoc.select_node("//adept:username"); + if (xpath_node) + loginMethod = xpath_node.node().attribute("method").value(); + else + { + if (throwOnNull) + EXCEPTION(USER_INVALID_ACTIVATION_FILE, "Invalid activation file"); + } + } + catch(gourou::Exception& e) + { + EXCEPTION(USER_INVALID_ACTIVATION_FILE, "Invalid activation file"); + } } std::string& User::getUUID() { return uuid; } @@ -86,138 +74,130 @@ namespace gourou { std::string& User::getDeviceFingerprint() { return deviceFingerprint; } std::string& User::getUsername() { return username; } std::string& User::getLoginMethod() { return loginMethod; } + std::string& User::getCertificate() { return certificate; } std::string& User::getAuthenticationCertificate() { return authenticationCertificate; } std::string& User::getPrivateLicenseKey() { return privateLicenseKey; } - + void User::readActivation(pugi::xml_document& doc) { - if (!doc.load_file(activationFile.c_str())) - EXCEPTION(USER_INVALID_ACTIVATION_FILE, "Invalid activation file"); + if (!doc.load_file(activationFile.c_str())) + EXCEPTION(USER_INVALID_ACTIVATION_FILE, "Invalid activation file"); } void User::updateActivationFile(const char* data) { - GOUROU_LOG(INFO, "Update Activation file : " << std::endl << data); + GOUROU_LOG(INFO, "Update Activation file : " << std::endl << data); - writeFile(activationFile, (unsigned char*)data, strlen(data)); - - parseActivationFile(false); + writeFile(activationFile, (unsigned char*)data, strlen(data)); + + parseActivationFile(false); } - + void User::updateActivationFile(const pugi::xml_document& doc) { - StringXMLWriter xmlWriter; - doc.save(xmlWriter, " "); - updateActivationFile(xmlWriter.getResult().c_str()); + StringXMLWriter xmlWriter; + doc.save(xmlWriter, " "); + updateActivationFile(xmlWriter.getResult().c_str()); } std::string User::getProperty(const std::string property) { - pugi::xpath_node xpathRes = activationDoc.select_node(property.c_str()); - if (!xpathRes) - EXCEPTION(USER_NO_PROPERTY, "Property " << property << " not found in activation.xml"); + pugi::xpath_node xpathRes = activationDoc.select_node(property.c_str()); + if (!xpathRes) + EXCEPTION(USER_NO_PROPERTY, "Property " << property << " not found in activation.xml"); - std::string res = xpathRes.node().first_child().value(); - return trim(res); + std::string res = xpathRes.node().first_child().value(); + return trim(res); } - + pugi::xpath_node_set User::getProperties(const std::string property) { - return activationDoc.select_nodes(property.c_str()); + return activationDoc.select_nodes(property.c_str()); } User* User::createUser(DRMProcessor* processor, const std::string& dirName, const std::string& ACSServer) { - struct stat _stat; + struct stat _stat; - if (stat(dirName.c_str(), &_stat) != 0) - { - if (mkdir_p(dirName.c_str(), S_IRWXU)) - EXCEPTION(USER_MKPATH, "Unable to create " << dirName) - } + if (stat(dirName.c_str(), &_stat) != 0) + { + if (mkdir_p(dirName.c_str(), S_IRWXU)) + EXCEPTION(USER_MKPATH, "Unable to create " << dirName) + } - User* user = new User(processor); - bool doUpdate = false; + User* user = new User(processor); + bool doUpdate = false; + + user->activationFile = dirName + "/activation.xml"; + user->parseActivationFile(false); - user->activationFile = dirName + "/activation.xml"; - user->parseActivationFile(false); + pugi::xpath_node nodeActivationInfo = user->activationDoc.select_node("activation_info"); + pugi::xpath_node nodeActivationServiceInfo = nodeActivationInfo.node().select_node("adept:activationServiceInfo"); + pugi::xml_node activationInfo; + pugi::xml_node activationServiceInfo; + + if (nodeActivationInfo && nodeActivationServiceInfo) + { + GOUROU_LOG(DEBUG, "Read previous activation configuration"); + activationInfo = nodeActivationInfo.node(); + activationServiceInfo = nodeActivationServiceInfo.node(); + } + else + { + GOUROU_LOG(DEBUG, "Create new activation"); - pugi::xpath_node nodeActivationInfo = user->activationDoc.select_node("activation_info"); - pugi::xpath_node nodeActivationServiceInfo = nodeActivationInfo.node().select_node("adept:activationServiceInfo"); - pugi::xml_node activationInfo; - pugi::xml_node activationServiceInfo; + user->activationDoc.reset(); + + pugi::xml_node decl = user->activationDoc.append_child(pugi::node_declaration); + decl.append_attribute("version") = "1.0"; + activationInfo = user->activationDoc.append_child("activationInfo"); + activationInfo.append_attribute("xmlns") = ADOBE_ADEPT_NS; + activationServiceInfo = activationInfo.append_child("adept:activationServiceInfo"); + activationServiceInfo.append_attribute("xmlns:adept") = ADOBE_ADEPT_NS; + + // Go to activation Service Info + std::string activationURL = ACSServer + "/ActivationServiceInfo"; + ByteArray activationServiceInfoReply = processor->sendRequest(activationURL); + pugi::xml_document docActivationServiceInfo; + docActivationServiceInfo.load_buffer(activationServiceInfoReply.data(), + activationServiceInfoReply.length()); - if (nodeActivationInfo && nodeActivationServiceInfo) - { - GOUROU_LOG(DEBUG, "Read previous activation configuration"); - activationInfo = nodeActivationInfo.node(); - activationServiceInfo = nodeActivationServiceInfo.node(); - } - else - { - GOUROU_LOG(DEBUG, "Create new activation"); + pugi::xpath_node path = docActivationServiceInfo.select_node("//authURL"); + appendTextElem(activationServiceInfo, "adept:authURL", path.node().first_child().value()); + path = docActivationServiceInfo.select_node("//userInfoURL"); + appendTextElem(activationServiceInfo, "adept:userInfoURL", path.node().first_child().value()); + appendTextElem(activationServiceInfo, "adept:activationURL", ACSServer); + path = docActivationServiceInfo.select_node("//certificate"); + appendTextElem(activationServiceInfo, "adept:certificate", path.node().first_child().value()); + doUpdate = true; + } + + pugi::xpath_node nodeAuthenticationCertificate = activationServiceInfo.select_node("adept:authenticationCertificate"); - user->activationDoc.reset(); + if (!nodeAuthenticationCertificate) + { + GOUROU_LOG(DEBUG, "Create new activation, authentication part"); - pugi::xml_node decl = user->activationDoc.append_child(pugi::node_declaration); - decl.append_attribute("version") = "1.0"; - activationInfo = user->activationDoc.append_child("activationInfo"); - activationInfo.append_attribute("xmlns") = ADOBE_ADEPT_NS; - activationServiceInfo = activationInfo.append_child("adept:activationServiceInfo"); - activationServiceInfo.append_attribute("xmlns:adept") = ADOBE_ADEPT_NS; + pugi::xpath_node xpathRes = activationServiceInfo.select_node("adept:authURL"); + if (!xpathRes) + EXCEPTION(USER_NO_AUTHENTICATION_URL, "No authentication URL"); + + std::string authenticationURL = xpathRes.node().first_child().value(); + authenticationURL = trim(authenticationURL) + "/AuthenticationServiceInfo"; + + // Go to authentication Service Info + ByteArray authenticationServiceInfo = processor->sendRequest(authenticationURL); + pugi::xml_document docAuthenticationServiceInfo; + docAuthenticationServiceInfo.load_buffer(authenticationServiceInfo.data(), authenticationServiceInfo.length()); + pugi::xpath_node path = docAuthenticationServiceInfo.select_node("//certificate"); + appendTextElem(activationServiceInfo, "adept:authenticationCertificate", path.node().first_child().value()); + doUpdate = true; + } - // Go to activation Service Info - std::string activationURL = ACSServer + "/ActivationServiceInfo"; - ByteArray activationServiceInfoReply = processor->sendRequest(activationURL); - pugi::xml_document docActivationServiceInfo; - docActivationServiceInfo.load_buffer(activationServiceInfoReply.data(), - activationServiceInfoReply.length()); - - pugi::xpath_node path = docActivationServiceInfo.select_node("//authURL"); - appendTextElem(activationServiceInfo, "adept:authURL", path.node().first_child().value()); - path = docActivationServiceInfo.select_node("//userInfoURL"); - appendTextElem(activationServiceInfo, "adept:userInfoURL", path.node().first_child().value()); - appendTextElem(activationServiceInfo, "adept:activationURL", ACSServer); - path = docActivationServiceInfo.select_node("//certificate"); - appendTextElem(activationServiceInfo, "adept:certificate", path.node().first_child().value()); - doUpdate = true; - } - - pugi::xpath_node nodeAuthenticationCertificate = activationServiceInfo.select_node("adept:authenticationCertificate"); - - if (!nodeAuthenticationCertificate) - { - GOUROU_LOG(DEBUG, "Create new activation, authentication part"); - - pugi::xpath_node xpathRes = activationServiceInfo.select_node("adept:authURL"); - if (!xpathRes) - EXCEPTION(USER_NO_AUTHENTICATION_URL, "No authentication URL"); - - std::string authenticationURL = xpathRes.node().first_child().value(); - authenticationURL = trim(authenticationURL) + "/AuthenticationServiceInfo"; - - // Go to authentication Service Info - ByteArray authenticationServiceInfo = processor->sendRequest(authenticationURL); - pugi::xml_document docAuthenticationServiceInfo; - docAuthenticationServiceInfo.load_buffer(authenticationServiceInfo.data(), authenticationServiceInfo.length()); - pugi::xpath_node path = docAuthenticationServiceInfo.select_node("//certificate"); - appendTextElem(activationServiceInfo, "adept:authenticationCertificate", path.node().first_child().value()); - doUpdate = true; - } - - if (doUpdate) - user->updateActivationFile(user->activationDoc); - - - return user; + if (doUpdate) + user->updateActivationFile(user->activationDoc); + + + return user; } - - std::string User::getLicenseServiceCertificate(std::string url) - { - if (licenseServiceCertificates.count(trim(url))) - return licenseServiceCertificates[trim(url)]; - - return ""; - } - } diff --git a/utils/Makefile b/utils/Makefile index 2b4bfda..701d275 100644 --- a/utils/Makefile +++ b/utils/Makefile @@ -1,57 +1,28 @@ -BINDIR ?= /bin -MANDIR ?= /share/man -TARGET_BINARIES=acsmdownloader adept_activate adept_remove adept_loan_mgt -TARGETS=$(TARGET_BINARIES) launcher - -MAN_PAGES=acsmdownloader adept_activate adept_remove adept_loan_mgt - -CXXFLAGS=-Wall -fPIC -I$(ROOT)/include -fmacro-prefix-map=$(ROOT)/= -fdata-sections -ffunction-sections - -STATIC_DEP= -# LDFLAGS += -Wl,--gc-sections -LDFLAGS += -L$(ROOT) -lcrypto -lzip -lz -lcurl -lpugixml +TARGETS=acsmdownloader adept_activate +CXXFLAGS=-Wall `pkg-config --cflags Qt5Core Qt5Network` -fPIC -I$(ROOT)/include -I$(ROOT)/lib/pugixml/src/ ifneq ($(STATIC_UTILS),) -STATIC_DEP = $(ROOT)/libgourou.a +LDFLAGS=`pkg-config --libs Qt5Core Qt5Network` -L$(ROOT) $(ROOT)/libgourou.a -lcrypto -lzip else -LDFLAGS += -lgourou +LDFLAGS=`pkg-config --libs Qt5Core Qt5Network` -L$(ROOT) -lgourou -lcrypto -lzip endif ifneq ($(DEBUG),) -CXXFLAGS += -ggdb -O0 -DDEBUG +CXXFLAGS += -ggdb -O0 else CXXFLAGS += -O2 endif - -COMMON_DEPS = drmprocessorclientimpl.cpp utils_common.cpp -COMMON_OBJECTS = $(COMMON_DEPS:.cpp=.o) -COMMON_LIB = utils.a - all: $(TARGETS) -${COMMON_LIB}: $(COMMON_DEPS) - $(CXX) $(CXXFLAGS) $(COMMON_DEPS) -c - $(AR) crs $@ $(COMMON_OBJECTS) +acsmdownloader: drmprocessorclientimpl.cpp acsmdownloader.cpp + $(CXX) $(CXXFLAGS) $^ $(LDFLAGS) -o $@ -%: %.cpp $(COMMON_LIB) $(STATIC_DEP) - $(CXX) $(CXXFLAGS) $^ $(STATIC_DEP) $(LDFLAGS) -o $@ - -install: $(TARGET_BINARIES) - install -d $(DESTDIR)$(PREFIX)/$(BINDIR) - install -m 755 $(TARGET_BINARIES) $(DESTDIR)$(PREFIX)/$(BINDIR) - install -d $(DESTDIR)$(PREFIX)/$(MANDIR)/man1 - install -m 644 man/*.1 $(DESTDIR)$(PREFIX)/$(MANDIR)/man1 - -uninstall: - cd $(DESTDIR)$(PREFIX)/$(BINDIR) - rm -f $(TARGET_BINARIES) - cd - - cd $(DESTDIR)$(PREFIX)/$(MANDIR)/man1 - rm -f $(addsuffix .1,$(TARGET_BINARIES) +adept_activate: drmprocessorclientimpl.cpp adept_activate.cpp + $(CXX) $(CXXFLAGS) $^ $(LDFLAGS) -o $@ clean: - rm -f $(TARGETS) $(COMMON_LIB) + rm -f $(TARGETS) ultraclean: clean diff --git a/utils/acsmdownloader.cpp b/utils/acsmdownloader.cpp index cc9a5d9..af0a566 100644 --- a/utils/acsmdownloader.cpp +++ b/utils/acsmdownloader.cpp @@ -4,16 +4,16 @@ All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - + * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. + notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. * Neither the name of the copyright holder nor the - names of its contributors may be used to endorse or promote products - derived from this software without specific prior written permission. - + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE @@ -26,184 +26,133 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ +#include #include -#include #include -#include + +#include +#include +#include +#include +#include #include -#include - #include "drmprocessorclientimpl.h" -#include "utils_common.h" + +#define ARRAY_SIZE(arr) (sizeof(arr)/sizeof(arr[0])) static const char* deviceFile = "device.xml"; static const char* activationFile = "activation.xml"; static const char* devicekeyFile = "devicesalt"; static const char* acsmFile = 0; -static bool exportPrivateKey = false; static const char* outputFile = 0; static const char* outputDir = 0; -static bool resume = false; -static bool notify = true; +static const char* defaultDirs[] = { + ".adept/", + "./adobe-digital-editions/", + "./.adobe-digital-editions/" +}; -class ACSMDownloader +class ACSMDownloader: public QRunnable { public: - - int run() + ACSMDownloader(QCoreApplication* app): + app(app) { - int ret = 0; - try - { - gourou::DRMProcessor processor(&client, deviceFile, activationFile, devicekeyFile); - gourou::User* user = processor.getUser(); - - if (exportPrivateKey) - { - std::string filename; - if (outputFile) - filename = outputFile; - else - { - filename = std::string("Adobe_PrivateLicenseKey--") + user->getUsername() + ".der"; - - if (outputDir) - filename = std::string(outputDir) + "/" + filename; - } - - createPath(filename.c_str()); - - processor.exportPrivateLicenseKey(filename); - - std::cout << "Private license key exported to " << filename << std::endl; - } - else - { - gourou::FulfillmentItem* item = processor.fulfill(acsmFile, notify); - - std::string filename; - if (outputFile) - filename = outputFile; - else - { - filename = item->getMetadata("title"); - if (filename == "") - filename = "output"; - else - { - // Remove invalid characters - std::replace(filename.begin(), filename.end(), '/', '_'); - } - - if (outputDir) - filename = std::string(outputDir) + "/" + filename; - } - - createPath(filename.c_str()); - - gourou::DRMProcessor::ITEM_TYPE type = processor.download(item, filename, resume); - - if (!outputFile) - { - std::string finalName = filename; - if (type == gourou::DRMProcessor::ITEM_TYPE::PDF) - finalName += ".pdf"; - else - finalName += ".epub"; - rename(filename.c_str(), finalName.c_str()); - filename = finalName; - } - std::cout << "Created " << filename << std::endl; - - serializeLoanToken(item); - } - } catch(std::exception& e) - { - std::cout << e.what() << std::endl; - ret = 1; - } - - return ret; + setAutoDelete(false); } - - void serializeLoanToken(gourou::FulfillmentItem* item) + + void run() { - gourou::LoanToken* token = item->getLoanToken(); + int ret = 0; + try + { + DRMProcessorClientImpl client; + gourou::DRMProcessor processor(&client, deviceFile, activationFile, devicekeyFile); - // No loan token available - if (!token) - return; + gourou::FulfillmentItem* item = processor.fulfill(acsmFile); - pugi::xml_document doc; + std::string filename; + if (!outputFile) + { + filename = item->getMetadata("title"); + if (filename == "") + filename = "output.epub"; + else + filename += ".epub"; + } + else + filename = outputFile; + + if (outputDir) + { + QDir dir(outputDir); + if (!dir.exists(outputDir)) + dir.mkpath(outputDir); - pugi::xml_node decl = doc.append_child(pugi::node_declaration); - decl.append_attribute("version") = "1.0"; + filename = std::string(outputDir) + "/" + filename; + } + + processor.download(item, filename); + std::cout << "Created " << filename << std::endl; + } catch(std::exception& e) + { + std::cout << e.what() << std::endl; + ret = 1; + } - pugi::xml_node root = doc.append_child("loanToken"); - gourou::appendTextElem(root, "id", (*token)["id"]); - gourou::appendTextElem(root, "operatorURL", (*token)["operatorURL"]); - gourou::appendTextElem(root, "validity", (*token)["validity"]); - gourou::appendTextElem(root, "name", item->getMetadata("title")); - - char * activationDir = strdup(deviceFile); - activationDir = dirname(activationDir); - - gourou::StringXMLWriter xmlWriter; - doc.save(xmlWriter, " "); - std::string xmlStr = xmlWriter.getResult(); - - // Use first bytes of SHA1(id) as filename - unsigned char sha1[gourou::SHA1_LEN]; - client.digest("SHA1", (unsigned char*)(*token)["id"].c_str(), (*token)["id"].size(), sha1); - gourou::ByteArray tmp(sha1, sizeof(sha1)); - std::string filenameHex = tmp.toHex(); - std::string filename(filenameHex.c_str(), ID_HASH_SIZE); - std::string fullPath = std::string(activationDir); - fullPath += std::string ("/") + std::string(LOANS_DIR); - mkpath(fullPath.c_str()); - fullPath += filename + std::string(".xml"); - gourou::writeFile(fullPath, xmlStr); - - std::cout << "Loan token serialized into " << fullPath << std::endl; - - free(activationDir); + this->app->exit(ret); } private: - DRMProcessorClientImpl client; -}; + QCoreApplication* app; +}; +static const char* findFile(const char* filename, bool inDefaultDirs=true) +{ + QFile file(filename); + + if (file.exists()) + return strdup(filename); + + if (!inDefaultDirs) return 0; + + for (int i=0; i<(int)ARRAY_SIZE(defaultDirs); i++) + { + QString path = QString(defaultDirs[i]) + QString(filename); + file.setFileName(path); + if (file.exists()) + return strdup(path.toStdString().c_str()); + } + + return 0; +} + +static void version(void) +{ + std::cout << "Current libgourou version : " << gourou::DRMProcessor::VERSION << std::endl ; +} static void usage(const char* cmd) { - std::cout << basename((char*)cmd) << " download EPUB file from ACSM request file" << std::endl << std::endl; - std::cout << "Usage: " << basename((char*)cmd) << " [OPTIONS] file.acsm" << std::endl << std::endl; - std::cout << "Global Options:" << std::endl; - std::cout << " " << "-O|--output-dir" << "\t" << "Optional output directory were to put result (default ./) (not compatible with -o)" << std::endl; - std::cout << " " << "-o|--output-file" << "\t" << "Optional output filename (default ) (not compatible with -O)" << std::endl; - std::cout << " " << "-f|--acsm-file" << "\t" << "Backward compatibility: ACSM request file for epub download" << std::endl; - std::cout << " " << "-e|--export-private-key"<< "\t" << "Export private key in DER format" << std::endl; - std::cout << " " << "-r|--resume" << "\t\t" << "Try to resume download (in case of previous failure)" << std::endl; - std::cout << " " << "-N|--no-notify" << "\t\t" << "Don't notify server, even if requested" << std::endl; + std::cout << "Download EPUB file from ACSM request file" << std::endl; + + std::cout << "Usage: " << cmd << " [(-d|--device-file) device.xml] [(-a|--activation-file) activation.xml] [(-s|--device-key-file) devicesalt] [(-O|--output-dir) dir] [(-o|--output-file) output.epub] [(-v|--verbose)] [(-h|--help)] (-f|--acsm-file) file.acsm" << std::endl << std::endl; + + std::cout << " " << "-d|--device-file" << "\t" << "device.xml file from eReader" << std::endl; + std::cout << " " << "-a|--activation-file" << "\t" << "activation.xml file from eReader" << std::endl; + std::cout << " " << "-k|--device-key-file" << "\t" << "private device key file (eg devicesalt/devkey.bin) from eReader" << std::endl; + std::cout << " " << "-O|--output-dir" << "\t" << "Optional output directory were to put result (default ./)" << std::endl; + std::cout << " " << "-o|--output-file" << "\t" << "Optional output epub filename (default )" << std::endl; + std::cout << " " << "-f|--acsm-file" << "\t" << "ACSM request file for epub download" << std::endl; std::cout << " " << "-v|--verbose" << "\t\t" << "Increase verbosity, can be set multiple times" << std::endl; std::cout << " " << "-V|--version" << "\t\t" << "Display libgourou version" << std::endl; std::cout << " " << "-h|--help" << "\t\t" << "This help" << std::endl; - std::cout << "ADEPT Options:" << std::endl; - std::cout << " " << "-D|--adept-directory" << "\t" << ".adept directory that must contains device.xml, activation.xml and devicesalt" << std::endl; - std::cout << " " << "-d|--device-file" << "\t" << "device.xml file from eReader" << std::endl; - std::cout << " " << "-a|--activation-file" << "\t" << "activation.xml file from eReader" << std::endl; - std::cout << " " << "-k|--device-key-file" << "\t" << "private device key file (eg devicesalt/devkey.bin) from eReader" << std::endl; - std::cout << std::endl; - - std::cout << "Environment:" << std::endl; - std::cout << "Device file, activation file and device key file are optionals. If not set, they are looked into :" << std::endl << std::endl; - std::cout << " * $ADEPT_DIR environment variable" << std::endl; - std::cout << " * /home//.config/adept" << 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 << " * Current directory" << std::endl; std::cout << " * .adept" << std::endl; std::cout << " * adobe-digital-editions directory" << std::endl; @@ -213,149 +162,105 @@ static void usage(const char* cmd) int main(int argc, char** argv) { int c, ret = -1; - std::string _deviceFile, _activationFile, _devicekeyFile; const char** files[] = {&devicekeyFile, &deviceFile, &activationFile}; int verbose = gourou::DRMProcessor::getLogLevel(); while (1) { - int option_index = 0; - static struct option long_options[] = { - {"adept-directory", required_argument, 0, 'D' }, - {"device-file", required_argument, 0, 'd' }, - {"activation-file", required_argument, 0, 'a' }, - {"device-key-file", required_argument, 0, 'k' }, - {"output-dir", required_argument, 0, 'O' }, - {"output-file", required_argument, 0, 'o' }, - {"acsm-file", required_argument, 0, 'f' }, - {"export-private-key",no_argument, 0, 'e' }, - {"resume", no_argument, 0, 'r' }, - {"no-notify", no_argument, 0, 'N' }, - {"verbose", no_argument, 0, 'v' }, - {"version", no_argument, 0, 'V' }, - {"help", no_argument, 0, 'h' }, - {0, 0, 0, 0 } - }; + int option_index = 0; + static struct option long_options[] = { + {"device-file", required_argument, 0, 'd' }, + {"activation-file", required_argument, 0, 'a' }, + {"device-key-file", required_argument, 0, 'k' }, + {"output-dir", required_argument, 0, 'O' }, + {"output-file", required_argument, 0, 'o' }, + {"acsm-file", required_argument, 0, 'f' }, + {"verbose", no_argument, 0, 'v' }, + {"version", no_argument, 0, 'V' }, + {"help", no_argument, 0, 'h' }, + {0, 0, 0, 0 } + }; - c = getopt_long(argc, argv, "D:d:a:k:O:o:f:erNvVh", + c = getopt_long(argc, argv, "d:a:k:O:o:f:vVh", long_options, &option_index); - if (c == -1) - break; + if (c == -1) + break; - switch (c) { - case 'D': - _deviceFile = std::string(optarg) + "/device.xml"; - _activationFile = std::string(optarg) + "/activation.xml"; - _devicekeyFile = std::string(optarg) + "/devicesalt"; - deviceFile = _deviceFile.c_str(); - activationFile = _activationFile.c_str(); - devicekeyFile = _devicekeyFile.c_str(); - break; - case 'd': - deviceFile = optarg; - break; - case 'a': - activationFile = optarg; - break; - case 'k': - devicekeyFile = optarg; - break; - case 'f': - acsmFile = optarg; - break; - case 'O': - outputDir = optarg; - break; - case 'o': - outputFile = optarg; - break; - case 'e': - exportPrivateKey = true; - break; - case 'r': - resume = true; - break; - case 'N': - notify = false; - break; - case 'v': - verbose++; - break; - case 'V': - version(); - return 0; - case 'h': - usage(argv[0]); - return 0; - default: - usage(argv[0]); - return -1; - } + switch (c) { + case 'd': + deviceFile = optarg; + break; + case 'a': + activationFile = optarg; + break; + case 'k': + devicekeyFile = optarg; + break; + case 'f': + acsmFile = optarg; + break; + case 'O': + outputDir = optarg; + break; + case 'o': + outputFile = optarg; + break; + case 'v': + verbose++; + break; + case 'V': + version(); + return 0; + case 'h': + usage(argv[0]); + return 0; + default: + usage(argv[0]); + return -1; + } } - + gourou::DRMProcessor::setLogLevel(verbose); - if (optind == argc-1) - acsmFile = argv[optind]; - - if ((!acsmFile && !exportPrivateKey) || (outputDir && !outputDir[0]) || - (outputFile && !outputFile[0])) + if (!acsmFile || (outputDir && !outputDir[0]) || + (outputFile && !outputFile[0])) { - usage(argv[0]); - return -1; + usage(argv[0]); + return -1; } - if (outputDir && outputFile) - { - std::cout << "Error : you cannot use both -o and -O" << std::endl; - return -1; - } - - ACSMDownloader downloader; + QCoreApplication app(argc, argv); + ACSMDownloader downloader(&app); int i; - bool hasErrors = false; - const char* orig; for (i=0; i<(int)ARRAY_SIZE(files); i++) { - orig = *files[i]; - *files[i] = findFile(*files[i]); - if (!*files[i]) - { - std::cout << "Error : " << orig << " doesn't exists, did you activate your device ?" << std::endl; - ret = -1; - hasErrors = true; - } + *files[i] = findFile(*files[i]); + if (!*files[i]) + { + std::cout << "Error : " << *files[i] << " doesn't exists" << std::endl; + ret = -1; + goto end; + } } - - if (hasErrors) - goto end; - - if (exportPrivateKey) + + QFile file(acsmFile); + if (!file.exists()) { - if (acsmFile) - { - usage(argv[0]); - return -1; - } - } - else - { - if (!pathExists(acsmFile)) - { - std::cout << "Error : " << acsmFile << " doesn't exists" << std::endl; - ret = -1; - goto end; - } + std::cout << "Error : " << acsmFile << " doesn't exists" << std::endl; + ret = -1; + goto end; } + + QThreadPool::globalInstance()->start(&downloader); - ret = downloader.run(); + ret = app.exec(); end: for (i=0; i<(int)ARRAY_SIZE(files); i++) { - if (*files[i]) - free((void*)*files[i]); + if (*files[i]) + free((void*)*files[i]); } return ret; diff --git a/utils/adept_activate.cpp b/utils/adept_activate.cpp index b12b617..3811143 100644 --- a/utils/adept_activate.cpp +++ b/utils/adept_activate.cpp @@ -4,16 +4,16 @@ All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - + * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. + notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. * Neither the name of the copyright holder nor the - names of its contributors may be used to endorse or promote products - derived from this software without specific prior written permission. - + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE @@ -28,17 +28,22 @@ #include #include +#include #include -#include -#include -#include #include #include +#include +#include +#include +#include +#include + #include #include "drmprocessorclientimpl.h" -#include "utils_common.h" + +#define ARRAY_SIZE(arr) (sizeof(arr)/sizeof(arr[0])) static const char* username = 0; static const char* password = 0; @@ -64,80 +69,90 @@ static int getch() { static std::string getpass(const char *prompt, bool show_asterisk=false) { - const char BACKSPACE=127; - const char RETURN=10; + const char BACKSPACE=127; + const char RETURN=10; - std::string password; - unsigned char ch=0; + std::string password; + unsigned char ch=0; - std::cout <signIn(username, password); - processor->activateDevice(); - - std::cout << username << " fully signed and device activated in " << outputDir << std::endl; - } catch(std::exception& e) - { - std::cout << e.what() << std::endl; - ret = 1; - } - - return ret; + setAutoDelete(false); } -}; + + void run() + { + int ret = 0; + try + { + DRMProcessorClientImpl client; + gourou::DRMProcessor* processor = gourou::DRMProcessor::createDRMProcessor( + &client, randomSerial, outputDir, hobbesVersion); + processor->signIn(username, password); + processor->activateDevice(); + + std::cout << username << " fully signed and device activated in " << outputDir << std::endl; + } catch(std::exception& e) + { + std::cout << e.what() << std::endl; + ret = 1; + } + + this->app->exit(ret); + } + +private: + QCoreApplication* app; +}; + +static void version(void) +{ + std::cout << "Current libgourou version : " << gourou::DRMProcessor::VERSION << std::endl ; +} static void usage(const char* cmd) { - std::cout << basename((char*)cmd) << " create new device files used by ADEPT DRM" << 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 << "Create new device files used by ADEPT DRM" << std::endl; + + std::cout << "Usage: " << cmd << " (-u|--username) username [(-p|--password) password] [(-O|--output-dir) dir] [(-r|--random-serial)] [(-v|--verbose)] [(-h|--help)]" << std::endl << 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 << " " << "-O|--output-dir" << "\t" << "Optional output directory were to put result (default ./.adept). This directory must not already exists" << std::endl; std::cout << " " << "-H|--hobbes-version" << "\t"<< "Force RMSDK version to a specific value (default: version of current librmsdk)" << std::endl; std::cout << " " << "-r|--random-serial" << "\t"<< "Generate a random device serial (if not set, it will be dependent of your current configuration)" << 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 << std::endl; @@ -146,8 +161,8 @@ static void usage(const char* cmd) static const char* abspath(const char* filename) { const char* root = getcwd(0, PATH_MAX); - std::string fullPath = std::string(root) + std::string("/") + filename; - const char* res = strdup(fullPath.c_str()); + QString fullPath = QString(root) + QString("/") + QString(filename); + const char* res = strdup(fullPath.toStdString().c_str()); free((void*)root); @@ -159,130 +174,100 @@ int main(int argc, char** argv) int c, ret = -1; const char* _outputDir = outputDir; int verbose = gourou::DRMProcessor::getLogLevel(); - bool anonymous = false; - + while (1) { - int option_index = 0; - static struct option long_options[] = { - {"anonymous", no_argument , 0, 'a' }, - {"username", required_argument, 0, 'u' }, - {"password", required_argument, 0, 'p' }, - {"output-dir", required_argument, 0, 'O' }, - {"hobbes-version",required_argument, 0, 'H' }, - {"random-serial", no_argument, 0, 'r' }, - {"verbose", no_argument, 0, 'v' }, - {"version", no_argument, 0, 'V' }, - {"help", no_argument, 0, 'h' }, - {0, 0, 0, 0 } - }; + int option_index = 0; + static struct option long_options[] = { + {"username", required_argument, 0, 'u' }, + {"password", required_argument, 0, 'p' }, + {"output-dir", required_argument, 0, 'O' }, + {"hobbes-version",required_argument, 0, 'H' }, + {"random-serial", no_argument, 0, 'r' }, + {"verbose", no_argument, 0, 'v' }, + {"version", no_argument, 0, 'V' }, + {"help", no_argument, 0, 'h' }, + {0, 0, 0, 0 } + }; - c = getopt_long(argc, argv, "au:p:O:H:rvVh", + c = getopt_long(argc, argv, "u:p:O:H:rvVh", long_options, &option_index); - if (c == -1) - break; + if (c == -1) + break; - switch (c) { - case 'a': - anonymous = true; - break; - case 'u': - username = optarg; - break; - case 'p': - password = optarg; - break; - case 'O': - _outputDir = optarg; - break; - case 'H': - hobbesVersion = optarg; - break; - case 'v': - verbose++; - break; - case 'V': - version(); - return 0; - case 'h': - usage(argv[0]); - return 0; - case 'r': - randomSerial = true; - break; - default: - usage(argv[0]); - return -1; - } + switch (c) { + case 'u': + username = optarg; + break; + case 'p': + password = optarg; + break; + case 'O': + _outputDir = optarg; + break; + case 'H': + hobbesVersion = optarg; + break; + case 'v': + verbose++; + break; + case 'V': + version(); + return 0; + case 'h': + usage(argv[0]); + return 0; + case 'r': + randomSerial = true; + break; + default: + usage(argv[0]); + return -1; + } } - + gourou::DRMProcessor::setLogLevel(verbose); - if ((!username && !anonymous) || - (username && anonymous)) + if (!username) { - usage(argv[0]); - return -1; + usage(argv[0]); + return -1; } - - if (anonymous) - { - username = "anonymous"; - password = ""; - } - + if (!_outputDir || _outputDir[0] == 0) { - outputDir = strdup(gourou::DRMProcessor::getDefaultAdeptDir().c_str()); + outputDir = abspath(DEFAULT_ADEPT_DIR); } else { - // Relative path - if (_outputDir[0] == '.' || _outputDir[0] != '/') - { - // realpath doesn't works if file/dir doesn't exists - if (pathExists(_outputDir)) - outputDir = strdup(realpath(_outputDir, 0)); - else - outputDir = strdup(abspath(_outputDir)); - } - else - outputDir = strdup(_outputDir); - } - - std::string pass; - if (pathExists(outputDir)) - { - int key; - - while (true) - { - std::cout << "!! Warning !! : " << outputDir << " already exists." << std::endl; - std::cout << "All your data will be overwrite. Would you like to continue ? [y/N] " << std::flush ; - key = getchar(); - if (key == 'n' || key == 'N' || key == '\n' || key == '\r') - goto end; - if (key == 'y' || key == 'Y') - break; - } - - // Clean STDIN buf - while ((key = getchar()) != '\n') - ; + // Relative path + if (_outputDir[0] == '.' || _outputDir[0] != '/') + { + QFile file(_outputDir); + // realpath doesn't works if file/dir doesn't exists + if (file.exists()) + outputDir = realpath(_outputDir, 0); + else + outputDir = abspath(_outputDir); + } + else + outputDir = strdup(_outputDir); } if (!password) { - char prompt[128]; - std::snprintf(prompt, sizeof(prompt), "Enter password for <%s> : ", username); - pass = getpass((const char*)prompt, false); - password = pass.c_str(); + char prompt[128]; + std::snprintf(prompt, sizeof(prompt), "Enter password for <%s> : ", username); + std::string pass = getpass((const char*)prompt, false); + password = pass.c_str(); } + + QCoreApplication app(argc, argv); + + ADEPTActivate activate(&app); + QThreadPool::globalInstance()->start(&activate); - ADEPTActivate activate; + ret = app.exec(); - ret = activate.run(); - -end: free((void*)outputDir); return ret; } diff --git a/utils/adept_loan_mgt.cpp b/utils/adept_loan_mgt.cpp deleted file mode 100644 index ab46d98..0000000 --- a/utils/adept_loan_mgt.cpp +++ /dev/null @@ -1,503 +0,0 @@ -/* - Copyright (c) 2022, Grégory Soutadé - - All rights reserved. - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * Neither the name of the copyright holder nor the - names of its contributors may be used to endorse or promote products - derived from this software without specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY - EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS BE LIABLE FOR ANY - DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND - ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include - -#include -#include - -#define _XOPEN_SOURCE 700 -#include -#include -#include -#include -#include - -#include -#include -#include "drmprocessorclientimpl.h" -#include "utils_common.h" - -#define MAX_SIZE_BOOK_NAME 30 - -static char* adeptDir = 0; -static const char* deviceFile = "device.xml"; -static const char* activationFile = "activation.xml"; -static const char* devicekeyFile = "devicesalt"; -static bool list = false; -static const char* returnID = 0; -static const char* deleteID = 0; -static bool notify = true; - -struct Loan -{ - std::string id; - std::string operatorURL; - std::string validity; - std::string bookName; - - std::string path; -}; - -class LoanMGT -{ -public: - ~LoanMGT() - { - for (const auto& kv : loanedBooks) - delete kv.second; - } - - int run() - { - int ret = 0; - try - { - DRMProcessorClientImpl client; - gourou::DRMProcessor processor(&client, deviceFile, activationFile, devicekeyFile); - - loadLoanedBooks(); - - if (list) - displayLoanList(); - else if (returnID) - returnBook(processor); - else if (deleteID) - deleteLoan(); - } catch(std::exception& e) - { - std::cout << e.what() << std::endl; - ret = 1; - } - - return ret; - } - -private: - void loadLoanedBooks() - { - DIR *dp; - struct dirent *ep; - int entryLen; - struct Loan* loan; - char * res; - - std::string loanDir = std::string(adeptDir) + std::string("/") + LOANS_DIR; - - if (!pathExists(loanDir.c_str())) - return; - - dp = opendir (loanDir.c_str()); - - if(!dp) - EXCEPTION(gourou::USER_INVALID_INPUT, "Cannot read directory " << loanDir); - - while ((ep = readdir (dp))) - { - if (ep->d_type != DT_LNK && - ep->d_type != DT_REG) - continue; - - entryLen = strlen(ep->d_name); - - if (entryLen <= 4 || - ep->d_name[entryLen-4] != '.' || - ep->d_name[entryLen-3] != 'x' || - ep->d_name[entryLen-2] != 'm' || - ep->d_name[entryLen-1] != 'l') - continue; - - std::string id = std::string(ep->d_name, entryLen-4); - - loan = new Loan; - loan->path = loanDir + std::string("/") + ep->d_name; - - pugi::xml_document xmlDoc; - pugi::xml_node node; - - if (!xmlDoc.load_file(loan->path.c_str(), pugi::parse_ws_pcdata_single|pugi::parse_escapes, pugi::encoding_utf8)) - { - std::cout << "Invalid loan entry " << loan->path << std::endl; - goto error; - } - - // id - node = xmlDoc.select_node("//id").node(); - if (!node) - { - std::cout << "Invalid loan entry " << ep->d_name << ", no id element" << std::endl; - goto error; - } - loan->id = node.first_child().value(); - - // operatorURL - node = xmlDoc.select_node("//operatorURL").node(); - if (!node) - { - std::cout << "Invalid loan entry " << ep->d_name << ", no operatorURL element" << std::endl; - goto error; - } - loan->operatorURL = node.first_child().value(); - - // validity - node = xmlDoc.select_node("//validity").node(); - if (!node) - { - std::cout << "Invalid loan entry " << ep->d_name << ", no validity element" << std::endl; - goto error; - } - loan->validity = node.first_child().value(); - - // bookName - node = xmlDoc.select_node("//name").node(); - if (!node) - { - std::cout << "Invalid loan entry " << ep->d_name << ", no name element" << std::endl; - goto error; - } - loan->bookName = node.first_child().value(); - - struct tm tm; -#ifdef __ANDROID__ - res = strptime(loan->validity.c_str(), "%Y-%m-%dT%H:%M:%S%z", &tm); -#else - res = strptime(loan->validity.c_str(), "%Y-%m-%dT%H:%M:%S%Z", &tm); -#endif - - if (res != NULL && *res == 0) - { - if (mktime(&tm) <= time(NULL)) - loan->validity = " (Expired)"; - } - else - { - std::cout << "Unable to parse validity timestamp :" << loan->validity << std::endl; - loan->validity = " (Unknown)"; - } - - loanedBooks[id] = loan; - continue; - - error: - if (loan) - delete loan; - } - - closedir (dp); - } - - void displayLoanList() - { - if (!loanedBooks.size()) - { - std::cout << "No books loaned" << std::endl; - return; - } - - struct Loan* loan; - unsigned int maxSizeBookName=0; - // Compute max size - for (const auto& kv : loanedBooks) - { - loan = kv.second; - if (loan->bookName.size() > maxSizeBookName) - maxSizeBookName = loan->bookName.size(); - } - - /* Manage empty names */ - if (maxSizeBookName == 0) - maxSizeBookName = sizeof("No name ")-1; - else if (maxSizeBookName < 4) - maxSizeBookName = 4; - else if (maxSizeBookName > MAX_SIZE_BOOK_NAME) - maxSizeBookName = MAX_SIZE_BOOK_NAME; - else if ((maxSizeBookName % 2)) - maxSizeBookName++; - - // std::cout << " ID Book Expiration" << std::endl; - // std::cout << "------------------------------" << std::endl; - - int fillID, fillBookName, fillExpiration=(20 - 10)/2; - - fillID = (ID_HASH_SIZE - 2) / 2; - fillBookName = (maxSizeBookName - 4) / 2; - - std::cout.width (fillID); - std::cout << ""; - std::cout << "ID" ; - std::cout.width (fillID); - std::cout << ""; - std::cout << " " ; - - std::cout.width (fillBookName); - std::cout << ""; - std::cout << "Book" ; - std::cout.width (fillBookName); - std::cout << ""; - std::cout << " " ; - - std::cout.width (fillExpiration); - std::cout << ""; - std::cout << "Expiration"; - std::cout.width (fillExpiration); - std::cout << "" << std::endl; - - std::cout.fill ('-'); - std::cout.width (ID_HASH_SIZE + 4 + maxSizeBookName + 4 + 20); - std::cout << "" << std::endl; - std::cout.fill (' '); - - std::string bookName; - - for (const auto& kv : loanedBooks) - { - loan = kv.second; - - std::cout << kv.first; - std::cout << " "; - - if (loan->bookName.size() == 0) - bookName = std::string("No name "); - else if (loan->bookName.size() > MAX_SIZE_BOOK_NAME) - bookName = std::string(loan->bookName.c_str(), MAX_SIZE_BOOK_NAME); - else - bookName = loan->bookName; - - std::cout << bookName; - std::cout.width (maxSizeBookName - bookName.size()); - std::cout << ""; - std::cout << " "; - - std::cout << loan->validity << std::endl; - } - - std::cout << std::endl; - } - - void returnBook(gourou::DRMProcessor& processor) - { - struct Loan* loan = loanedBooks[std::string(returnID)]; - - if (!loan) - { - std::cout << "Error : Loan " << returnID << " doesn't exists" << std::endl; - return; - } - - processor.returnLoan(loan->id, loan->operatorURL, notify); - - deleteID = returnID; - if (deleteLoan(false)) - { - std::cout << "Loan " << returnID << " successfully returned" << std::endl; - } - } - - bool deleteLoan(bool displayResult=true) - { - struct Loan* loan = loanedBooks[std::string(deleteID)]; - - if (!loan) - { - std::cout << "Error : Loan " << deleteID << " doesn't exists" << std::endl; - return false; - } - - if (unlink(loan->path.c_str())) - { - std::cout << "Error : Cannot delete " << loan->path << std::endl; - return false; - } - else if (displayResult) - { - std::cout << "Loan " << deleteID << " deleted" << std::endl; - } - - return true; - } - - std::map loanedBooks; -}; - - -static void usage(const char* cmd) -{ - std::cout << basename((char*)cmd) << " manage loaned books" << std::endl << std::endl; - - std::cout << "Usage: " << basename((char*)cmd) << " [OPTIONS]" << std::endl << std::endl; - - std::cout << "Global Options:" << std::endl; - std::cout << " " << "-l|--list" << "\t\t" << "List all loaned books" << std::endl; - std::cout << " " << "-r|--return" << "\t\t" << "Return a loaned book" << std::endl; - std::cout << " " << "-d|--delete" << "\t\t" << "Delete a loan entry without returning it" << std::endl; - std::cout << " " << "-N|--no-notify" << "\t\t" << "Don't notify server, even if requested" << std::endl; - std::cout << " " << "-v|--verbose" << "\t\t" << "Increase verbosity, can be set multiple times" << std::endl; - std::cout << " " << "-V|--version" << "\t\t" << "Display libgourou version" << std::endl; - std::cout << " " << "-h|--help" << "\t\t" << "This help" << std::endl; - - std::cout << "ADEPT Options:" << std::endl; - std::cout << " " << "-D|--adept-directory" << "\t" << ".adept directory that must contains device.xml, activation.xml and devicesalt" << std::endl; - std::cout << std::endl; - - std::cout << "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//.config/adept" << std::endl; - std::cout << " * Current directory" << std::endl; - std::cout << " * .adept" << std::endl; - std::cout << " * adobe-digital-editions directory" << std::endl; - std::cout << " * .adobe-digital-editions directory" << std::endl; -} - -int main(int argc, char** argv) -{ - int c, ret = -1; - - const char** files[] = {&devicekeyFile, &deviceFile, &activationFile}; - int verbose = gourou::DRMProcessor::getLogLevel(); - int actions = 0; - - while (1) { - int option_index = 0; - static struct option long_options[] = { - {"adept-directory", required_argument, 0, 'D' }, - {"list", no_argument, 0, 'l' }, - {"return", no_argument, 0, 'r' }, - {"delete", no_argument, 0, 'd' }, - {"no-notify", no_argument, 0, 'N' }, - {"verbose", no_argument, 0, 'v' }, - {"version", no_argument, 0, 'V' }, - {"help", no_argument, 0, 'h' }, - {0, 0, 0, 0 } - }; - - c = getopt_long(argc, argv, "d:lr:D:vVh", - long_options, &option_index); - if (c == -1) - break; - - switch (c) { - case 'D': - adeptDir = optarg; - break; - case 'l': - list = true; - actions++; - break; - case 'r': - returnID = optarg; - actions++; - break; - case 'd': - deleteID = optarg; - actions++; - break; - case 'N': - notify = false; - break; - case 'v': - verbose++; - break; - case 'V': - version(); - return 0; - case 'h': - usage(argv[0]); - return 0; - default: - usage(argv[0]); - return -1; - } - } - - gourou::DRMProcessor::setLogLevel(verbose); - - // By default, simply list books loaned - if (actions == 0) - list = true; - else if (actions != 1) - { - usage(argv[0]); - return -1; - } - - LoanMGT loanMGT; - - int i; - bool hasErrors = false; - const char* orig; - char *filename; - for (i=0; i<(int)ARRAY_SIZE(files); i++) - { - orig = *files[i]; - - if (adeptDir) - { - std::string path = std::string(adeptDir) + std::string("/") + orig; - filename = strdup(path.c_str()); - } - else - filename = strdup(orig); - *files[i] = findFile(filename); - free(filename); - if (!*files[i]) - { - std::cout << "Error : " << orig << " doesn't exists, did you activate your device ?" << std::endl; - hasErrors = true; - } - } - - if (hasErrors) - { - // In case of adept dir was provided by user - adeptDir = 0; - goto end; - } - - if (adeptDir) - adeptDir = strdup(adeptDir); // For below free - else - { - adeptDir = strdup(deviceFile); - adeptDir = dirname(adeptDir); - } - - ret = loanMGT.run(); - -end: - for (i=0; i<(int)ARRAY_SIZE(files); i++) - { - if (*files[i]) - free((void*)*files[i]); - } - - if (adeptDir) - free(adeptDir); - - return ret; -} diff --git a/utils/adept_remove.cpp b/utils/adept_remove.cpp deleted file mode 100644 index 47112ab..0000000 --- a/utils/adept_remove.cpp +++ /dev/null @@ -1,326 +0,0 @@ -/* - Copyright (c) 2021, Grégory Soutadé - - All rights reserved. - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * Neither the name of the copyright holder nor the - names of its contributors may be used to endorse or promote products - derived from this software without specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY - EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS BE LIABLE FOR ANY - DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND - ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include -#include - -#include - -#include -#include - -#include "drmprocessorclientimpl.h" -#include "utils_common.h" - -static const char* deviceFile = "device.xml"; -static const char* activationFile = "activation.xml"; -static const char* devicekeyFile = "devicesalt"; -static const char* inputFile = 0; -static const char* outputFile = 0; -static const char* outputDir = 0; - -static char* encryptionKeyUser = 0; -static unsigned char* encryptionKey = 0; -static unsigned encryptionKeySize = 0; - -static inline unsigned char htoi(unsigned char c) -{ - if (c >= '0' && c <= '9') - c -= '0'; - else if (c >= 'a' && c <= 'f') - c -= 'a' - 10; - else if (c >= 'A' && c <= 'F') - c -= 'A' - 10; - else - EXCEPTION(gourou::USER_INVALID_INPUT, "Invalid character " << c << " in encryption key"); - - return c; -} - -static inline bool endsWith(const std::string& s, const std::string& suffix) -{ - return s.rfind(suffix) == std::abs((int)(s.size()-suffix.size())); -} - -class ADEPTRemove -{ -public: - - int run() - { - int ret = 0; - try - { - gourou::DRMProcessor::ITEM_TYPE type; - DRMProcessorClientImpl client; - gourou::DRMProcessor processor(&client, deviceFile, activationFile, devicekeyFile); - - std::string filename; - if (outputFile) - filename = outputFile; - else - { - filename = std::string(inputFile); - - if (outputDir) - filename = std::string(outputDir) + "/" + filename; - } - - if (endsWith(filename, ".epub")) - type = gourou::DRMProcessor::ITEM_TYPE::EPUB; - else if (endsWith(filename, ".pdf")) - type = gourou::DRMProcessor::ITEM_TYPE::PDF; - else - { - EXCEPTION(gourou::DRM_FORMAT_NOT_SUPPORTED, "Unsupported file format of " << filename); - } - - createPath(filename.c_str()); - - if (inputFile != filename) - { - unlink(filename.c_str()); - fileCopy(inputFile, filename.c_str()); - processor.removeDRM(inputFile, filename, type, encryptionKey, encryptionKeySize); - std::cout << "DRM removed into new file " << filename << std::endl; - } - else - { - // Use temp file for PDF - if (type == gourou::DRMProcessor::ITEM_TYPE::PDF) - { - std::string tempFile = filename + ".tmp"; - /* Be sure there is not already a temp file */ - unlink(tempFile.c_str()); - processor.removeDRM(filename, tempFile, type, encryptionKey, encryptionKeySize); - /* Original file must be removed before doing a copy... */ - unlink(filename.c_str()); - if (rename(tempFile.c_str(), filename.c_str())) - { - EXCEPTION(gourou::DRM_FILE_ERROR, "Unable to copy " << tempFile << " into " << filename); - } - } - else - processor.removeDRM(inputFile, filename, type, encryptionKey, encryptionKeySize); - std::cout << "DRM removed from " << filename << std::endl; - } - } catch(std::exception& e) - { - std::cout << e.what() << std::endl; - ret = 1; - } - - return ret; - } -}; - -static void usage(const char* cmd) -{ - std::cout << basename((char*)cmd) << " remove ADEPT DRM (from Adobe) of EPUB/PDF file" << std::endl << std::endl; - - std::cout << "Usage: " << basename((char*)cmd) << " [OPTIONS] file(.epub|pdf)" << std::endl << std::endl; - - std::cout << "Global Options:" << std::endl; - std::cout << " " << "-O|--output-dir" << "\t" << "Optional output directory were to put result (default ./) (not compatible with -o)" << std::endl; - std::cout << " " << "-o|--output-file" << "\t" << "Optional output filename (default inplace DRM removal>) (not compatible with -O)" << std::endl; - std::cout << " " << "-f|--input-file" << "\t" << "Backward compatibility: EPUB/PDF file to process" << std::endl; - std::cout << " " << "-v|--verbose" << "\t\t" << "Increase verbosity, can be set multiple times" << std::endl; - std::cout << " " << "-V|--version" << "\t\t" << "Display libgourou version" << std::endl; - std::cout << " " << "-h|--help" << "\t\t" << "This help" << std::endl; - - std::cout << "ADEPT Options:" << std::endl; - std::cout << " " << "-D|--adept-directory" << "\t" << ".adept directory that must contains device.xml, activation.xml and devicesalt" << std::endl; - std::cout << " " << "-d|--device-file" << "\t" << "device.xml file from eReader" << std::endl; - std::cout << " " << "-a|--activation-file" << "\t" << "activation.xml file from eReader" << std::endl; - std::cout << " " << "-k|--device-key-file" << "\t" << "private device key file (eg devicesalt/devkey.bin) from eReader" << std::endl; - - std::cout << std::endl; - - std::cout << "Environment:" << std::endl; - std::cout << "Device file, activation file and device key file are optionals. If not set, they are looked into :" << std::endl; - std::cout << " * $ADEPT_DIR environment variable" << std::endl; - std::cout << " * /home//.config/adept" << std::endl; - std::cout << " * Current directory" << std::endl; - std::cout << " * .adept" << std::endl; - std::cout << " * adobe-digital-editions directory" << std::endl; - std::cout << " * .adobe-digital-editions directory" << std::endl; -} - -int main(int argc, char** argv) -{ - int c, ret = -1; - - const char** files[] = {&devicekeyFile, &deviceFile, &activationFile}; - int verbose = gourou::DRMProcessor::getLogLevel(); - std::string _deviceFile, _activationFile, _devicekeyFile; - - while (1) { - int option_index = 0; - static struct option long_options[] = { - {"adept-directory", required_argument, 0, 'D' }, - {"device-file", required_argument, 0, 'd' }, - {"activation-file", required_argument, 0, 'a' }, - {"device-key-file", required_argument, 0, 'k' }, - {"output-dir", required_argument, 0, 'O' }, - {"output-file", required_argument, 0, 'o' }, - {"input-file", required_argument, 0, 'f' }, - {"encryption-key", required_argument, 0, 'K' }, // Private option - {"verbose", no_argument, 0, 'v' }, - {"version", no_argument, 0, 'V' }, - {"help", no_argument, 0, 'h' }, - {0, 0, 0, 0 } - }; - - c = getopt_long(argc, argv, "D:d:a:k:O:o:f:K:vVh", - long_options, &option_index); - if (c == -1) - break; - - switch (c) { - case 'D': - _deviceFile = std::string(optarg) + "/device.xml"; - _activationFile = std::string(optarg) + "/activation.xml"; - _devicekeyFile = std::string(optarg) + "/devicesalt"; - deviceFile = _deviceFile.c_str(); - activationFile = _activationFile.c_str(); - devicekeyFile = _devicekeyFile.c_str(); - break; - case 'd': - deviceFile = optarg; - break; - case 'a': - activationFile = optarg; - break; - case 'k': - devicekeyFile = optarg; - break; - case 'f': - inputFile = optarg; - break; - case 'O': - outputDir = optarg; - break; - case 'o': - outputFile = optarg; - break; - case 'K': - encryptionKeyUser = optarg; - break; - case 'v': - verbose++; - break; - case 'V': - version(); - return 0; - case 'h': - usage(argv[0]); - return 0; - default: - usage(argv[0]); - return -1; - } - } - - gourou::DRMProcessor::setLogLevel(verbose); - - if (optind == argc-1) - inputFile = argv[optind]; - - if (!inputFile || (outputDir && !outputDir[0]) || - (outputFile && !outputFile[0])) - { - usage(argv[0]); - return -1; - } - - if (outputDir && outputFile) - { - std::cout << "Error : you cannot use both -o and -O" << std::endl; - return -1; - } - - ADEPTRemove remover; - - int i; - bool hasErrors = false; - const char* orig; - for (i=0; i<(int)ARRAY_SIZE(files); i++) - { - orig = *files[i]; - *files[i] = findFile(*files[i]); - if (!*files[i]) - { - std::cout << "Error : " << orig << " doesn't exists, did you activate your device ?" << std::endl; - ret = -1; - hasErrors = true; - } - } - - if (encryptionKeyUser) - { - int size = std::string(encryptionKeyUser).size(); - if ((size % 2)) - { - std::cout << "Error : Encryption key must be odd length" << std::endl; - goto end; - } - - if (encryptionKeyUser[0] == '0' && encryptionKeyUser[1] == 'x') - { - encryptionKeyUser += 2; - size -= 2; - } - - encryptionKey = new unsigned char[size/2]; - - for(i=0; i - -#include -#include -#include -#include - -#define OPENSSL_NO_DEPRECATED 1 #include #include #include #include -#include -#include -#include +#include +#include +#include +#include +#include -#include #include #include +#include #include "drmprocessorclientimpl.h" -static int error_cb(const char *str, size_t len, void *u) -{ - std::cout << str << std::endl; - return 0; -} - -DRMProcessorClientImpl::DRMProcessorClientImpl(): - legacy(0), deflt(0) -{ -#if OPENSSL_VERSION_MAJOR >= 3 - legacy = OSSL_PROVIDER_load(NULL, "legacy"); - if (!legacy) - { - ERR_print_errors_cb(error_cb, NULL); - EXCEPTION(gourou::CLIENT_OSSL_ERROR, "Error, OpenSSL legacy provider not available"); - } - - deflt = OSSL_PROVIDER_load(NULL, "default"); - if (!deflt) - EXCEPTION(gourou::CLIENT_OSSL_ERROR, "Error, OpenSSL default provider not available"); - OSSL_PROVIDER_load(NULL, "base"); -#endif - -#ifdef WIN32 - strcpy(cookiejar, "C:\\temp\\libgourou_cookie_jar_XXXXXX"); -#else - strcpy(cookiejar, "/tmp/libgourou_cookie_jar_XXXXXX"); -#endif - - int fd = mkstemp(cookiejar); - if (fd >= 0) - close(fd); - else - { - EXCEPTION(gourou::CLIENT_FILE_ERROR, "mkstemp error"); - } -} - -DRMProcessorClientImpl::~DRMProcessorClientImpl() -{ -#if OPENSSL_VERSION_MAJOR >= 3 - if (legacy) - OSSL_PROVIDER_unload(legacy); - - if (deflt) - OSSL_PROVIDER_unload(deflt); -#endif - - unlink(cookiejar); -} - /* Digest interface */ void* DRMProcessorClientImpl::createDigest(const std::string& digestName) { - EVP_MD_CTX *md_ctx = EVP_MD_CTX_new(); + EVP_MD_CTX *sha_ctx = EVP_MD_CTX_new(); const EVP_MD* md = EVP_get_digestbyname(digestName.c_str()); + EVP_DigestInit(sha_ctx, md); - if (EVP_DigestInit(md_ctx, md) != 1) - { - EVP_MD_CTX_free(md_ctx); - EXCEPTION(gourou::CLIENT_DIGEST_ERROR, ERR_error_string(ERR_get_error(), NULL)); - } - - return md_ctx; + return sha_ctx; } -void DRMProcessorClientImpl::digestUpdate(void* handler, unsigned char* data, unsigned int length) +int DRMProcessorClientImpl::digestUpdate(void* handler, unsigned char* data, unsigned int length) { - if (EVP_DigestUpdate((EVP_MD_CTX *)handler, data, length) != 1) - EXCEPTION(gourou::CLIENT_DIGEST_ERROR, ERR_error_string(ERR_get_error(), NULL)); + return EVP_DigestUpdate((EVP_MD_CTX *)handler, data, length); } -void DRMProcessorClientImpl::digestFinalize(void* handler, unsigned char* digestOut) +int DRMProcessorClientImpl::digestFinalize(void* handler, unsigned char* digestOut) { int res = EVP_DigestFinal((EVP_MD_CTX *)handler, digestOut, NULL); EVP_MD_CTX_free((EVP_MD_CTX *)handler); - - if (res <= 0) - EXCEPTION(gourou::CLIENT_DIGEST_ERROR, ERR_error_string(ERR_get_error(), NULL)); + return res; } -void DRMProcessorClientImpl::digest(const std::string& digestName, unsigned char* data, unsigned int length, unsigned char* digestOut) +int DRMProcessorClientImpl::digest(const std::string& digestName, unsigned char* data, unsigned int length, unsigned char* digestOut) { void* handler = createDigest(digestName); - digestUpdate(handler, data, length); - digestFinalize(handler, digestOut); + if (!handler) + return -1; + if (digestUpdate(handler, data, length)) + return -1; + return digestFinalize(handler, digestOut); } /* Random interface */ @@ -144,441 +82,168 @@ void DRMProcessorClientImpl::randBytes(unsigned char* bytesOut, unsigned int len } /* HTTP interface */ -#define HTTP_REQ_MAX_RETRY 5 -#define DISPLAY_THRESHOLD 10*1024 // Threshold to display download progression -static unsigned downloadedBytes; -static int lastPercent = -1; - -static int downloadProgress(void *clientp, curl_off_t dltotal, curl_off_t dlnow, - curl_off_t ultotal, curl_off_t ulnow) +std::string DRMProcessorClientImpl::sendHTTPRequest(const std::string& URL, const std::string& POSTData, const std::string& contentType) { -// For "big" files only - if (dltotal >= DISPLAY_THRESHOLD && gourou::logLevel >= gourou::LG_LOG_WARN) - { - int percent = 0; - if (dltotal) - percent = (dlnow * 100) / dltotal; + QNetworkRequest request(QUrl(URL.c_str())); + QNetworkAccessManager networkManager; + QByteArray replyData; - if (lastPercent != percent) - { - std::cout << "\rDownload " << percent << "%" << std::flush; - lastPercent = percent; - } - } - - return 0; -} - -static size_t curlRead(void *data, size_t size, size_t nmemb, void *userp) -{ - gourou::ByteArray* replyData = (gourou::ByteArray*) userp; - - replyData->append((unsigned char*)data, size*nmemb); - - return size*nmemb; -} - -static size_t curlReadFd(void *data, size_t size, size_t nmemb, void *userp) -{ - int fd = *(int*) userp; - - size_t res = write(fd, data, size*nmemb); - - downloadedBytes += res; - - return res; -} - -static size_t curlHeaders(char *buffer, size_t size, size_t nitems, void *userdata) -{ - std::map* responseHeaders = (std::map*)userdata; - std::string::size_type pos = 0; - std::string buf(buffer, size*nitems); - - pos = buf.find(":", pos); - - if (pos != std::string::npos) - { - std::string key = std::string(buffer, pos); - std::string value = std::string(&buffer[pos+1], (size*nitems)-(pos+1)); - - key = gourou::trim(key); - value = gourou::trim(value); - - (*responseHeaders)[key] = value; - - if (gourou::logLevel >= gourou::LG_LOG_DEBUG) - std::cout << key << " : " << value << std::endl; - } - - return size*nitems; -} - -std::string DRMProcessorClientImpl::sendHTTPRequest(const std::string& URL, const std::string& POSTData, const std::string& contentType, std::map* responseHeaders, int fd, bool resume) -{ - gourou::ByteArray replyData; - std::map localHeaders; - - if (!responseHeaders) - responseHeaders = &localHeaders; - - GOUROU_LOG(INFO, "Send request to " << URL); + GOUROU_LOG(gourou::INFO, "Send request to " << URL); if (POSTData.size()) { - GOUROU_LOG(DEBUG, "<<< " << std::endl << POSTData); + GOUROU_LOG(gourou::DEBUG, "<<< " << std::endl << POSTData); } - - unsigned prevDownloadedBytes; - downloadedBytes = 0; - if (fd && resume) - { - struct stat _stat; - if (!fstat(fd, &_stat)) - { - GOUROU_LOG(WARN, "Resume download @ " << _stat.st_size << " bytes"); - downloadedBytes = _stat.st_size; - } - else - GOUROU_LOG(WARN, "Want to resume, but fstat failed"); - } - - CURL *curl = curl_easy_init(); - CURLcode res; - curl_easy_setopt(curl, CURLOPT_URL, URL.c_str()); - curl_easy_setopt(curl, CURLOPT_USERAGENT, "book2png"); - curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1); - - - struct curl_slist *list = NULL; - list = curl_slist_append(list, "Accept: */*"); - std::string _contentType; + + request.setRawHeader("Accept", "*/*"); + request.setRawHeader("User-Agent", "book2png"); if (contentType.size()) - { - _contentType = "Content-Type: " + contentType; - list = curl_slist_append(list, _contentType.c_str()); - } + request.setRawHeader("Content-Type", contentType.c_str()); - curl_easy_setopt(curl, CURLOPT_HTTPHEADER, list); - curl_easy_setopt(curl, CURLOPT_COOKIEJAR, cookiejar); + QNetworkReply* reply; if (POSTData.size()) - { - curl_easy_setopt(curl, CURLOPT_POST, 1L); - curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, POSTData.size()); - curl_easy_setopt(curl, CURLOPT_POSTFIELDS, POSTData.data()); - } - - if (fd) - { - curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, curlReadFd); - curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void*)&fd); - } + reply = networkManager.post(request, POSTData.c_str()); else + reply = networkManager.get(request); + + QCoreApplication* app = QCoreApplication::instance(); + networkManager.moveToThread(app->thread()); + while (!reply->isFinished()) + app->processEvents(); + + QByteArray location = reply->rawHeader("Location"); + if (location.size() != 0) { - curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, curlRead); - curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void*)&replyData); + GOUROU_LOG(gourou::DEBUG, "New location"); + return sendHTTPRequest(location.constData(), POSTData, contentType); } - curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, curlHeaders); - curl_easy_setopt(curl, CURLOPT_HEADERDATA, (void*)responseHeaders); + if (reply->error() != QNetworkReply::NoError) + EXCEPTION(gourou::CLIENT_NETWORK_ERROR, "Error " << reply->errorString().toStdString()); - curl_easy_setopt(curl, CURLOPT_XFERINFOFUNCTION, downloadProgress); - curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 0); - lastPercent = -1; - - for (int i=0; i= gourou::DEBUG) { - prevDownloadedBytes = downloadedBytes; - if (downloadedBytes) - curl_easy_setopt(curl, CURLOPT_RESUME_FROM, downloadedBytes); - - res = curl_easy_perform(curl); - - // Connexion failed, wait & retry - if (res == CURLE_COULDNT_CONNECT) - { - GOUROU_LOG(WARN, "\nConnection failed, attempt " << (i+1) << "/" << HTTP_REQ_MAX_RETRY); - } - // Transfer failed but some data has been received - // --> try again without incrementing tries - else if (res == CURLE_RECV_ERROR) - { - if (prevDownloadedBytes != downloadedBytes) - { - GOUROU_LOG(WARN, "\nConnection broken, but data received, try again"); - i--; - } - else - GOUROU_LOG(WARN, "\nConnection broken and no data received, attempt " << (i+1) << "/" << HTTP_REQ_MAX_RETRY); - } - // Other error --> fail - else - break; - - // Wait a little bit (250ms * i) - usleep((250 * 1000) * (i+1)); - } - - curl_slist_free_all(list); - - long http_code = 400; - curl_easy_getinfo (curl, CURLINFO_RESPONSE_CODE, &http_code); - - curl_easy_cleanup(curl); - - if (res != CURLE_OK) - EXCEPTION(gourou::CLIENT_NETWORK_ERROR, "Error " << curl_easy_strerror(res)); - - if (http_code >= 400) - EXCEPTION(gourou::CLIENT_HTTP_ERROR, "HTTP Error code " << http_code); - - if ((downloadedBytes >= DISPLAY_THRESHOLD || replyData.size() >= DISPLAY_THRESHOLD) && - gourou::logLevel >= gourou::LG_LOG_WARN) - std::cout << std::endl; - - if ((*responseHeaders)["Content-Type"] == "application/vnd.adobe.adept+xml") - { - GOUROU_LOG(DEBUG, ">>> " << std::endl << replyData.data()); + QList headers = reply->rawHeaderList(); + for (int i = 0; i < headers.size(); ++i) { + std::cout << headers[i].constData() << " : " << reply->rawHeader(headers[i]).constData() << std::endl; + } } - 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; ireadAll(); + if (reply->rawHeader("Content-Type") == "application/vnd.adobe.adept+xml") { - while (out[i] == 0) - RAND_bytes(&out[i], 1); + GOUROU_LOG(gourou::DEBUG, ">>> " << std::endl << replyData.data()); } - - out[0] = 0x0; - out[1] = 0x2; - out[outLength - inLength - 1] = 0x00; - - memcpy(&out[outLength - inLength], in, inLength); + + return std::string(replyData.data(), replyData.length()); } - void DRMProcessorClientImpl::RSAPrivateEncrypt(const unsigned char* RSAKey, unsigned int RSAKeyLength, - const RSA_KEY_TYPE keyType, const std::string& password, - const unsigned char* data, unsigned dataLength, - unsigned char* res) + const RSA_KEY_TYPE keyType, const std::string& password, + const unsigned char* data, unsigned dataLength, + unsigned char* res) { PKCS12 * pkcs12; - EVP_PKEY_CTX *ctx; - EVP_PKEY* pkey = NULL; - size_t outlen; - unsigned char* tmp; - int ret; + EVP_PKEY* pkey; + X509* cert; + STACK_OF(X509)* ca; + RSA * rsa; pkcs12 = d2i_PKCS12(NULL, &RSAKey, RSAKeyLength); 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); + rsa = EVP_PKEY_get1_RSA(pkey); - if (PKCS12_parse(pkcs12, password.c_str(), &pkey, NULL, NULL) <= 0) - EXCEPTION(gourou::CLIENT_INVALID_PKCS12, ERR_error_string(ERR_get_error(), NULL)); - - outlen = EVP_PKEY_get_size(pkey); - - ctx = EVP_PKEY_CTX_new(pkey, NULL); - - /* Use RSA private key */ - if (EVP_PKEY_decrypt_init(ctx) <= 0) - EXCEPTION(gourou::CLIENT_RSA_ERROR, ERR_error_string(ERR_get_error(), NULL)); - - if (EVP_PKEY_CTX_set_rsa_padding(ctx, RSA_NO_PADDING) <= 0) - EXCEPTION(gourou::CLIENT_RSA_ERROR, ERR_error_string(ERR_get_error(), NULL)); - - tmp = (unsigned char*)malloc(outlen); - - /* PKCS1 functions are no more exported */ - padWithPKCS1(tmp, outlen, data, dataLength); - - ret = EVP_PKEY_decrypt(ctx, res, &outlen, tmp, outlen); - - EVP_PKEY_CTX_free(ctx); - free(tmp); - - if (ret <= 0) - EXCEPTION(gourou::CLIENT_RSA_ERROR, ERR_error_string(ERR_get_error(), NULL)); -} - -void DRMProcessorClientImpl::RSAPrivateDecrypt(const unsigned char* RSAKey, unsigned int RSAKeyLength, - const RSA_KEY_TYPE keyType, const std::string& password, - const unsigned char* data, unsigned dataLength, - unsigned char* res) -{ - BIO* mem = BIO_new_mem_buf(RSAKey, RSAKeyLength); - PKCS8_PRIV_KEY_INFO* p8inf = d2i_PKCS8_PRIV_KEY_INFO_bio(mem, NULL); - - if (!p8inf) - EXCEPTION(gourou::CLIENT_INVALID_PKCS8, ERR_error_string(ERR_get_error(), NULL)); - - EVP_PKEY_CTX *ctx; - EVP_PKEY* pkey = EVP_PKCS82PKEY(p8inf); - size_t outlen = dataLength; - int ret; - - if (!pkey) - EXCEPTION(gourou::CLIENT_INVALID_PKCS8, ERR_error_string(ERR_get_error(), NULL)); - - ctx = EVP_PKEY_CTX_new(pkey, NULL); - - if (EVP_PKEY_decrypt_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)); - - ret = EVP_PKEY_decrypt(ctx, res, &outlen, data, dataLength); - - PKCS8_PRIV_KEY_INFO_free(p8inf); - EVP_PKEY_CTX_free(ctx); - BIO_free(mem); - - if (ret <= 0) - EXCEPTION(gourou::CLIENT_RSA_ERROR, ERR_error_string(ERR_get_error(), NULL)); -} - -void DRMProcessorClientImpl::RSAPublicEncrypt(const unsigned char* RSAKey, unsigned int RSAKeyLength, - const RSA_KEY_TYPE keyType, - const unsigned char* data, unsigned dataLength, - unsigned char* res) -{ - size_t outlen; - unsigned char* tmp; - - X509 * x509 = d2i_X509(0, &RSAKey, RSAKeyLength); - if (!x509) - EXCEPTION(gourou::CLIENT_INVALID_CERTIFICATE, "Invalid certificate"); - - EVP_PKEY_CTX *ctx; - EVP_PKEY * pkey = X509_get_pubkey(x509); - - if (!pkey) - EXCEPTION(gourou::CLIENT_NO_PUB_KEY, "No public key in certificate"); - - ctx = EVP_PKEY_CTX_new(pkey, NULL); - - if (EVP_PKEY_encrypt_init(ctx) <= 0) - EXCEPTION(gourou::CLIENT_RSA_ERROR, ERR_error_string(ERR_get_error(), NULL)); - - if (EVP_PKEY_CTX_set_rsa_padding(ctx, RSA_NO_PADDING) <= 0) - EXCEPTION(gourou::CLIENT_RSA_ERROR, ERR_error_string(ERR_get_error(), NULL)); - - outlen = EVP_PKEY_get_size(pkey); - - tmp = (unsigned char*)malloc(outlen); - - /* - PKCS1 functions are no more exported. - Some OpenSSL libraries still use type 1 - */ - padWithPKCS1Type2(tmp, outlen, data, dataLength); - - int ret = EVP_PKEY_encrypt(ctx, res, &outlen, tmp, outlen); - - EVP_PKEY_CTX_free(ctx); - free(tmp); - - EVP_PKEY_free(pkey); + int ret = RSA_private_encrypt(dataLength, data, res, rsa, RSA_PKCS1_PADDING); 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)); + + if (gourou::logLevel >= gourou::DEBUG) + { + printf("Sig : "); + for(int i=0; i<(int)sizeof(res); i++) + printf("%02x ", res[i]); + printf("\n"); + } +} + +void DRMProcessorClientImpl::RSAPublicEncrypt(const unsigned char* RSAKey, unsigned int RSAKeyLength, + const RSA_KEY_TYPE keyType, + const unsigned char* data, unsigned dataLength, + unsigned char* res) +{ + X509 * x509 = d2i_X509(0, &RSAKey, RSAKeyLength); + if (!x509) + EXCEPTION(gourou::CLIENT_INVALID_CERTIFICATE, "Invalid certificate"); + + EVP_PKEY * evpKey = X509_get_pubkey(x509); + RSA* rsa = EVP_PKEY_get1_RSA(evpKey); + EVP_PKEY_free(evpKey); + + if (!rsa) + EXCEPTION(gourou::CLIENT_NO_PRIV_KEY, "No private key in certificate"); + + int ret = RSA_public_encrypt(dataLength, data, res, rsa, RSA_PKCS1_PADDING); + if (ret < 0) + EXCEPTION(gourou::CLIENT_RSA_ERROR, ERR_error_string(ERR_get_error(), NULL)); } void* DRMProcessorClientImpl::generateRSAKey(int keyLengthBits) { BIGNUM * bn = BN_new(); - EVP_PKEY_CTX* ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_RSA, NULL); - EVP_PKEY *key = NULL; - + RSA * rsa = RSA_new(); BN_set_word(bn, 0x10001); - - 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); + RSA_generate_key_ex(rsa, keyLengthBits, bn, 0); BN_free(bn); - return key; + return rsa; } void DRMProcessorClientImpl::destroyRSAHandler(void* handler) { - free(handler); + RSA_free((RSA*)handler); } void DRMProcessorClientImpl::extractRSAPublicKey(void* handler, unsigned char** keyOut, unsigned int* keyOutLength) { + EVP_PKEY * evpKey = EVP_PKEY_new(); + EVP_PKEY_set1_RSA(evpKey, (RSA*)handler); X509_PUBKEY *x509_pubkey = 0; - X509_PUBKEY_set(&x509_pubkey, (EVP_PKEY*)handler); + X509_PUBKEY_set(&x509_pubkey, evpKey); *keyOutLength = i2d_X509_PUBKEY(x509_pubkey, keyOut); X509_PUBKEY_free(x509_pubkey); + EVP_PKEY_free(evpKey); } void DRMProcessorClientImpl::extractRSAPrivateKey(void* handler, unsigned char** keyOut, unsigned int* keyOutLength) { - PKCS8_PRIV_KEY_INFO * privKey = EVP_PKEY2PKCS8((EVP_PKEY*)handler); + EVP_PKEY * evpKey = EVP_PKEY_new(); + EVP_PKEY_set1_RSA(evpKey, (RSA*)handler); + PKCS8_PRIV_KEY_INFO * privKey = EVP_PKEY2PKCS8(evpKey); *keyOutLength = i2d_PKCS8_PRIV_KEY_INFO(privKey, keyOut); PKCS8_PRIV_KEY_INFO_free(privKey); + EVP_PKEY_free(evpKey); } - + void DRMProcessorClientImpl::extractCertificate(const unsigned char* RSAKey, unsigned int RSAKeyLength, - const RSA_KEY_TYPE keyType, const std::string& password, - unsigned char** certOut, unsigned int* certOutLength) + const RSA_KEY_TYPE keyType, const std::string& password, + unsigned char** certOut, unsigned int* certOutLength) { PKCS12 * pkcs12; EVP_PKEY* pkey = 0; X509* cert = 0; + STACK_OF(X509)* ca; pkcs12 = d2i_PKCS12(NULL, &RSAKey, RSAKeyLength); if (!pkcs12) - EXCEPTION(gourou::CLIENT_INVALID_PKCS12, ERR_error_string(ERR_get_error(), NULL)); - PKCS12_parse(pkcs12, password.c_str(), &pkey, &cert, NULL); - - 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)); + PKCS12_parse(pkcs12, password.c_str(), &pkey, &cert, &ca); *certOutLength = i2d_X509(cert, certOut); @@ -586,175 +251,113 @@ void DRMProcessorClientImpl::extractCertificate(const unsigned char* RSAKey, uns } /* Crypto interface */ -void DRMProcessorClientImpl::encrypt(CRYPTO_ALGO algo, CHAINING_MODE chaining, - const unsigned char* key, unsigned int keyLength, - const unsigned char* iv, unsigned int ivLength, - const unsigned char* dataIn, unsigned int dataInLength, - unsigned char* dataOut, unsigned int* dataOutLength) +void DRMProcessorClientImpl::AESEncrypt(CHAINING_MODE chaining, + const unsigned char* key, unsigned int keyLength, + const unsigned char* iv, unsigned int ivLength, + const unsigned char* dataIn, unsigned int dataInLength, + unsigned char* dataOut, unsigned int* dataOutLength) { - void* handler = encryptInit(algo, chaining, key, keyLength, iv, ivLength); - encryptUpdate(handler, dataIn, dataInLength, dataOut, dataOutLength); - encryptFinalize(handler, dataOut+*dataOutLength, dataOutLength); + void* handler = AESEncryptInit(chaining, key, keyLength, iv, ivLength); + AESEncryptUpdate(handler, dataIn, dataInLength, dataOut, dataOutLength); + AESEncryptFinalize(handler, dataOut+*dataOutLength, dataOutLength); } -void* DRMProcessorClientImpl::encryptInit(CRYPTO_ALGO algo, CHAINING_MODE chaining, - const unsigned char* key, unsigned int keyLength, - const unsigned char* iv, unsigned int ivLength) +void* DRMProcessorClientImpl::AESEncryptInit(CHAINING_MODE chaining, + const unsigned char* key, unsigned int keyLength, + const unsigned char* iv, unsigned int ivLength) { EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new(); - int ret = 0; - switch (algo) + switch(keyLength) { - case ALGO_AES: - { - switch(keyLength) - { - case 16: - switch(chaining) - { - case CHAIN_ECB: - ret = EVP_EncryptInit(ctx, EVP_aes_128_ecb(), key, iv); - break; - case CHAIN_CBC: - ret = EVP_EncryptInit(ctx, EVP_aes_128_cbc(), key, iv); - break; - default: - EXCEPTION(gourou::CLIENT_BAD_CHAINING, "Unknown chaining mode " << chaining); - } - break; - default: - EVP_CIPHER_CTX_free(ctx); - EXCEPTION(gourou::CLIENT_BAD_KEY_SIZE, "Invalid key size " << keyLength); - } - break; - } - case ALGO_RC4: - { - if (keyLength != 16) - { - EVP_CIPHER_CTX_free(ctx); - EXCEPTION(gourou::CLIENT_BAD_KEY_SIZE, "Invalid key size " << keyLength); - } - 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)); + case 16: + switch(chaining) + { + case CHAIN_ECB: + EVP_EncryptInit_ex(ctx, EVP_aes_128_ecb(), NULL, key, iv); + break; + case CHAIN_CBC: + EVP_EncryptInit_ex(ctx, EVP_aes_128_cbc(), NULL, key, iv); + break; + default: + EXCEPTION(gourou::CLIENT_BAD_CHAINING, "Unknown chaining mode " << chaining); + } + break; + default: + EVP_CIPHER_CTX_free(ctx); + EXCEPTION(gourou::CLIENT_BAD_KEY_SIZE, "Invalid key size " << keyLength); } return ctx; } -void* DRMProcessorClientImpl::decryptInit(CRYPTO_ALGO algo, CHAINING_MODE chaining, - const unsigned char* key, unsigned int keyLength, - const unsigned char* iv, unsigned int ivLength) +void* DRMProcessorClientImpl::AESDecryptInit(CHAINING_MODE chaining, + const unsigned char* key, unsigned int keyLength, + const unsigned char* iv, unsigned int ivLength) { EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new(); - int ret = 0; - switch(algo) + switch(keyLength) { - case ALGO_AES: - { - switch(keyLength) - { - case 16: - switch(chaining) - { - case CHAIN_ECB: - ret = EVP_DecryptInit(ctx, EVP_aes_128_ecb(), key, iv); - break; - case CHAIN_CBC: - ret = EVP_DecryptInit(ctx, EVP_aes_128_cbc(), key, iv); - break; - default: - EXCEPTION(gourou::CLIENT_BAD_CHAINING, "Unknown chaining mode " << chaining); - } - break; - default: - EVP_CIPHER_CTX_free(ctx); - EXCEPTION(gourou::CLIENT_BAD_KEY_SIZE, "Invalid key size " << keyLength); - } - break; - } - case ALGO_RC4: - { - if (keyLength != 16) - { - EVP_CIPHER_CTX_free(ctx); - EXCEPTION(gourou::CLIENT_BAD_KEY_SIZE, "Invalid key size " << keyLength); - } - 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)); + case 16: + switch(chaining) + { + case CHAIN_ECB: + EVP_DecryptInit_ex(ctx, EVP_aes_128_ecb(), NULL, key, iv); + break; + case CHAIN_CBC: + EVP_DecryptInit_ex(ctx, EVP_aes_128_cbc(), NULL, key, iv); + break; + default: + EXCEPTION(gourou::CLIENT_BAD_CHAINING, "Unknown chaining mode " << chaining); + } + break; + default: + EVP_CIPHER_CTX_free(ctx); + EXCEPTION(gourou::CLIENT_BAD_KEY_SIZE, "Invalid key size " << keyLength); } return ctx; } -void DRMProcessorClientImpl::encryptUpdate(void* handler, const unsigned char* dataIn, unsigned int dataInLength, - unsigned char* dataOut, unsigned int* dataOutLength) +void DRMProcessorClientImpl::AESEncryptUpdate(void* handler, const unsigned char* dataIn, unsigned int dataInLength, + unsigned char* dataOut, unsigned int* dataOutLength) { - 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)); + EVP_EncryptUpdate((EVP_CIPHER_CTX*)handler, dataOut, (int*)dataOutLength, dataIn, dataInLength); } -void DRMProcessorClientImpl::encryptFinalize(void* handler, - unsigned char* dataOut, unsigned int* dataOutLength) +void DRMProcessorClientImpl::AESEncryptFinalize(void* handler, + unsigned char* dataOut, unsigned int* dataOutLength) { - int len, ret; - - ret = EVP_EncryptFinal_ex((EVP_CIPHER_CTX*)handler, dataOut, &len); + int len; + EVP_EncryptFinal_ex((EVP_CIPHER_CTX*)handler, dataOut, &len); *dataOutLength += len; EVP_CIPHER_CTX_free((EVP_CIPHER_CTX*)handler); - - if (ret <= 0) - EXCEPTION(gourou::CLIENT_CRYPT_ERROR, ERR_error_string(ERR_get_error(), NULL)); } -void DRMProcessorClientImpl::decrypt(CRYPTO_ALGO algo, CHAINING_MODE chaining, - const unsigned char* key, unsigned int keyLength, - const unsigned char* iv, unsigned int ivLength, - const unsigned char* dataIn, unsigned int dataInLength, - unsigned char* dataOut, unsigned int* dataOutLength) +void DRMProcessorClientImpl::AESDecrypt(CHAINING_MODE chaining, + const unsigned char* key, unsigned int keyLength, + const unsigned char* iv, unsigned int ivLength, + const unsigned char* dataIn, unsigned int dataInLength, + unsigned char* dataOut, unsigned int* dataOutLength) { - void* handler = decryptInit(algo, chaining, key, keyLength, iv, ivLength); - decryptUpdate(handler, dataIn, dataInLength, dataOut, dataOutLength); - decryptFinalize(handler, dataOut+*dataOutLength, dataOutLength); + void* handler = AESDecryptInit(chaining, key, keyLength, iv, ivLength); + AESDecryptUpdate(handler, dataIn, dataInLength, dataOut, dataOutLength); + AESDecryptFinalize(handler, dataOut+*dataOutLength, dataOutLength); } -void DRMProcessorClientImpl::decryptUpdate(void* handler, const unsigned char* dataIn, unsigned int dataInLength, - unsigned char* dataOut, unsigned int* dataOutLength) +void DRMProcessorClientImpl::AESDecryptUpdate(void* handler, const unsigned char* dataIn, unsigned int dataInLength, + unsigned char* dataOut, unsigned int* dataOutLength) { - 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)); + EVP_DecryptUpdate((EVP_CIPHER_CTX*)handler, dataOut, (int*)dataOutLength, dataIn, dataInLength); } -void DRMProcessorClientImpl::decryptFinalize(void* handler, unsigned char* dataOut, unsigned int* dataOutLength) +void DRMProcessorClientImpl::AESDecryptFinalize(void* handler, unsigned char* dataOut, unsigned int* dataOutLength) { - int len, ret; - - ret = EVP_DecryptFinal_ex((EVP_CIPHER_CTX*)handler, dataOut, &len); + int len; + EVP_DecryptFinal_ex((EVP_CIPHER_CTX*)handler, dataOut, &len); *dataOutLength += len; EVP_CIPHER_CTX_free((EVP_CIPHER_CTX*)handler); - - if (ret <= 0) - EXCEPTION(gourou::CLIENT_CRYPT_ERROR, ERR_error_string(ERR_get_error(), NULL)); } void* DRMProcessorClientImpl::zipOpen(const std::string& path) @@ -762,47 +365,43 @@ void* DRMProcessorClientImpl::zipOpen(const std::string& path) zip_t* handler = zip_open(path.c_str(), 0, 0); if (!handler) - EXCEPTION(gourou::CLIENT_BAD_ZIP_FILE, "Invalid zip file " << path); + EXCEPTION(gourou::CLIENT_BAD_ZIP_FILE, "Invalid zip file " << path); return handler; } -void DRMProcessorClientImpl::zipReadFile(void* handler, const std::string& path, gourou::ByteArray& result, bool decompress) +std::string DRMProcessorClientImpl::zipReadFile(void* handler, const std::string& path) { std::string res; + unsigned char* buffer; zip_stat_t sb; - + if (zip_stat((zip_t *)handler, path.c_str(), 0, &sb) < 0) - EXCEPTION(gourou::CLIENT_ZIP_ERROR, "Zip error, no file " << path << ", " << zip_strerror((zip_t *)handler)); + EXCEPTION(gourou::CLIENT_ZIP_ERROR, "Zip error " << zip_strerror((zip_t *)handler)); if (!(sb.valid & (ZIP_STAT_INDEX|ZIP_STAT_SIZE))) - EXCEPTION(gourou::CLIENT_ZIP_ERROR, "Required fields missing"); + EXCEPTION(gourou::CLIENT_ZIP_ERROR, "Required fields missing"); + + buffer = new unsigned char[sb.size]; + + zip_file_t *f = zip_fopen_index((zip_t *)handler, sb.index, ZIP_FL_COMPRESSED); - result.resize(sb.size); - - zip_file_t *f = zip_fopen_index((zip_t *)handler, sb.index, (decompress)?0:ZIP_FL_COMPRESSED); - zip_fread(f, result.data(), sb.size); + zip_fread(f, buffer, sb.size); zip_fclose(f); + + res = std::string((char*)buffer, sb.size); + delete[] buffer; + + return res; } -void DRMProcessorClientImpl::zipWriteFile(void* handler, const std::string& path, gourou::ByteArray& content) +void DRMProcessorClientImpl::zipWriteFile(void* handler, const std::string& path, const std::string& content) { - zip_int64_t ret; - - zip_source_t* s = zip_source_buffer((zip_t*)handler, content.takeShadowData(), content.length(), 1); - - zip_int64_t idx = zip_name_locate((zip_t*)handler, path.c_str(), 0); - - // File doesn't exists - if (idx == -1) - ret = zip_file_add((zip_t*)handler, path.c_str(), s, 0); - else - ret = zip_file_replace((zip_t*)handler, idx, s, ZIP_FL_OVERWRITE); - - if (ret < 0) + zip_source_t* s = zip_source_buffer((zip_t*)handler, content.c_str(), content.length(), 0); + if (zip_file_add((zip_t*)handler, path.c_str(), s, ZIP_FL_OVERWRITE|ZIP_FL_ENC_UTF_8) < 0) { - zip_source_free(s); - EXCEPTION(gourou::CLIENT_ZIP_ERROR, "Zip error " << zip_strerror((zip_t *)handler)); + zip_source_free(s); + EXCEPTION(gourou::CLIENT_ZIP_ERROR, "Zip error " << zip_strerror((zip_t *)handler)); } } @@ -811,103 +410,13 @@ void DRMProcessorClientImpl::zipDeleteFile(void* handler, const std::string& pat zip_int64_t idx = zip_name_locate((zip_t*)handler, path.c_str(), 0); if (idx < 0) - EXCEPTION(gourou::CLIENT_ZIP_ERROR, "No such file " << path.c_str()); - + EXCEPTION(gourou::CLIENT_ZIP_ERROR, "No such file " << path.c_str()); + if (zip_delete((zip_t*)handler, idx)) - EXCEPTION(gourou::CLIENT_ZIP_ERROR, "Zip error " << zip_strerror((zip_t *)handler)); + EXCEPTION(gourou::CLIENT_ZIP_ERROR, "Zip error " << zip_strerror((zip_t *)handler)); } void DRMProcessorClientImpl::zipClose(void* handler) { zip_close((zip_t*)handler); } - -void DRMProcessorClientImpl::inflate(gourou::ByteArray& data, gourou::ByteArray& result, - int wbits) -{ - unsigned int dataSize = data.size()*2; - unsigned char* buffer = new unsigned char[dataSize]; - - z_stream infstream; - - infstream.zalloc = Z_NULL; - infstream.zfree = Z_NULL; - infstream.opaque = Z_NULL; - - infstream.avail_in = (uInt)data.size(); - infstream.next_in = (Bytef *)data.data(); // input char array - infstream.avail_out = (uInt)dataSize; // size of output - infstream.next_out = (Bytef *)buffer; // output char array - - int ret = inflateInit2(&infstream, wbits); - - if (ret != Z_OK) - EXCEPTION(gourou::CLIENT_ZIP_ERROR, "Inflate error, code " << zError(ret) << ", msg " << infstream.msg); - - ret = ::inflate(&infstream, Z_FINISH); - while (ret == Z_OK || ret == Z_STREAM_END || ret == Z_BUF_ERROR) - { - // Real error - if (ret == Z_BUF_ERROR && infstream.avail_out == (uInt)dataSize) - break; - - result.append(buffer, dataSize-infstream.avail_out); - - if ((ret == Z_OK && infstream.avail_out != 0) || ret == Z_STREAM_END) - break; - infstream.avail_out = (uInt)dataSize; // size of output - infstream.next_out = (Bytef *)buffer; // output char array - ret = ::inflate(&infstream, Z_FINISH); - } - - if (ret == Z_STREAM_END) - ret = inflateEnd(&infstream); - - delete[] buffer; - - if (ret != Z_OK && ret != Z_STREAM_END) - EXCEPTION(gourou::CLIENT_ZIP_ERROR, "Inflate error, code " << zError(ret) << ", msg " << infstream.msg); -} - -void DRMProcessorClientImpl::deflate(gourou::ByteArray& data, gourou::ByteArray& result, - int wbits, int compressionLevel) -{ - unsigned int dataSize = data.size(); - unsigned char* buffer = new unsigned char[dataSize]; - - z_stream defstream; - - defstream.zalloc = Z_NULL; - defstream.zfree = Z_NULL; - defstream.opaque = Z_NULL; - - defstream.avail_in = (uInt)dataSize; - defstream.next_in = (Bytef *)data.data(); // input char array - defstream.avail_out = (uInt)dataSize; // size of output - defstream.next_out = (Bytef *)buffer; // output char array - - int ret = deflateInit2(&defstream, Z_DEFAULT_COMPRESSION, Z_DEFLATED, wbits, - compressionLevel, Z_DEFAULT_STRATEGY); - - if (ret != Z_OK) - EXCEPTION(gourou::CLIENT_ZIP_ERROR, "Deflate error, code " << zError(ret) << ", msg " << defstream.msg); - - ret = ::deflate(&defstream, Z_FINISH); - while (ret == Z_OK || ret == Z_STREAM_END) - { - result.append(buffer, dataSize-defstream.avail_out); - if ((ret == Z_OK && defstream.avail_out != 0) || ret == Z_STREAM_END) - break; - defstream.avail_out = (uInt)dataSize; // size of output - defstream.next_out = (Bytef *)buffer; // output char array - ret = ::deflate(&defstream, Z_FINISH); - } - - if (ret == Z_STREAM_END) - ret = deflateEnd(&defstream); - - delete[] buffer; - - if (ret != Z_OK && ret != Z_STREAM_END) - EXCEPTION(gourou::CLIENT_ZIP_ERROR, "Deflate error, code " << zError(ret) << ", msg " << defstream.msg); -} diff --git a/utils/drmprocessorclientimpl.h b/utils/drmprocessorclientimpl.h index 453058b..f475d96 100644 --- a/utils/drmprocessorclientimpl.h +++ b/utils/drmprocessorclientimpl.h @@ -4,16 +4,16 @@ All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - + * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. + notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. * Neither the name of the copyright holder nor the - names of its contributors may be used to endorse or promote products - derived from this software without specific prior written permission. - + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE @@ -31,115 +31,83 @@ #include -#if OPENSSL_VERSION_MAJOR >= 3 -#include -#endif - #include class DRMProcessorClientImpl : public gourou::DRMProcessorClient { -public: - DRMProcessorClientImpl(); - ~DRMProcessorClientImpl(); - + public: /* Digest interface */ virtual void* createDigest(const std::string& digestName); - virtual void digestUpdate(void* handler, unsigned char* data, unsigned int length); - virtual void digestFinalize(void* handler,unsigned char* digestOut); - virtual void digest(const std::string& digestName, unsigned char* data, unsigned int length, unsigned char* digestOut); + virtual int digestUpdate(void* handler, unsigned char* data, unsigned int length); + virtual int digestFinalize(void* handler,unsigned char* digestOut); + virtual int digest(const std::string& digestName, unsigned char* data, unsigned int length, unsigned char* digestOut); /* Random interface */ virtual void randBytes(unsigned char* bytesOut, unsigned int length); /* HTTP interface */ - virtual std::string sendHTTPRequest(const std::string& URL, const std::string& POSTData=std::string(""), const std::string& contentType=std::string(""), std::map* responseHeaders=0, int fd=0, bool resume=false); + virtual std::string sendHTTPRequest(const std::string& URL, const std::string& POSTData=std::string(""), const std::string& contentType=std::string("")); virtual void RSAPrivateEncrypt(const unsigned char* RSAKey, unsigned int RSAKeyLength, - const RSA_KEY_TYPE keyType, const std::string& password, - const unsigned char* data, unsigned dataLength, - unsigned char* res); - - virtual void RSAPrivateDecrypt(const unsigned char* RSAKey, unsigned int RSAKeyLength, - const RSA_KEY_TYPE keyType, const std::string& password, - const unsigned char* data, unsigned dataLength, - unsigned char* res); - + const RSA_KEY_TYPE keyType, const std::string& password, + const unsigned char* data, unsigned dataLength, + unsigned char* res); + virtual void RSAPublicEncrypt(const unsigned char* RSAKey, unsigned int RSAKeyLength, - const RSA_KEY_TYPE keyType, - const unsigned char* data, unsigned dataLength, - unsigned char* res); + const RSA_KEY_TYPE keyType, + const unsigned char* data, unsigned dataLength, + unsigned char* res); virtual void* generateRSAKey(int keyLengthBits); virtual void destroyRSAHandler(void* handler); - + virtual void extractRSAPublicKey(void* RSAKeyHandler, unsigned char** keyOut, unsigned int* keyOutLength); virtual void extractRSAPrivateKey(void* RSAKeyHandler, unsigned char** keyOut, unsigned int* keyOutLength); virtual void extractCertificate(const unsigned char* RSAKey, unsigned int RSAKeyLength, - const RSA_KEY_TYPE keyType, const std::string& password, - unsigned char** certOut, unsigned int* certOutLength); - + const RSA_KEY_TYPE keyType, const std::string& password, + unsigned char** certOut, unsigned int* certOutLength); + /* Crypto interface */ - virtual void encrypt(CRYPTO_ALGO algo, CHAINING_MODE chaining, - const unsigned char* key, unsigned int keyLength, - const unsigned char* iv, unsigned int ivLength, - const unsigned char* dataIn, unsigned int dataInLength, - unsigned char* dataOut, unsigned int* dataOutLength); + virtual void AESEncrypt(CHAINING_MODE chaining, + const unsigned char* key, unsigned int keyLength, + const unsigned char* iv, unsigned int ivLength, + const unsigned char* dataIn, unsigned int dataInLength, + unsigned char* dataOut, unsigned int* dataOutLength); - virtual void* encryptInit(CRYPTO_ALGO algo, CHAINING_MODE chaining, - const unsigned char* key, unsigned int keyLength, - const unsigned char* iv=0, unsigned int ivLength=0); + virtual void* AESEncryptInit(CHAINING_MODE chaining, + const unsigned char* key, unsigned int keyLength, + const unsigned char* iv=0, unsigned int ivLength=0); - virtual void encryptUpdate(void* handler, const unsigned char* dataIn, unsigned int dataInLength, - unsigned char* dataOut, unsigned int* dataOutLength); - virtual void encryptFinalize(void* handler, unsigned char* dataOut, unsigned int* dataOutLength); + virtual void AESEncryptUpdate(void* handler, const unsigned char* dataIn, unsigned int dataInLength, + unsigned char* dataOut, unsigned int* dataOutLength); + virtual void AESEncryptFinalize(void* handler, unsigned char* dataOut, unsigned int* dataOutLength); - virtual void decrypt(CRYPTO_ALGO algo, CHAINING_MODE chaining, - const unsigned char* key, unsigned int keyLength, - const unsigned char* iv, unsigned int ivLength, - const unsigned char* dataIn, unsigned int dataInLength, - unsigned char* dataOut, unsigned int* dataOutLength); + virtual void AESDecrypt(CHAINING_MODE chaining, + const unsigned char* key, unsigned int keyLength, + const unsigned char* iv, unsigned int ivLength, + const unsigned char* dataIn, unsigned int dataInLength, + unsigned char* dataOut, unsigned int* dataOutLength); - virtual void* decryptInit(CRYPTO_ALGO algo, CHAINING_MODE chaining, - const unsigned char* key, unsigned int keyLength, - const unsigned char* iv=0, unsigned int ivLength=0); + virtual void* AESDecryptInit(CHAINING_MODE chaining, + const unsigned char* key, unsigned int keyLength, + const unsigned char* iv=0, unsigned int ivLength=0); - virtual void decryptUpdate(void* handler, const unsigned char* dataIn, unsigned int dataInLength, - unsigned char* dataOut, unsigned int* dataOutLength); - virtual void decryptFinalize(void* handler, unsigned char* dataOut, unsigned int* dataOutLength); + virtual void AESDecryptUpdate(void* handler, const unsigned char* dataIn, unsigned int dataInLength, + unsigned char* dataOut, unsigned int* dataOutLength); + virtual void AESDecryptFinalize(void* handler, unsigned char* dataOut, unsigned int* dataOutLength); /* ZIP Interface */ virtual void* zipOpen(const std::string& path); - - virtual void zipReadFile(void* handler, const std::string& path, gourou::ByteArray& result, bool decompress=true); - - virtual void zipWriteFile(void* handler, const std::string& path, gourou::ByteArray& content); - - virtual void zipDeleteFile(void* handler, const std::string& path); - - virtual void zipClose(void* handler); - - virtual void inflate(gourou::ByteArray& data, gourou::ByteArray& result, - int wbits=-15); - virtual void deflate(gourou::ByteArray& data, gourou::ByteArray& result, - int wbits=-15, int compressionLevel=8); - -private: - - void padWithPKCS1(unsigned char* out, unsigned int outLength, - const unsigned char* in, unsigned int inLength); - void padWithPKCS1Type2(unsigned char* out, unsigned int outLength, - const unsigned char* in, unsigned int inLength); - -#if OPENSSL_VERSION_MAJOR >= 3 - OSSL_PROVIDER *legacy, *deflt; -#else - void *legacy, *deflt; -#endif - - char cookiejar[64]; + virtual std::string zipReadFile(void* handler, const std::string& path); + + virtual void zipWriteFile(void* handler, const std::string& path, const std::string& content); + + virtual void zipDeleteFile(void* handler, const std::string& path); + + virtual void zipClose(void* handler); + }; #endif diff --git a/utils/launcher.cpp b/utils/launcher.cpp deleted file mode 100644 index 6b29f78..0000000 --- a/utils/launcher.cpp +++ /dev/null @@ -1,40 +0,0 @@ -#include -#include -#include -#include - -#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; -} diff --git a/utils/man/acsmdownloader.1 b/utils/man/acsmdownloader.1 deleted file mode 100644 index 32fc08a..0000000 --- a/utils/man/acsmdownloader.1 +++ /dev/null @@ -1,61 +0,0 @@ -.\" 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 ) -.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//.config/adept -.IP -* Current directory -.IP -* .adept -.IP -* adobe\-digital\-editions directory -.IP -* .adobe\-digital\-editions directory diff --git a/utils/man/adept_activate.1 b/utils/man/adept_activate.1 deleted file mode 100644 index 1516f38..0000000 --- a/utils/man/adept_activate.1 +++ /dev/null @@ -1,67 +0,0 @@ -.\" 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 diff --git a/utils/man/adept_loan_mgt.1 b/utils/man/adept_loan_mgt.1 deleted file mode 100644 index 619b600..0000000 --- a/utils/man/adept_loan_mgt.1 +++ /dev/null @@ -1,46 +0,0 @@ -.\" 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//.config/adept -.IP -* Current directory -.IP -* .adept -.IP -* adobe\-digital\-editions directory -.IP -* .adobe\-digital\-editions directory diff --git a/utils/man/adept_remove.1 b/utils/man/adept_remove.1 deleted file mode 100644 index f1d446f..0000000 --- a/utils/man/adept_remove.1 +++ /dev/null @@ -1,55 +0,0 @@ -.\" 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//.config/adept -.IP -* Current directory -.IP -* .adept -.IP -* adobe\-digital\-editions directory -.IP -* .adobe\-digital\-editions directory diff --git a/utils/utils_common.cpp b/utils/utils_common.cpp deleted file mode 100644 index 61fce6d..0000000 --- a/utils/utils_common.cpp +++ /dev/null @@ -1,164 +0,0 @@ -/* - Copyright (c) 2022, Grégory Soutadé - - All rights reserved. - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * Neither the name of the copyright holder nor the - names of its contributors may be used to endorse or promote products - derived from this software without specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY - EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS BE LIABLE FOR ANY - DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND - ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -#include -#include -#include "utils_common.h" - -static const char* defaultDirs[] = { - ".adept/", - "./adobe-digital-editions/", - "./.adobe-digital-editions/" -}; - -void version(void) -{ - std::cout << "Current libgourou version : " << gourou::DRMProcessor::VERSION << std::endl ; -} - -bool pathExists(const char* path) -{ - struct stat _stat; - int ret = stat(path, &_stat); - - return (ret == 0); -} - -const char* findFile(const char* filename, bool inDefaultDirs) -{ - std::string path; - - const char* adeptDir = getenv("ADEPT_DIR"); - if (adeptDir && adeptDir[0]) - { - path = adeptDir + std::string("/") + filename; - if (pathExists(path.c_str())) - return strdup(path.c_str()); - } - - path = gourou::DRMProcessor::getDefaultAdeptDir() + filename; - if (pathExists(path.c_str())) - return strdup(path.c_str()); - - if (pathExists(filename)) - return strdup(filename); - - if (!inDefaultDirs) return 0; - - for (int i=0; i<(int)ARRAY_SIZE(defaultDirs); i++) - { - path = std::string(defaultDirs[i]) + filename; - if (pathExists(path.c_str())) - return strdup(path.c_str()); - } - - return 0; -} - -// https://stackoverflow.com/questions/2336242/recursive-mkdir-system-call-on-unix -void mkpath(const char *dir) -{ - char tmp[PATH_MAX]; - char *p = NULL; - size_t len; - - snprintf(tmp, sizeof(tmp),"%s",dir); - len = strlen(tmp); - if (tmp[len - 1] == '/') - tmp[len - 1] = 0; - for (p = tmp + 1; *p; p++) - if (*p == '/') { - *p = 0; - mkdir(tmp, S_IRWXU); - *p = '/'; - } - mkdir(tmp, S_IRWXU); -} - -void fileCopy(const char* in, const char* out) -{ - char buffer[4096], *_buffer; - int ret, ret2, fdIn, fdOut; - - fdIn = open(in, O_RDONLY); - - if (!fdIn) - EXCEPTION(gourou::CLIENT_FILE_ERROR, "Unable to open " << in); - - fdOut = gourou::createNewFile(out); - - if (!fdOut) - { - close (fdIn); - EXCEPTION(gourou::CLIENT_FILE_ERROR, "Unable to open " << out); - } - - while (true) - { - ret = ::read(fdIn, buffer, sizeof(buffer)); - if (ret <= 0) - break; - do - { - _buffer = buffer; - ret2 = ::write(fdOut, _buffer, ret); - if (ret2 >= 0) - { - ret -= ret2; - _buffer += ret2; - } - else - { - EXCEPTION(gourou::CLIENT_FILE_ERROR, "Error writing " << out); - } - } while (ret); - } - - close (fdIn); - close (fdOut); -} - -void createPath(const char* filename) -{ - char* basepath = strdup(filename); - char* outputDir = dirname(basepath); - if (outputDir && !pathExists(outputDir)) - mkpath(outputDir); - free(basepath); -} diff --git a/utils/utils_common.h b/utils/utils_common.h deleted file mode 100644 index 519b8aa..0000000 --- a/utils/utils_common.h +++ /dev/null @@ -1,72 +0,0 @@ -/* - Copyright (c) 2022, Grégory Soutadé - - All rights reserved. - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * Neither the name of the copyright holder nor the - names of its contributors may be used to endorse or promote products - derived from this software without specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY - EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS BE LIABLE FOR ANY - DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND - ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#ifndef _UTILS_COMMON_H_ -#define _UTILS_COMMON_H_ - -#define LOANS_DIR "loans/" -#define ID_HASH_SIZE 16 - -#define ARRAY_SIZE(arr) (sizeof(arr)/sizeof(arr[0])) - -/** - * @brief Display libgourou version - */ -void version(void); - -/** - * @brief Find a given filename in current directory and/or in default directories - * - * @param filename Filename to search - * @param inDefaultDirs Search is default directories or not - * - * @return A copy of full path - */ -const char* findFile(const char* filename, bool inDefaultDirs=true); - -/** - * @brief Does the file (or directory) exists - */ -bool pathExists(const char* path); - -/** - * @brief Recursively created dir - */ -void mkpath(const char *dir); - -/** - * @brief Copy file in into file out - */ -void fileCopy(const char* in, const char* out); - -/** - * @brief Create intermediate directories if it does not exists - */ -void createPath(const char* filename); - -#endif