21 Commits
v0.7.1 ... v0.8

Author SHA1 Message Date
Grégory Soutadé
6e3958f09e Compute over encrypted key also for PDF files 2022-09-04 09:25:06 +02:00
Grégory Soutadé
2dbd4cc343 Update version 2022-08-29 15:19:23 +02:00
Grégory Soutadé
e28dc39a68 Make DRMProcessorClient API more consistent 2022-08-29 15:19:23 +02:00
Grégory Soutadé
7b8c7acbad Compute first pass for encryptedKey if keyType attribute is set 2022-08-29 11:57:33 +02:00
Grégory Soutadé
56b3231f92 Add dumpBuffer() in libgourou_common 2022-08-29 11:56:47 +02:00
Grégory Soutadé
7084fb7025 Add fromHex() static function to ByteArray 2022-08-29 11:56:47 +02:00
Grégory Soutadé
7f5b787cb9 Add launcher util for AppImage 2022-08-29 11:56:47 +02:00
Grégory Soutadé
086e9b0610 Update version 2022-08-29 11:56:47 +02:00
Grégory Soutadé
600535d52c Utils: Migration to OpenSSL3 2022-08-29 11:56:47 +02:00
Grégory Soutadé
57c3a58994 Add STATIC_NONCE option for build (developper mode) 2022-08-10 21:34:30 +02:00
Grégory Soutadé
210b265693 Forward DEBUG flag in Makefile 2022-08-10 21:34:30 +02:00
Grégory Soutadé
33bb983283 Change log levels names to avoid collisions 2022-08-10 21:34:30 +02:00
Grégory Soutadé
3c73b8ccb3 Update .gitignore
Patch from Nguyễn Gia Phong
2022-07-03 09:22:06 +02:00
Grégory Soutadé
4acf401031 Don't clone base64 repository at first build, use a static version of Base64.h (not modified since many years)
Patch from Nguyễn Gia Phong
2022-07-03 09:20:05 +02:00
Grégory Soutadé
7666d2a241 Update README 2022-06-12 15:03:14 +02:00
Grégory Soutadé
201ec69b11 Add scripts/update_lib.sh 2022-06-12 15:03:14 +02:00
Grégory Soutadé
5e018ddbd8 Update version 2022-06-08 12:24:38 +02:00
Grégory Soutadé
81563056e0 Update README 2022-06-08 12:24:38 +02:00
Grégory Soutadé
22880c71c6 Update Makefile to support separated OpenSSL3 compilation 2022-06-08 12:24:38 +02:00
Grégory Soutadé
4f288f4e24 Add support for OpenSSL 3 2022-06-05 15:29:20 +02:00
Grégory Soutadé
3d4e6e3918 Look for <loan> element in <permissions> node in addition to <loanToken> one 2022-06-05 13:51:57 +02:00
22 changed files with 817 additions and 276 deletions

1
.gitignore vendored
View File

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

View File

@@ -1,10 +1,9 @@
AR ?= $(CROSS)ar AR ?= $(CROSS)ar
CXX ?= $(CROSS)g++ CXX ?= $(CROSS)g++
UPDFPARSERLIB = ./lib/updfparser/libupdfparser.a UPDFPARSERLIB = ./lib/updfparser/libupdfparser.a
CXXFLAGS=-Wall -fPIC -I./include -I./lib -I./lib/pugixml/src/ -I./lib/updfparser/include CXXFLAGS=-Wall -fPIC -I./include -I./lib/pugixml/src/ -I./lib/updfparser/include
LDFLAGS = $(UPDFPARSERLIB) LDFLAGS = $(UPDFPARSERLIB)
BUILD_STATIC ?= 0 BUILD_STATIC ?= 0
@@ -24,11 +23,15 @@ endif
ifneq ($(DEBUG),) ifneq ($(DEBUG),)
CXXFLAGS += -ggdb -O0 CXXFLAGS += -ggdb -O0 -DDEBUG
else else
CXXFLAGS += -O2 CXXFLAGS += -O2
endif endif
ifneq ($(STATIC_NONCE),)
CXXFLAGS += -DSTATIC_NONCE=1
endif
SRCDIR := src SRCDIR := src
INCDIR := inc INCDIR := inc
BUILDDIR := obj BUILDDIR := obj
@@ -45,6 +48,9 @@ lib:
mkdir lib mkdir lib
./scripts/setup.sh ./scripts/setup.sh
update_lib:
./scripts/update_lib.sh
obj: obj:
mkdir obj mkdir obj

View File

@@ -35,8 +35,16 @@ Dependencies
For libgourou : For libgourou :
_externals_ :
* None * None
_internals_ :
* PugiXML
* Base64
* uPDFParser
For utils : For utils :
* libcurl * libcurl
@@ -44,12 +52,18 @@ For utils :
* libzip * libzip
Internal libraries are automatically fetched and statically compiled during the first run.
When you update libgourou's repository, **don't forget to update internal libraries** with :
make update_lib
Compilation Compilation
----------- -----------
Use _make_ command Use _make_ command
make [CROSS=XXX] [DEBUG=(0*|1)] [STATIC_UTILS=(0*|1)] [BUILD_UTILS=(0|1*)] [BUILD_STATIC=(0*|1)] [BUILD_SHARED=(0|1*)] make [CROSS=XXX] [DEBUG=(0*|1)] [STATIC_UTILS=(0*|1)] [BUILD_UTILS=(0|1*)] [BUILD_STATIC=(0*|1)] [BUILD_SHARED=(0|1*)] [all*|clean|ultraclean|build_utils]
CROSS can define a cross compiler prefix (ie arm-linux-gnueabihf-) CROSS can define a cross compiler prefix (ie arm-linux-gnueabihf-)
@@ -104,6 +118,11 @@ To return a loaned book :
You can get utils full options description with -h or --help switch You can get utils full options description with -h or --help switch
Docker
------
A docker image (by bcliang) is available at [https://github.com/bcliang/docker-libgourou/](https://github.com/bcliang/docker-libgourou/)
Copyright Copyright
--------- ---------
@@ -111,7 +130,6 @@ Copyright
Grégory Soutadé Grégory Soutadé
License License
------- -------
@@ -120,7 +138,6 @@ libgourou : LGPL v3 or later
utils : BSD utils : BSD
Special thanks Special thanks
-------------- --------------

124
include/Base64.h Normal file
View File

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

View File

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

View File

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

View File

@@ -40,7 +40,7 @@
#define ACS_SERVER "http://adeactivate.adobe.com/adept" #define ACS_SERVER "http://adeactivate.adobe.com/adept"
#endif #endif
#define LIBGOUROU_VERSION "0.7.1" #define LIBGOUROU_VERSION "0.8"
namespace gourou namespace gourou
{ {
@@ -231,6 +231,7 @@ namespace gourou
void buildSignInRequest(pugi::xml_document& signInRequest, const std::string& adobeID, const std::string& adobePassword, const std::string& authenticationCertificate); void buildSignInRequest(pugi::xml_document& signInRequest, const std::string& adobeID, const std::string& adobePassword, const std::string& authenticationCertificate);
void fetchLicenseServiceCertificate(const std::string& licenseURL, void fetchLicenseServiceCertificate(const std::string& licenseURL,
const std::string& operatorURL); const std::string& operatorURL);
std::string encryptedKeyFirstPass(pugi::xml_document& rightsDoc, const std::string& encryptedKey, const std::string& keyType, ITEM_TYPE type);
void decryptADEPTKey(const std::string& encryptedKey, unsigned char* decryptedKey); void decryptADEPTKey(const std::string& encryptedKey, unsigned char* decryptedKey);
void removeEPubDRM(const std::string& filenameIn, const std::string& filenameOut, const unsigned char* encryptionKey, unsigned encryptionKeySize); void removeEPubDRM(const std::string& filenameIn, const std::string& filenameOut, const unsigned char* encryptionKey, unsigned encryptionKeySize);
void generatePDFObjectKey(int version, void generatePDFObjectKey(int version,

View File

@@ -106,6 +106,7 @@ namespace gourou
CLIENT_INVALID_PKCS12, CLIENT_INVALID_PKCS12,
CLIENT_INVALID_CERTIFICATE, CLIENT_INVALID_CERTIFICATE,
CLIENT_NO_PRIV_KEY, CLIENT_NO_PRIV_KEY,
CLIENT_NO_PUB_KEY,
CLIENT_RSA_ERROR, CLIENT_RSA_ERROR,
CLIENT_BAD_CHAINING, CLIENT_BAD_CHAINING,
CLIENT_BAD_KEY_SIZE, CLIENT_BAD_KEY_SIZE,
@@ -114,7 +115,10 @@ namespace gourou
CLIENT_GENERIC_EXCEPTION, CLIENT_GENERIC_EXCEPTION,
CLIENT_NETWORK_ERROR, CLIENT_NETWORK_ERROR,
CLIENT_INVALID_PKCS8, CLIENT_INVALID_PKCS8,
CLIENT_FILE_ERROR CLIENT_FILE_ERROR,
CLIENT_OSSL_ERROR,
CLIENT_CRYPT_ERROR,
CLIENT_DIGEST_ERROR,
}; };
enum DRM_REMOVAL_ERROR { enum DRM_REMOVAL_ERROR {
@@ -124,7 +128,8 @@ namespace gourou
DRM_FORMAT_NOT_SUPPORTED, DRM_FORMAT_NOT_SUPPORTED,
DRM_IN_OUT_EQUALS, DRM_IN_OUT_EQUALS,
DRM_MISSING_PARAMETER, DRM_MISSING_PARAMETER,
DRM_INVALID_KEY_SIZE DRM_INVALID_KEY_SIZE,
DRM_ERR_ENCRYPTION_KEY_FP
}; };
/** /**
@@ -139,7 +144,7 @@ namespace gourou
std::stringstream msg; std::stringstream msg;
msg << "Exception code : 0x" << std::setbase(16) << code << std::endl; msg << "Exception code : 0x" << std::setbase(16) << code << std::endl;
msg << "Message : " << message << std::endl; msg << "Message : " << message << std::endl;
if (logLevel >= DEBUG) if (logLevel >= LG_LOG_DEBUG)
msg << "File : " << file << ":" << std::setbase(10) << line << std::endl; msg << "File : " << file << ":" << std::setbase(10) << line << std::endl;
fullmessage = strdup(msg.str().c_str()); fullmessage = strdup(msg.str().c_str());
} }
@@ -223,35 +228,9 @@ namespace gourou
* It can throw an exception if tag does not exists * It can throw an exception if tag does not exists
* or just return an empty value * or just return an empty value
*/ */
static inline std::string extractTextElem(const pugi::xml_document& doc, const char* tagName, bool throwOnNull=true) static inline std::string extractTextElem(const pugi::xml_node& root, const char* tagName, bool throwOnNull=true)
{ {
pugi::xpath_node xpath_node = doc.select_node(tagName); pugi::xpath_node xpath_node = root.select_node(tagName);
if (!xpath_node)
{
if (throwOnNull)
EXCEPTION(GOUROU_TAG_NOT_FOUND, "Tag " << tagName << " not found");
return "";
}
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 = node.value();
return trim(res);
}
static inline std::string extractTextElem(const pugi::xml_node& doc, const char* tagName, bool throwOnNull=true)
{
pugi::xpath_node xpath_node = doc.select_node(tagName);
if (!xpath_node) if (!xpath_node)
{ {
@@ -275,6 +254,37 @@ namespace gourou
return trim(res); return trim(res);
} }
/**
* @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::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_attribute attr = xpath_node.node().attribute(attributeName);
if (!attr)
{
if (throwOnNull)
EXCEPTION(GOUROU_TAG_NOT_FOUND, "Attribute element " << attributeName << " for tag " << tagName << " not found");
return "";
}
std::string res = attr.value();
return trim(res);
}
/** /**
* @brief Append an element to root with a sub text element * @brief Append an element to root with a sub text element
* *
@@ -288,6 +298,29 @@ namespace gourou
node.append_child(pugi::node_pcdata).set_value(value.c_str()); node.append_child(pugi::node_pcdata).set_value(value.c_str());
} }
/**
* Remove "urn:uuid:" prefix and all '-' from uuid
* urn:uuid:9cb786e8-586a-4950-8901-fff8d2ee6025
* ->
* 9cb786e8586a49508901fff8d2ee6025
*/
static inline std::string extractIdFromUUID(const std::string& uuid)
{
unsigned int i = 0;
std::string res;
if (uuid.find("urn:uuid:") == 0)
i = 9;
for(; i<uuid.size(); i++)
{
if (uuid[i] != '-')
res += uuid[i];
}
return res;
}
/** /**
* @brief Open a file descriptor on path. If it already exists and truncate == true, it's truncated * @brief Open a file descriptor on path. If it already exists and truncate == true, it's truncated
* *
@@ -413,6 +446,20 @@ namespace gourou
} }
return 0; return 0;
} }
static inline void dumpBuffer(GOUROU_LOG_LEVEL level, const char* title, const unsigned char* data, unsigned int len)
{
if (gourou::logLevel < level)
return;
printf("%s", title);
for(unsigned int i=0; i<len; i++)
{
if (i && !(i%16)) printf("\n");
printf("%02x ", data[i]);
}
printf("\n");
}
} }
#endif #endif

View File

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

View File

@@ -8,11 +8,6 @@ if [ ! -d lib/pugixml ] ; then
popd popd
fi fi
# Base64
if [ ! -d lib/base64 ] ; then
git clone https://gist.github.com/f0fd86b6c73063283afe550bc5d77594.git lib/base64
fi
# uPDFParser # uPDFParser
if [ ! -d lib/updfparser ] ; then if [ ! -d lib/updfparser ] ; then
git clone git://soutade.fr/updfparser.git lib/updfparser git clone git://soutade.fr/updfparser.git lib/updfparser

18
scripts/update_lib.sh Executable file
View File

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

View File

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

View File

@@ -37,7 +37,7 @@
namespace gourou namespace gourou
{ {
GOUROU_LOG_LEVEL logLevel = WARN; GOUROU_LOG_LEVEL logLevel = LG_LOG_WARN;
const std::string DRMProcessor::VERSION = LIBGOUROU_VERSION; const std::string DRMProcessor::VERSION = LIBGOUROU_VERSION;
DRMProcessor::DRMProcessor(DRMProcessorClient* client):client(client), device(0), user(0) DRMProcessor::DRMProcessor(DRMProcessorClient* client):client(client), device(0), user(0)
@@ -89,7 +89,7 @@ namespace gourou
uint16_t nlength = htons(length); uint16_t nlength = htons(length);
char c; char c;
if (logLevel >= TRACE) if (logLevel >= LG_LOG_TRACE)
printf("%02x %02x ", ((uint8_t*)&nlength)[0], ((uint8_t*)&nlength)[1]); printf("%02x %02x ", ((uint8_t*)&nlength)[0], ((uint8_t*)&nlength)[1]);
client->digestUpdate(sha_ctx, (unsigned char*)&nlength, sizeof(nlength)); client->digestUpdate(sha_ctx, (unsigned char*)&nlength, sizeof(nlength));
@@ -98,17 +98,17 @@ namespace gourou
{ {
c = string[i]; c = string[i];
client->digestUpdate(sha_ctx, (unsigned char*)&c, 1); client->digestUpdate(sha_ctx, (unsigned char*)&c, 1);
if (logLevel >= TRACE) if (logLevel >= LG_LOG_TRACE)
printf("%c", c); printf("%c", c);
} }
if (logLevel >= TRACE) if (logLevel >= LG_LOG_TRACE)
printf("\n"); printf("\n");
} }
void DRMProcessor::pushTag(void* sha_ctx, uint8_t tag) void DRMProcessor::pushTag(void* sha_ctx, uint8_t tag)
{ {
client->digestUpdate(sha_ctx, &tag, sizeof(tag)); client->digestUpdate(sha_ctx, &tag, sizeof(tag));
if (logLevel >= TRACE) if (logLevel >= LG_LOG_TRACE)
printf("%02x ", tag); printf("%02x ", tag);
} }
@@ -226,14 +226,7 @@ namespace gourou
client->digestFinalize(sha_ctx, sha_out); client->digestFinalize(sha_ctx, sha_out);
if (logLevel >= DEBUG) dumpBuffer(gourou::LG_LOG_DEBUG, "\nSHA OUT : ", sha_out, SHA1_LEN);
{
printf("\nSHA OUT : ");
for(int i=0; i<(int)SHA1_LEN; i++)
printf("%02x ", sha_out[i]);
printf("\n");
}
} }
void DRMProcessor::signNode(pugi::xml_node& rootNode) void DRMProcessor::signNode(pugi::xml_node& rootNode)
@@ -252,13 +245,8 @@ namespace gourou
client->RSAPrivateEncrypt(privateRSAKey.data(), privateRSAKey.length(), client->RSAPrivateEncrypt(privateRSAKey.data(), privateRSAKey.length(),
RSAInterface::RSA_KEY_PKCS12, deviceKey.toBase64().data(), RSAInterface::RSA_KEY_PKCS12, deviceKey.toBase64().data(),
sha_out, sizeof(sha_out), res); sha_out, sizeof(sha_out), res);
if (logLevel >= DEBUG)
{ dumpBuffer(gourou::LG_LOG_DEBUG, "Sig : ", res, sizeof(res));
printf("Sig : ");
for(int i=0; i<(int)sizeof(res); i++)
printf("%02x ", res[i]);
printf("\n");
}
std::string signature = ByteArray(res, sizeof(res)).toBase64(); std::string signature = ByteArray(res, sizeof(res)).toBase64();
appendTextElem(rootNode, "adept:signature", signature); appendTextElem(rootNode, "adept:signature", signature);
@@ -282,7 +270,11 @@ namespace gourou
struct timeval tv; struct timeval tv;
gettimeofday(&tv, 0); gettimeofday(&tv, 0);
uint32_t nonce32[2] = {0x6f046000, 0x388a}; uint32_t nonce32[2] = {0x6f046000, 0x388a};
#ifdef STATIC_NONCE
uint64_t bigtime = 0xAA001122BBCCAAULL;
#else
uint64_t bigtime = tv.tv_sec*1000; uint64_t bigtime = tv.tv_sec*1000;
#endif
nonce32[0] += (bigtime & 0xFFFFFFFF) + (tv.tv_usec/1000); nonce32[0] += (bigtime & 0xFFFFFFFF) + (tv.tv_usec/1000);
nonce32[1] += ((bigtime >> 32) & 0xFFFFFFFF); nonce32[1] += ((bigtime >> 32) & 0xFFFFFFFF);
@@ -877,7 +869,7 @@ namespace gourou
// Generate IV in front // Generate IV in front
client->randBytes(encrypted_data, 16); client->randBytes(encrypted_data, 16);
client->Encrypt(CryptoInterface::ALGO_AES, CryptoInterface::CHAIN_CBC, client->encrypt(CryptoInterface::ALGO_AES, CryptoInterface::CHAIN_CBC,
deviceKey, 16, encrypted_data, 16, deviceKey, 16, encrypted_data, 16,
data, len, data, len,
encrypted_data+16, &outLen); encrypted_data+16, &outLen);
@@ -896,7 +888,7 @@ namespace gourou
const unsigned char* deviceKey = device->getDeviceKey(); const unsigned char* deviceKey = device->getDeviceKey();
unsigned char* decrypted_data = new unsigned char[len-16]; unsigned char* decrypted_data = new unsigned char[len-16];
client->Decrypt(CryptoInterface::ALGO_AES, CryptoInterface::CHAIN_CBC, client->decrypt(CryptoInterface::ALGO_AES, CryptoInterface::CHAIN_CBC,
deviceKey, 16, data, 16, deviceKey, 16, data, 16,
data+16, len-16, data+16, len-16,
decrypted_data, &outLen); decrypted_data, &outLen);
@@ -959,22 +951,102 @@ namespace gourou
ByteArray arrayEncryptedKey = ByteArray::fromBase64(encryptedKey); ByteArray arrayEncryptedKey = ByteArray::fromBase64(encryptedKey);
std::string privateKeyData = user->getPrivateLicenseKey(); std::string privateKeyData = user->getPrivateLicenseKey();
ByteArray privateRSAKey = ByteArray::fromBase64(privateKeyData); ByteArray privateRSAKey = ByteArray::fromBase64(privateKeyData);
ByteArray deviceKey(device->getDeviceKey(), Device::DEVICE_KEY_SIZE); dumpBuffer(gourou::LG_LOG_DEBUG, "To decrypt : ", arrayEncryptedKey.data(), arrayEncryptedKey.length());
std::string pkcs12 = user->getPKCS12();
client->RSAPrivateDecrypt(privateRSAKey.data(), privateRSAKey.length(), client->RSAPrivateDecrypt(privateRSAKey.data(), privateRSAKey.length(),
RSAInterface::RSA_KEY_PKCS12, deviceKey.toBase64().data(), RSAInterface::RSA_KEY_PKCS8, "",
arrayEncryptedKey.data(), arrayEncryptedKey.length(), decryptedKey); arrayEncryptedKey.data(), arrayEncryptedKey.length(), decryptedKey);
if (decryptedKey[0] != 0x00 || decryptedKey[1] != 0x02 ||
decryptedKey[RSA_KEY_SIZE-16-1] != 0x00)
EXCEPTION(DRM_ERR_ENCRYPTION_KEY, "Unable to retrieve encryption key");
} }
/**
* RSA Key can be over encrypted with AES128-CBC if keyType attribute is set
* For EPUB, Key = SHA256(keyType)[14:22] || SHA256(keyType)[7:13]
* For PDF, Key = SHA256(keyType)[6:19] || SHA256(keyType)[3:6]
* 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, ITEM_TYPE type)
{
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));
switch(type)
{
case EPUB:
memcpy(key, &digest[14], 9);
memcpy(&key[9], &digest[7], 7);
break;
case PDF:
memcpy(key, &digest[6], 13);
memcpy(&key[13], &digest[3], 3);
break;
}
id = extractTextElem(rightsDoc, "/adept:rights/licenseToken/device");
if (id == "")
EXCEPTION(DRM_ERR_ENCRYPTION_KEY_FP, "Device id not found in rights.xml");
ByteArray deviceId = ByteArray::fromHex(extractIdFromUUID(id));
unsigned char* _deviceId = deviceId.data();
id = extractTextElem(rightsDoc, "/adept:rights/licenseToken/fulfillment");
if (id == "")
EXCEPTION(DRM_ERR_ENCRYPTION_KEY_FP, "Fulfillment id not found in rights.xml");
ByteArray fulfillmentId = ByteArray::fromHex(extractIdFromUUID(id));
unsigned char* _fulfillmentId = fulfillmentId.data();
id = extractTextElem(rightsDoc, "/adept:rights/licenseToken/voucher");
if (id == "")
EXCEPTION(DRM_ERR_ENCRYPTION_KEY_FP, "Voucher id not found in rights.xml");
ByteArray voucherId = ByteArray::fromHex(extractIdFromUUID(id));
unsigned char* _voucherId = voucherId.data();
if (deviceId.size() < sizeof(iv) || fulfillmentId.size() < sizeof(iv) || voucherId.size() < sizeof(iv))
EXCEPTION(DRM_ERR_ENCRYPTION_KEY_FP, "One id has a bad length");
for(unsigned int i=0; i<sizeof(iv); i++)
iv[i] = _deviceId[i] ^ _fulfillmentId[i] ^ _voucherId[i];
ByteArray arrayEncryptedKey = ByteArray::fromBase64(encryptedKey);
dumpBuffer(gourou::LG_LOG_DEBUG, "First pass key : ", key, sizeof(key));
dumpBuffer(gourou::LG_LOG_DEBUG, "First pass IV : ", iv, sizeof(iv));
unsigned char* clearRSAKey = new unsigned char[arrayEncryptedKey.size()];
client->decrypt(CryptoInterface::ALGO_AES, CryptoInterface::CHAIN_CBC,
(const unsigned char*)key, (unsigned int)sizeof(key),
(const unsigned char*)iv, (unsigned int)sizeof(iv),
(const unsigned char*)arrayEncryptedKey.data(), arrayEncryptedKey.size(),
(unsigned char*)clearRSAKey, &dataOutLength);
dumpBuffer(gourou::LG_LOG_DEBUG, "\nDecrypted key : ", clearRSAKey, dataOutLength);
/* Last block could be 0x10*16 which is OpenSSL padding, remove it if it's the case */
bool skipLastLine = true;
for(unsigned int i=dataOutLength-16; i<dataOutLength; i++)
{
if (clearRSAKey[i] != 0x10)
{
skipLastLine = false;
break;
}
}
ByteArray res(clearRSAKey, (skipLastLine)?dataOutLength-16:dataOutLength);
delete[] clearRSAKey;
return res.toBase64();
}
void DRMProcessor::removeEPubDRM(const std::string& filenameIn, const std::string& filenameOut, void DRMProcessor::removeEPubDRM(const std::string& filenameIn, const std::string& filenameOut,
const unsigned char* encryptionKey, unsigned encryptionKeySize) const unsigned char* encryptionKey, unsigned encryptionKeySize)
@@ -991,7 +1063,20 @@ namespace gourou
unsigned char decryptedKey[RSA_KEY_SIZE]; unsigned char decryptedKey[RSA_KEY_SIZE];
if (!encryptionKey) if (!encryptionKey)
{
std::string keyType = extractTextAttribute(rightsDoc, "/adept:rights/licenseToken/encryptedKey", "keyType", false);
if (keyType != "")
encryptedKey = encryptedKeyFirstPass(rightsDoc, encryptedKey, keyType, EPUB);
decryptADEPTKey(encryptedKey, decryptedKey); decryptADEPTKey(encryptedKey, decryptedKey);
dumpBuffer(gourou::LG_LOG_DEBUG, "Decrypted : ", decryptedKey, RSA_KEY_SIZE);
if (decryptedKey[0] != 0x00 || decryptedKey[1] != 0x02 ||
decryptedKey[RSA_KEY_SIZE-16-1] != 0x00)
EXCEPTION(DRM_ERR_ENCRYPTION_KEY, "Unable to retrieve encryption key");
}
else else
{ {
GOUROU_LOG(DEBUG, "Use provided encryption key"); GOUROU_LOG(DEBUG, "Use provided encryption key");
@@ -1037,7 +1122,7 @@ namespace gourou
gourou::ByteArray inflateData(true); gourou::ByteArray inflateData(true);
unsigned int dataOutLength; unsigned int dataOutLength;
client->Decrypt(CryptoInterface::ALGO_AES, CryptoInterface::CHAIN_CBC, client->decrypt(CryptoInterface::ALGO_AES, CryptoInterface::CHAIN_CBC,
decryptedKey+sizeof(decryptedKey)-16, 16, /* Key */ decryptedKey+sizeof(decryptedKey)-16, 16, /* Key */
_data, 16, /* IV */ _data, 16, /* IV */
&_data[16], zipData.length()-16, &_data[16], zipData.length()-16,
@@ -1173,7 +1258,20 @@ namespace gourou
std::string encryptedKey = extractTextElem(rightsDoc, "/adept:rights/licenseToken/encryptedKey"); std::string encryptedKey = extractTextElem(rightsDoc, "/adept:rights/licenseToken/encryptedKey");
if (!encryptionKey) if (!encryptionKey)
{
std::string keyType = extractTextAttribute(rightsDoc, "/adept:rights/licenseToken/encryptedKey", "keyType", false);
if (keyType != "")
encryptedKey = encryptedKeyFirstPass(rightsDoc, encryptedKey, keyType, PDF);
decryptADEPTKey(encryptedKey, decryptedKey); decryptADEPTKey(encryptedKey, decryptedKey);
dumpBuffer(gourou::LG_LOG_DEBUG, "Decrypted : ", decryptedKey, RSA_KEY_SIZE);
if (decryptedKey[0] != 0x00 || decryptedKey[1] != 0x02 ||
decryptedKey[RSA_KEY_SIZE-16-1] != 0x00)
EXCEPTION(DRM_ERR_ENCRYPTION_KEY, "Unable to retrieve encryption key");
}
else else
{ {
GOUROU_LOG(DEBUG, "Use provided encryption key"); GOUROU_LOG(DEBUG, "Use provided encryption key");
@@ -1241,8 +1339,8 @@ namespace gourou
GOUROU_LOG(DEBUG, "Decrypt string " << dictIt->first << " " << dataLength); GOUROU_LOG(DEBUG, "Decrypt string " << dictIt->first << " " << dataLength);
client->Decrypt(CryptoInterface::ALGO_RC4, CryptoInterface::CHAIN_ECB, client->decrypt(CryptoInterface::ALGO_RC4, CryptoInterface::CHAIN_ECB,
tmpKey, 16, /* Key */ tmpKey, sizeof(tmpKey), /* Key */
NULL, 0, /* IV */ NULL, 0, /* IV */
encryptedData, dataLength, encryptedData, dataLength,
clearData, &dataOutLength); clearData, &dataOutLength);
@@ -1274,8 +1372,8 @@ namespace gourou
GOUROU_LOG(DEBUG, "Decrypt stream id " << object->objectId() << ", size " << stream->dataLength()); GOUROU_LOG(DEBUG, "Decrypt stream id " << object->objectId() << ", size " << stream->dataLength());
client->Decrypt(CryptoInterface::ALGO_RC4, CryptoInterface::CHAIN_ECB, client->decrypt(CryptoInterface::ALGO_RC4, CryptoInterface::CHAIN_ECB,
tmpKey, 16, /* Key */ tmpKey, sizeof(tmpKey), /* Key */
NULL, 0, /* IV */ NULL, 0, /* IV */
encryptedData, dataLength, encryptedData, dataLength,
clearData, &dataOutLength); clearData, &dataOutLength);

View File

@@ -31,10 +31,23 @@ namespace gourou
node = doc.select_node("/envelope/loanToken/loan").node(); node = doc.select_node("/envelope/loanToken/loan").node();
if (!node) if (node)
EXCEPTION(FFI_INVALID_LOAN_TOKEN, "No loanToken/loan element in document");
properties["id"] = node.first_child().value(); properties["id"] = node.first_child().value();
else
{
node = doc.select_node("/envelope/fulfillmentResult/resourceItemInfo/licenseToken/permissions/display/loan").node();
if (node)
properties["id"] = node.first_child().value();
else
{
node = doc.select_node("/envelope/fulfillmentResult/resourceItemInfo/licenseToken/permissions/play/loan").node();
if (node)
properties["id"] = node.first_child().value();
else
EXCEPTION(FFI_INVALID_LOAN_TOKEN, "No loanToken/loan element in document");
}
}
node = doc.select_node("/envelope/loanToken/operatorURL").node(); node = doc.select_node("/envelope/loanToken/operatorURL").node();
@@ -50,6 +63,7 @@ namespace gourou
else else
{ {
node = doc.select_node("/envelope/fulfillmentResult/resourceItemInfo/licenseToken/permissions/play/until").node(); node = doc.select_node("/envelope/fulfillmentResult/resourceItemInfo/licenseToken/permissions/play/until").node();
if (node) if (node)
properties["validity"] = node.first_child().value(); properties["validity"] = node.first_child().value();
else else

View File

@@ -1,10 +1,10 @@
TARGETS=acsmdownloader adept_activate adept_remove adept_loan_mgt TARGETS=acsmdownloader adept_activate adept_remove adept_loan_mgt launcher
CXXFLAGS=-Wall -fPIC -I$(ROOT)/include -I$(ROOT)/lib/pugixml/src/ CXXFLAGS=-Wall -fPIC -I$(ROOT)/include -I$(ROOT)/lib/pugixml/src/
STATIC_DEP= STATIC_DEP=
LDFLAGS=-L$(ROOT) -lcrypto -lzip -lz -lcurl LDFLAGS += -L$(ROOT) -lcrypto -lzip -lz -lcurl
ifneq ($(STATIC_UTILS),) ifneq ($(STATIC_UTILS),)
STATIC_DEP = $(ROOT)/libgourou.a STATIC_DEP = $(ROOT)/libgourou.a
@@ -13,11 +13,12 @@ LDFLAGS += -lgourou
endif endif
ifneq ($(DEBUG),) ifneq ($(DEBUG),)
CXXFLAGS += -ggdb -O0 CXXFLAGS += -ggdb -O0 -DDEBUG
else else
CXXFLAGS += -O2 CXXFLAGS += -O2
endif endif
COMMON_DEPS = drmprocessorclientimpl.cpp utils_common.cpp COMMON_DEPS = drmprocessorclientimpl.cpp utils_common.cpp
COMMON_OBJECTS = $(COMMON_DEPS:.cpp=.o) COMMON_OBJECTS = $(COMMON_DEPS:.cpp=.o)
COMMON_LIB = utils.a COMMON_LIB = utils.a
@@ -28,19 +29,10 @@ ${COMMON_LIB}: ${COMMON_DEPS} ${STATIC_DEP}
$(CXX) $(CXXFLAGS) ${COMMON_DEPS} $(LDFLAGS) -c $(CXX) $(CXXFLAGS) ${COMMON_DEPS} $(LDFLAGS) -c
$(AR) crs $@ ${COMMON_OBJECTS} $(STATIC_DEP) $(AR) crs $@ ${COMMON_OBJECTS} $(STATIC_DEP)
acsmdownloader: acsmdownloader.cpp ${COMMON_LIB} %: %.cpp ${COMMON_LIB}
$(CXX) $(CXXFLAGS) $^ $(LDFLAGS) -o $@
adept_activate: adept_activate.cpp ${COMMON_LIB}
$(CXX) $(CXXFLAGS) $^ $(LDFLAGS) -o $@
adept_remove: adept_remove.cpp ${COMMON_LIB}
$(CXX) $(CXXFLAGS) $^ $(LDFLAGS) -o $@
adept_loan_mgt: adept_loan_mgt.cpp ${COMMON_LIB}
$(CXX) $(CXXFLAGS) $^ $(LDFLAGS) -o $@ $(CXX) $(CXXFLAGS) $^ $(LDFLAGS) -o $@
clean: clean:
rm -f $(TARGETS) rm -f $(TARGETS) $(COMMON_LIB)
ultraclean: clean ultraclean: clean

View File

@@ -184,7 +184,7 @@ static void usage(const char* cmd)
{ {
std::cout << "Download EPUB file from ACSM request file" << 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] [(-k|--device-key-file) devicesalt] [(-O|--output-dir) dir] [(-o|--output-file) output(.epub|.pdf|.der)] [(-r|--resume)] [(-v|--verbose)] [(-h|--help)] (-f|--acsm-file) file.acsm|(-e|--export-private-key)" << std::endl << std::endl; std::cout << "Usage: " << basename((char*)cmd) << " [(-d|--device-file) device.xml] [(-a|--activation-file) activation.xml] [(-k|--device-key-file) devicesalt] [(-O|--output-dir) dir] [(-o|--output-file) output(.epub|.pdf|.der)] [(-r|--resume)] [(-v|--verbose)] [(-h|--help)] (-f|--acsm-file) file.acsm|(-e|--export-private-key)" << std::endl << std::endl;
std::cout << " " << "-d|--device-file" << "\t" << "device.xml file from eReader" << std::endl; std::cout << " " << "-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 << " " << "-a|--activation-file" << "\t" << "activation.xml file from eReader" << std::endl;

View File

@@ -126,7 +126,7 @@ static void usage(const char* cmd)
{ {
std::cout << "Create new device files used by ADEPT DRM" << std::endl; std::cout << "Create new device files used by ADEPT DRM" << std::endl;
std::cout << "Usage: " << cmd << " (-a|--anonymous) | ( (-u|--username) username [(-p|--password) password] ) [(-O|--output-dir) dir] [(-r|--random-serial)] [(-v|--verbose)] [(-h|--help)]" << std::endl << std::endl; std::cout << "Usage: " << basename((char*)cmd) << " (-a|--anonymous) | ( (-u|--username) username [(-p|--password) password] ) [(-O|--output-dir) dir] [(-r|--random-serial)] [(-v|--verbose)] [(-h|--help)]" << std::endl << std::endl;
std::cout << " " << "-a|--anonymous" << "\t" << "Anonymous account, no need for username/password (Use it only with a DRM removal software)" << std::endl; std::cout << " " << "-a|--anonymous" << "\t" << "Anonymous account, no need for username/password (Use it only with a DRM removal software)" << std::endl;
std::cout << " " << "-u|--username" << "\t\t" << "AdobeID username (ie adobe.com email account)" << std::endl; std::cout << " " << "-u|--username" << "\t\t" << "AdobeID username (ie adobe.com email account)" << std::endl;

View File

@@ -336,7 +336,7 @@ static void usage(const char* cmd)
{ {
std::cout << "Manage loaned books" << std::endl; std::cout << "Manage loaned books" << std::endl;
std::cout << "Usage: " << cmd << " [(-d|--activation-dir) dir] (-l|--list)|(-D|--delete loanID)|(-R|--delete loanID) [(-v|--verbose)] [(-h|--help)]" << std::endl << std::endl; std::cout << "Usage: " << basename((char*)cmd) << " [(-d|--activation-dir) dir] (-l|--list)|(-D|--delete loanID)|(-R|--delete loanID) [(-v|--verbose)] [(-h|--help)]" << std::endl << std::endl;
std::cout << " " << "-d|--activation-dir" << "\t" << "Directory of device.xml/activation.xml and device key" << std::endl; std::cout << " " << "-d|--activation-dir" << "\t" << "Directory of device.xml/activation.xml and device key" << std::endl;
std::cout << " " << "-l|--list" << "\t\t" << "List all loaned books" << std::endl; std::cout << " " << "-l|--list" << "\t\t" << "List all loaned books" << std::endl;

View File

@@ -143,7 +143,7 @@ static void usage(const char* cmd)
{ {
std::cout << "Remove ADEPT DRM (from Adobe) of EPUB/PDF file" << std::endl; std::cout << "Remove ADEPT DRM (from Adobe) of EPUB/PDF file" << std::endl;
std::cout << "Usage: " << cmd << " [(-d|--device-file) device.xml] [(-a|--activation-file) activation.xml] [(-k|--device-key-file) devicesalt] [(-O|--output-dir) dir] [(-o|--output-file) output(.epub|.pdf|.der)] [(-v|--verbose)] [(-h|--help)] (-f|--input-file) file(.epub|pdf)" << std::endl << std::endl; std::cout << "Usage: " << basename((char*)cmd) << " [(-d|--device-file) device.xml] [(-a|--activation-file) activation.xml] [(-k|--device-key-file) devicesalt] [(-O|--output-dir) dir] [(-o|--output-file) output(.epub|.pdf|.der)] [(-v|--verbose)] [(-h|--help)] (-f|--input-file) file(.epub|pdf)" << std::endl << std::endl;
std::cout << " " << "-d|--device-file" << "\t" << "device.xml file from eReader" << 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 << " " << "-a|--activation-file" << "\t" << "activation.xml file from eReader" << std::endl;

View File

@@ -31,10 +31,14 @@
#include <cctype> #include <cctype>
#include <locale> #include <locale>
#define OPENSSL_NO_DEPRECATED 1
#include <openssl/rand.h> #include <openssl/rand.h>
#include <openssl/pkcs12.h> #include <openssl/pkcs12.h>
#include <openssl/evp.h> #include <openssl/evp.h>
#include <openssl/err.h> #include <openssl/err.h>
#include <openssl/rsa.h>
#include <openssl/bn.h>
#include <curl/curl.h> #include <curl/curl.h>
@@ -42,9 +46,33 @@
#include <zip.h> #include <zip.h>
#include <libgourou_common.h> #include <libgourou_common.h>
#include <libgourou_log.h>
#include "drmprocessorclientimpl.h" #include "drmprocessorclientimpl.h"
DRMProcessorClientImpl::DRMProcessorClientImpl():
legacy(0), deflt(0)
{
#if OPENSSL_VERSION_MAJOR >= 3
legacy = OSSL_PROVIDER_load(NULL, "legacy");
if (!legacy)
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");
#endif
}
DRMProcessorClientImpl::~DRMProcessorClientImpl()
{
#if OPENSSL_VERSION_MAJOR >= 3
if (legacy)
OSSL_PROVIDER_unload(legacy);
if (deflt)
OSSL_PROVIDER_unload(deflt);
#endif
}
/* Digest interface */ /* Digest interface */
void* DRMProcessorClientImpl::createDigest(const std::string& digestName) void* DRMProcessorClientImpl::createDigest(const std::string& digestName)
{ {
@@ -54,32 +82,32 @@ void* DRMProcessorClientImpl::createDigest(const std::string& digestName)
if (EVP_DigestInit(md_ctx, md) != 1) if (EVP_DigestInit(md_ctx, md) != 1)
{ {
EVP_MD_CTX_free(md_ctx); EVP_MD_CTX_free(md_ctx);
return 0; EXCEPTION(gourou::CLIENT_DIGEST_ERROR, ERR_error_string(ERR_get_error(), NULL));
} }
return md_ctx; return md_ctx;
} }
int DRMProcessorClientImpl::digestUpdate(void* handler, unsigned char* data, unsigned int length) void DRMProcessorClientImpl::digestUpdate(void* handler, unsigned char* data, unsigned int length)
{ {
return (EVP_DigestUpdate((EVP_MD_CTX *)handler, data, length)) ? 0 : -1; if (EVP_DigestUpdate((EVP_MD_CTX *)handler, data, length) != 1)
EXCEPTION(gourou::CLIENT_DIGEST_ERROR, ERR_error_string(ERR_get_error(), NULL));
} }
int DRMProcessorClientImpl::digestFinalize(void* handler, unsigned char* digestOut) void DRMProcessorClientImpl::digestFinalize(void* handler, unsigned char* digestOut)
{ {
int res = EVP_DigestFinal((EVP_MD_CTX *)handler, digestOut, NULL); int res = EVP_DigestFinal((EVP_MD_CTX *)handler, digestOut, NULL);
EVP_MD_CTX_free((EVP_MD_CTX *)handler); EVP_MD_CTX_free((EVP_MD_CTX *)handler);
return (res == 1) ? 0 : -1;
if (res <= 0)
EXCEPTION(gourou::CLIENT_DIGEST_ERROR, ERR_error_string(ERR_get_error(), NULL));
} }
int DRMProcessorClientImpl::digest(const std::string& digestName, unsigned char* data, unsigned int length, unsigned char* digestOut) void DRMProcessorClientImpl::digest(const std::string& digestName, unsigned char* data, unsigned int length, unsigned char* digestOut)
{ {
void* handler = createDigest(digestName); void* handler = createDigest(digestName);
if (!handler) digestUpdate(handler, data, length);
return -1; digestFinalize(handler, digestOut);
if (digestUpdate(handler, data, length))
return -1;
return digestFinalize(handler, digestOut);
} }
/* Random interface */ /* Random interface */
@@ -97,7 +125,7 @@ static int downloadProgress(void *clientp, curl_off_t dltotal, curl_off_t dlnow,
curl_off_t ultotal, curl_off_t ulnow) curl_off_t ultotal, curl_off_t ulnow)
{ {
// For "big" files only // For "big" files only
if (dltotal >= DISPLAY_THRESHOLD && gourou::logLevel >= gourou::WARN) if (dltotal >= DISPLAY_THRESHOLD && gourou::logLevel >= gourou::LG_LOG_WARN)
{ {
int percent = 0; int percent = 0;
if (dltotal) if (dltotal)
@@ -147,7 +175,7 @@ static size_t curlHeaders(char *buffer, size_t size, size_t nitems, void *userda
(*responseHeaders)[key] = value; (*responseHeaders)[key] = value;
if (gourou::logLevel >= gourou::DEBUG) if (gourou::logLevel >= gourou::LG_LOG_DEBUG)
std::cout << key << " : " << value << std::endl; std::cout << key << " : " << value << std::endl;
} }
@@ -162,10 +190,10 @@ std::string DRMProcessorClientImpl::sendHTTPRequest(const std::string& URL, cons
if (!responseHeaders) if (!responseHeaders)
responseHeaders = &localHeaders; responseHeaders = &localHeaders;
GOUROU_LOG(gourou::INFO, "Send request to " << URL); GOUROU_LOG(INFO, "Send request to " << URL);
if (POSTData.size()) if (POSTData.size())
{ {
GOUROU_LOG(gourou::DEBUG, "<<< " << std::endl << POSTData); GOUROU_LOG(DEBUG, "<<< " << std::endl << POSTData);
} }
unsigned prevDownloadedBytes; unsigned prevDownloadedBytes;
@@ -175,11 +203,11 @@ std::string DRMProcessorClientImpl::sendHTTPRequest(const std::string& URL, cons
struct stat _stat; struct stat _stat;
if (!fstat(fd, &_stat)) if (!fstat(fd, &_stat))
{ {
GOUROU_LOG(gourou::WARN, "Resume download @ " << _stat.st_size << " bytes"); GOUROU_LOG(WARN, "Resume download @ " << _stat.st_size << " bytes");
downloadedBytes = _stat.st_size; downloadedBytes = _stat.st_size;
} }
else else
GOUROU_LOG(gourou::WARN, "Want to resume, but fstat failed"); GOUROU_LOG(WARN, "Want to resume, but fstat failed");
} }
CURL *curl = curl_easy_init(); CURL *curl = curl_easy_init();
@@ -235,7 +263,7 @@ std::string DRMProcessorClientImpl::sendHTTPRequest(const std::string& URL, cons
// Connexion failed, wait & retry // Connexion failed, wait & retry
if (res == CURLE_COULDNT_CONNECT) if (res == CURLE_COULDNT_CONNECT)
{ {
GOUROU_LOG(gourou::WARN, "\nConnection failed, attempt " << (i+1) << "/" << HTTP_REQ_MAX_RETRY); GOUROU_LOG(WARN, "\nConnection failed, attempt " << (i+1) << "/" << HTTP_REQ_MAX_RETRY);
} }
// Transfer failed but some data has been received // Transfer failed but some data has been received
// --> try again without incrementing tries // --> try again without incrementing tries
@@ -243,11 +271,11 @@ std::string DRMProcessorClientImpl::sendHTTPRequest(const std::string& URL, cons
{ {
if (prevDownloadedBytes != downloadedBytes) if (prevDownloadedBytes != downloadedBytes)
{ {
GOUROU_LOG(gourou::WARN, "\nConnection broken, but data received, try again"); GOUROU_LOG(WARN, "\nConnection broken, but data received, try again");
i--; i--;
} }
else else
GOUROU_LOG(gourou::WARN, "\nConnection broken and no data received, attempt " << (i+1) << "/" << HTTP_REQ_MAX_RETRY); GOUROU_LOG(WARN, "\nConnection broken and no data received, attempt " << (i+1) << "/" << HTTP_REQ_MAX_RETRY);
} }
// Other error --> fail // Other error --> fail
else else
@@ -264,46 +292,79 @@ std::string DRMProcessorClientImpl::sendHTTPRequest(const std::string& URL, cons
EXCEPTION(gourou::CLIENT_NETWORK_ERROR, "Error " << curl_easy_strerror(res)); EXCEPTION(gourou::CLIENT_NETWORK_ERROR, "Error " << curl_easy_strerror(res));
if ((downloadedBytes >= DISPLAY_THRESHOLD || replyData.size() >= DISPLAY_THRESHOLD) && if ((downloadedBytes >= DISPLAY_THRESHOLD || replyData.size() >= DISPLAY_THRESHOLD) &&
gourou::logLevel >= gourou::WARN) gourou::logLevel >= gourou::LG_LOG_WARN)
std::cout << std::endl; std::cout << std::endl;
if ((*responseHeaders)["Content-Type"] == "application/vnd.adobe.adept+xml") if ((*responseHeaders)["Content-Type"] == "application/vnd.adobe.adept+xml")
{ {
GOUROU_LOG(gourou::DEBUG, ">>> " << std::endl << replyData.data()); GOUROU_LOG(DEBUG, ">>> " << std::endl << replyData.data());
} }
return std::string((char*)replyData.data(), replyData.length()); return std::string((char*)replyData.data(), replyData.length());
} }
void DRMProcessorClientImpl::padWithPKCS1(unsigned char* out, unsigned int outLength,
const unsigned char* in, unsigned int inLength)
{
if (outLength < (inLength + 3))
EXCEPTION(gourou::CLIENT_RSA_ERROR, "Not enough space for PKCS1 padding");
/*
PKCS1v5 Padding is :
0x00 0x01 0xff * n 0x00 dataIn
*/
memset(out, 0xFF, outLength);
out[0] = 0x0;
out[1] = 0x1;
out[outLength - inLength - 1] = 0x00;
memcpy(&out[outLength - inLength], in, inLength);
}
void DRMProcessorClientImpl::RSAPrivateEncrypt(const unsigned char* RSAKey, unsigned int RSAKeyLength, void DRMProcessorClientImpl::RSAPrivateEncrypt(const unsigned char* RSAKey, unsigned int RSAKeyLength,
const RSA_KEY_TYPE keyType, const std::string& password, const RSA_KEY_TYPE keyType, const std::string& password,
const unsigned char* data, unsigned dataLength, const unsigned char* data, unsigned dataLength,
unsigned char* res) unsigned char* res)
{ {
PKCS12 * pkcs12; PKCS12 * pkcs12;
EVP_PKEY* pkey; EVP_PKEY_CTX *ctx;
X509* cert; EVP_PKEY* pkey = NULL;
STACK_OF(X509)* ca; size_t outlen;
RSA * rsa; unsigned char* tmp;
int ret;
pkcs12 = d2i_PKCS12(NULL, &RSAKey, RSAKeyLength); pkcs12 = d2i_PKCS12(NULL, &RSAKey, RSAKeyLength);
if (!pkcs12) if (!pkcs12)
EXCEPTION(gourou::CLIENT_INVALID_PKCS12, ERR_error_string(ERR_get_error(), NULL)); EXCEPTION(gourou::CLIENT_INVALID_PKCS12, ERR_error_string(ERR_get_error(), NULL));
PKCS12_parse(pkcs12, password.c_str(), &pkey, &cert, &ca);
rsa = EVP_PKEY_get1_RSA(pkey);
int ret = RSA_private_encrypt(dataLength, data, res, rsa, RSA_PKCS1_PADDING); if (PKCS12_parse(pkcs12, password.c_str(), &pkey, NULL, NULL) <= 0)
EXCEPTION(gourou::CLIENT_INVALID_PKCS12, ERR_error_string(ERR_get_error(), NULL));
if (ret < 0) outlen = EVP_PKEY_get_size(pkey);
ctx = EVP_PKEY_CTX_new(pkey, NULL);
/* Use RSA private key */
if (EVP_PKEY_decrypt_init(ctx) <= 0)
EXCEPTION(gourou::CLIENT_RSA_ERROR, ERR_error_string(ERR_get_error(), NULL)); EXCEPTION(gourou::CLIENT_RSA_ERROR, ERR_error_string(ERR_get_error(), NULL));
if (gourou::logLevel >= gourou::DEBUG) if (EVP_PKEY_CTX_set_rsa_padding(ctx, RSA_NO_PADDING) <= 0)
{ EXCEPTION(gourou::CLIENT_RSA_ERROR, ERR_error_string(ERR_get_error(), NULL));
printf("Encrypted : ");
for(int i=0; i<ret; i++) tmp = (unsigned char*)malloc(outlen);
printf("%02x ", res[i]);
printf("\n"); /* PKCS1 functions are no more exported */
} padWithPKCS1(tmp, outlen, data, dataLength);
ret = EVP_PKEY_decrypt(ctx, res, &outlen, tmp, outlen);
EVP_PKEY_CTX_free(ctx);
free(tmp);
if (ret <= 0)
EXCEPTION(gourou::CLIENT_RSA_ERROR, ERR_error_string(ERR_get_error(), NULL));
} }
void DRMProcessorClientImpl::RSAPrivateDecrypt(const unsigned char* RSAKey, unsigned int RSAKeyLength, void DRMProcessorClientImpl::RSAPrivateDecrypt(const unsigned char* RSAKey, unsigned int RSAKeyLength,
@@ -317,24 +378,30 @@ void DRMProcessorClientImpl::RSAPrivateDecrypt(const unsigned char* RSAKey, unsi
if (!p8inf) if (!p8inf)
EXCEPTION(gourou::CLIENT_INVALID_PKCS8, ERR_error_string(ERR_get_error(), NULL)); EXCEPTION(gourou::CLIENT_INVALID_PKCS8, ERR_error_string(ERR_get_error(), NULL));
EVP_PKEY_CTX *ctx;
EVP_PKEY* pkey = EVP_PKCS82PKEY(p8inf); EVP_PKEY* pkey = EVP_PKCS82PKEY(p8inf);
RSA * rsa; size_t outlen = dataLength;
int ret; int ret;
rsa = EVP_PKEY_get1_RSA(pkey); if (!pkey)
EXCEPTION(gourou::CLIENT_INVALID_PKCS8, ERR_error_string(ERR_get_error(), NULL));
ret = RSA_private_decrypt(dataLength, data, res, rsa, RSA_NO_PADDING); ctx = EVP_PKEY_CTX_new(pkey, NULL);
if (ret < 0) if (EVP_PKEY_decrypt_init(ctx) <= 0)
EXCEPTION(gourou::CLIENT_RSA_ERROR, ERR_error_string(ERR_get_error(), NULL)); EXCEPTION(gourou::CLIENT_RSA_ERROR, ERR_error_string(ERR_get_error(), NULL));
if (gourou::logLevel >= gourou::DEBUG) if (EVP_PKEY_CTX_set_rsa_padding(ctx, RSA_NO_PADDING) <= 0)
{ EXCEPTION(gourou::CLIENT_RSA_ERROR, ERR_error_string(ERR_get_error(), NULL));
printf("Decrypted : ");
for(int i=0; i<ret; i++) ret = EVP_PKEY_decrypt(ctx, res, &outlen, data, dataLength);
printf("%02x ", res[i]);
printf("\n"); PKCS8_PRIV_KEY_INFO_free(p8inf);
} EVP_PKEY_CTX_free(ctx);
BIO_free(mem);
if (ret <= 0)
EXCEPTION(gourou::CLIENT_RSA_ERROR, ERR_error_string(ERR_get_error(), NULL));
} }
void DRMProcessorClientImpl::RSAPublicEncrypt(const unsigned char* RSAKey, unsigned int RSAKeyLength, void DRMProcessorClientImpl::RSAPublicEncrypt(const unsigned char* RSAKey, unsigned int RSAKeyLength,
@@ -342,61 +409,79 @@ void DRMProcessorClientImpl::RSAPublicEncrypt(const unsigned char* RSAKey, unsig
const unsigned char* data, unsigned dataLength, const unsigned char* data, unsigned dataLength,
unsigned char* res) unsigned char* res)
{ {
size_t outlen;
X509 * x509 = d2i_X509(0, &RSAKey, RSAKeyLength); X509 * x509 = d2i_X509(0, &RSAKey, RSAKeyLength);
if (!x509) if (!x509)
EXCEPTION(gourou::CLIENT_INVALID_CERTIFICATE, "Invalid certificate"); EXCEPTION(gourou::CLIENT_INVALID_CERTIFICATE, "Invalid certificate");
EVP_PKEY_CTX *ctx;
EVP_PKEY * evpKey = X509_get_pubkey(x509); EVP_PKEY * evpKey = X509_get_pubkey(x509);
RSA* rsa = EVP_PKEY_get1_RSA(evpKey);
EVP_PKEY_free(evpKey);
if (!rsa) if (!evpKey)
EXCEPTION(gourou::CLIENT_NO_PRIV_KEY, "No private key in certificate"); EXCEPTION(gourou::CLIENT_NO_PUB_KEY, "No public key in certificate");
ctx = EVP_PKEY_CTX_new(evpKey, NULL);
if (EVP_PKEY_encrypt_init(ctx) <= 0)
EXCEPTION(gourou::CLIENT_RSA_ERROR, ERR_error_string(ERR_get_error(), NULL));
if (EVP_PKEY_CTX_set_rsa_padding(ctx, RSA_PKCS1_PADDING) <= 0)
EXCEPTION(gourou::CLIENT_RSA_ERROR, ERR_error_string(ERR_get_error(), NULL));
int ret = EVP_PKEY_encrypt(ctx, res, &outlen, data, dataLength);
EVP_PKEY_CTX_free(ctx);
int ret = RSA_public_encrypt(dataLength, data, res, rsa, RSA_PKCS1_PADDING);
if (ret < 0) if (ret < 0)
EXCEPTION(gourou::CLIENT_RSA_ERROR, ERR_error_string(ERR_get_error(), NULL)); EXCEPTION(gourou::CLIENT_RSA_ERROR, ERR_error_string(ERR_get_error(), NULL));
EVP_PKEY_free(evpKey);
} }
void* DRMProcessorClientImpl::generateRSAKey(int keyLengthBits) void* DRMProcessorClientImpl::generateRSAKey(int keyLengthBits)
{ {
BIGNUM * bn = BN_new(); BIGNUM * bn = BN_new();
RSA * rsa = RSA_new(); EVP_PKEY_CTX* ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_RSA, NULL);
EVP_PKEY *key = NULL;
BN_set_word(bn, 0x10001); BN_set_word(bn, 0x10001);
RSA_generate_key_ex(rsa, keyLengthBits, bn, 0);
EVP_PKEY_keygen_init(ctx);
EVP_PKEY_CTX_set_rsa_keygen_bits(ctx, keyLengthBits);
EVP_PKEY_CTX_set_rsa_padding(ctx, RSA_PKCS1_PADDING);
EVP_PKEY_CTX_set1_rsa_keygen_pubexp(ctx, bn);
EVP_PKEY_keygen(ctx, &key);
EVP_PKEY_CTX_free(ctx);
BN_free(bn); BN_free(bn);
return rsa; return key;
} }
void DRMProcessorClientImpl::destroyRSAHandler(void* handler) void DRMProcessorClientImpl::destroyRSAHandler(void* handler)
{ {
RSA_free((RSA*)handler); free(handler);
} }
void DRMProcessorClientImpl::extractRSAPublicKey(void* handler, unsigned char** keyOut, unsigned int* keyOutLength) void DRMProcessorClientImpl::extractRSAPublicKey(void* handler, unsigned char** keyOut, unsigned int* keyOutLength)
{ {
EVP_PKEY * evpKey = EVP_PKEY_new();
EVP_PKEY_set1_RSA(evpKey, (RSA*)handler);
X509_PUBKEY *x509_pubkey = 0; X509_PUBKEY *x509_pubkey = 0;
X509_PUBKEY_set(&x509_pubkey, evpKey); X509_PUBKEY_set(&x509_pubkey, (EVP_PKEY*)handler);
*keyOutLength = i2d_X509_PUBKEY(x509_pubkey, keyOut); *keyOutLength = i2d_X509_PUBKEY(x509_pubkey, keyOut);
X509_PUBKEY_free(x509_pubkey); X509_PUBKEY_free(x509_pubkey);
EVP_PKEY_free(evpKey);
} }
void DRMProcessorClientImpl::extractRSAPrivateKey(void* handler, unsigned char** keyOut, unsigned int* keyOutLength) void DRMProcessorClientImpl::extractRSAPrivateKey(void* handler, unsigned char** keyOut, unsigned int* keyOutLength)
{ {
EVP_PKEY * evpKey = EVP_PKEY_new(); PKCS8_PRIV_KEY_INFO * privKey = EVP_PKEY2PKCS8((EVP_PKEY*)handler);
EVP_PKEY_set1_RSA(evpKey, (RSA*)handler);
PKCS8_PRIV_KEY_INFO * privKey = EVP_PKEY2PKCS8(evpKey);
*keyOutLength = i2d_PKCS8_PRIV_KEY_INFO(privKey, keyOut); *keyOutLength = i2d_PKCS8_PRIV_KEY_INFO(privKey, keyOut);
PKCS8_PRIV_KEY_INFO_free(privKey); PKCS8_PRIV_KEY_INFO_free(privKey);
EVP_PKEY_free(evpKey);
} }
void DRMProcessorClientImpl::extractCertificate(const unsigned char* RSAKey, unsigned int RSAKeyLength, void DRMProcessorClientImpl::extractCertificate(const unsigned char* RSAKey, unsigned int RSAKeyLength,
@@ -406,12 +491,14 @@ void DRMProcessorClientImpl::extractCertificate(const unsigned char* RSAKey, uns
PKCS12 * pkcs12; PKCS12 * pkcs12;
EVP_PKEY* pkey = 0; EVP_PKEY* pkey = 0;
X509* cert = 0; X509* cert = 0;
STACK_OF(X509)* ca;
pkcs12 = d2i_PKCS12(NULL, &RSAKey, RSAKeyLength); pkcs12 = d2i_PKCS12(NULL, &RSAKey, RSAKeyLength);
if (!pkcs12) if (!pkcs12)
EXCEPTION(gourou::CLIENT_INVALID_PKCS12, ERR_error_string(ERR_get_error(), NULL)); EXCEPTION(gourou::CLIENT_INVALID_PKCS12, ERR_error_string(ERR_get_error(), NULL));
PKCS12_parse(pkcs12, password.c_str(), &pkey, &cert, &ca); PKCS12_parse(pkcs12, password.c_str(), &pkey, &cert, NULL);
if (!cert)
EXCEPTION(gourou::CLIENT_INVALID_PKCS12, ERR_error_string(ERR_get_error(), NULL));
*certOutLength = i2d_X509(cert, certOut); *certOutLength = i2d_X509(cert, certOut);
@@ -419,24 +506,27 @@ void DRMProcessorClientImpl::extractCertificate(const unsigned char* RSAKey, uns
} }
/* Crypto interface */ /* Crypto interface */
void DRMProcessorClientImpl::Encrypt(CRYPTO_ALGO algo, CHAINING_MODE chaining, void DRMProcessorClientImpl::encrypt(CRYPTO_ALGO algo, CHAINING_MODE chaining,
const unsigned char* key, unsigned int keyLength, const unsigned char* key, unsigned int keyLength,
const unsigned char* iv, unsigned int ivLength, const unsigned char* iv, unsigned int ivLength,
const unsigned char* dataIn, unsigned int dataInLength, const unsigned char* dataIn, unsigned int dataInLength,
unsigned char* dataOut, unsigned int* dataOutLength) unsigned char* dataOut, unsigned int* dataOutLength)
{ {
void* handler = EncryptInit(algo, chaining, key, keyLength, iv, ivLength); void* handler = encryptInit(algo, chaining, key, keyLength, iv, ivLength);
EncryptUpdate(handler, dataIn, dataInLength, dataOut, dataOutLength); encryptUpdate(handler, dataIn, dataInLength, dataOut, dataOutLength);
EncryptFinalize(handler, dataOut+*dataOutLength, dataOutLength); encryptFinalize(handler, dataOut+*dataOutLength, dataOutLength);
} }
void* DRMProcessorClientImpl::EncryptInit(CRYPTO_ALGO algo, CHAINING_MODE chaining, void* DRMProcessorClientImpl::encryptInit(CRYPTO_ALGO algo, CHAINING_MODE chaining,
const unsigned char* key, unsigned int keyLength, const unsigned char* key, unsigned int keyLength,
const unsigned char* iv, unsigned int ivLength) const unsigned char* iv, unsigned int ivLength)
{ {
EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new(); EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new();
int ret = 0;
if (algo == ALGO_AES) switch (algo)
{
case ALGO_AES:
{ {
switch(keyLength) switch(keyLength)
{ {
@@ -444,10 +534,10 @@ void* DRMProcessorClientImpl::EncryptInit(CRYPTO_ALGO algo, CHAINING_MODE chaini
switch(chaining) switch(chaining)
{ {
case CHAIN_ECB: case CHAIN_ECB:
EVP_EncryptInit(ctx, EVP_aes_128_ecb(), key, iv); ret = EVP_EncryptInit(ctx, EVP_aes_128_ecb(), key, iv);
break; break;
case CHAIN_CBC: case CHAIN_CBC:
EVP_EncryptInit(ctx, EVP_aes_128_cbc(), key, iv); ret = EVP_EncryptInit(ctx, EVP_aes_128_cbc(), key, iv);
break; break;
default: default:
EXCEPTION(gourou::CLIENT_BAD_CHAINING, "Unknown chaining mode " << chaining); EXCEPTION(gourou::CLIENT_BAD_CHAINING, "Unknown chaining mode " << chaining);
@@ -457,26 +547,39 @@ void* DRMProcessorClientImpl::EncryptInit(CRYPTO_ALGO algo, CHAINING_MODE chaini
EVP_CIPHER_CTX_free(ctx); EVP_CIPHER_CTX_free(ctx);
EXCEPTION(gourou::CLIENT_BAD_KEY_SIZE, "Invalid key size " << keyLength); EXCEPTION(gourou::CLIENT_BAD_KEY_SIZE, "Invalid key size " << keyLength);
} }
break;
} }
else if (algo == ALGO_RC4) case ALGO_RC4:
{ {
if (keyLength != 16) if (keyLength != 16)
{ {
EVP_CIPHER_CTX_free(ctx); EVP_CIPHER_CTX_free(ctx);
EXCEPTION(gourou::CLIENT_BAD_KEY_SIZE, "Invalid key size " << keyLength); EXCEPTION(gourou::CLIENT_BAD_KEY_SIZE, "Invalid key size " << keyLength);
} }
EVP_DecryptInit(ctx, EVP_rc4(), key, iv); ret = EVP_DecryptInit(ctx, EVP_rc4(), key, iv);
break;
} }
}
if (ret <= 0)
{
EVP_CIPHER_CTX_free(ctx);
EXCEPTION(gourou::CLIENT_CRYPT_ERROR, ERR_error_string(ERR_get_error(), NULL));
}
return ctx; return ctx;
} }
void* DRMProcessorClientImpl::DecryptInit(CRYPTO_ALGO algo, CHAINING_MODE chaining, void* DRMProcessorClientImpl::decryptInit(CRYPTO_ALGO algo, CHAINING_MODE chaining,
const unsigned char* key, unsigned int keyLength, const unsigned char* key, unsigned int keyLength,
const unsigned char* iv, unsigned int ivLength) const unsigned char* iv, unsigned int ivLength)
{ {
EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new(); EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new();
int ret = 0;
if (algo == ALGO_AES) switch(algo)
{
case ALGO_AES:
{ {
switch(keyLength) switch(keyLength)
{ {
@@ -484,10 +587,10 @@ void* DRMProcessorClientImpl::DecryptInit(CRYPTO_ALGO algo, CHAINING_MODE chaini
switch(chaining) switch(chaining)
{ {
case CHAIN_ECB: case CHAIN_ECB:
EVP_DecryptInit(ctx, EVP_aes_128_ecb(), key, iv); ret = EVP_DecryptInit(ctx, EVP_aes_128_ecb(), key, iv);
break; break;
case CHAIN_CBC: case CHAIN_CBC:
EVP_DecryptInit(ctx, EVP_aes_128_cbc(), key, iv); ret = EVP_DecryptInit(ctx, EVP_aes_128_cbc(), key, iv);
break; break;
default: default:
EXCEPTION(gourou::CLIENT_BAD_CHAINING, "Unknown chaining mode " << chaining); EXCEPTION(gourou::CLIENT_BAD_CHAINING, "Unknown chaining mode " << chaining);
@@ -497,58 +600,81 @@ void* DRMProcessorClientImpl::DecryptInit(CRYPTO_ALGO algo, CHAINING_MODE chaini
EVP_CIPHER_CTX_free(ctx); EVP_CIPHER_CTX_free(ctx);
EXCEPTION(gourou::CLIENT_BAD_KEY_SIZE, "Invalid key size " << keyLength); EXCEPTION(gourou::CLIENT_BAD_KEY_SIZE, "Invalid key size " << keyLength);
} }
break;
} }
else if (algo == ALGO_RC4) case ALGO_RC4:
{ {
if (keyLength != 16) if (keyLength != 16)
{ {
EVP_CIPHER_CTX_free(ctx); EVP_CIPHER_CTX_free(ctx);
EXCEPTION(gourou::CLIENT_BAD_KEY_SIZE, "Invalid key size " << keyLength); EXCEPTION(gourou::CLIENT_BAD_KEY_SIZE, "Invalid key size " << keyLength);
} }
EVP_DecryptInit(ctx, EVP_rc4(), key, iv); ret = EVP_DecryptInit(ctx, EVP_rc4(), key, iv);
break;
}
}
if (ret <= 0)
{
EVP_CIPHER_CTX_free(ctx);
EXCEPTION(gourou::CLIENT_CRYPT_ERROR, ERR_error_string(ERR_get_error(), NULL));
} }
return ctx; return ctx;
} }
void DRMProcessorClientImpl::EncryptUpdate(void* handler, const unsigned char* dataIn, unsigned int dataInLength, void DRMProcessorClientImpl::encryptUpdate(void* handler, const unsigned char* dataIn, unsigned int dataInLength,
unsigned char* dataOut, unsigned int* dataOutLength) unsigned char* dataOut, unsigned int* dataOutLength)
{ {
EVP_EncryptUpdate((EVP_CIPHER_CTX*)handler, dataOut, (int*)dataOutLength, dataIn, dataInLength); int ret = EVP_EncryptUpdate((EVP_CIPHER_CTX*)handler, dataOut, (int*)dataOutLength, dataIn, dataInLength);
if (ret <= 0)
EXCEPTION(gourou::CLIENT_CRYPT_ERROR, ERR_error_string(ERR_get_error(), NULL));
} }
void DRMProcessorClientImpl::EncryptFinalize(void* handler, void DRMProcessorClientImpl::encryptFinalize(void* handler,
unsigned char* dataOut, unsigned int* dataOutLength) unsigned char* dataOut, unsigned int* dataOutLength)
{ {
int len; int len, ret;
EVP_EncryptFinal_ex((EVP_CIPHER_CTX*)handler, dataOut, &len);
ret = EVP_EncryptFinal_ex((EVP_CIPHER_CTX*)handler, dataOut, &len);
*dataOutLength += len; *dataOutLength += len;
EVP_CIPHER_CTX_free((EVP_CIPHER_CTX*)handler); EVP_CIPHER_CTX_free((EVP_CIPHER_CTX*)handler);
if (ret <= 0)
EXCEPTION(gourou::CLIENT_CRYPT_ERROR, ERR_error_string(ERR_get_error(), NULL));
} }
void DRMProcessorClientImpl::Decrypt(CRYPTO_ALGO algo, CHAINING_MODE chaining, void DRMProcessorClientImpl::decrypt(CRYPTO_ALGO algo, CHAINING_MODE chaining,
const unsigned char* key, unsigned int keyLength, const unsigned char* key, unsigned int keyLength,
const unsigned char* iv, unsigned int ivLength, const unsigned char* iv, unsigned int ivLength,
const unsigned char* dataIn, unsigned int dataInLength, const unsigned char* dataIn, unsigned int dataInLength,
unsigned char* dataOut, unsigned int* dataOutLength) unsigned char* dataOut, unsigned int* dataOutLength)
{ {
void* handler = DecryptInit(algo, chaining, key, keyLength, iv, ivLength); void* handler = decryptInit(algo, chaining, key, keyLength, iv, ivLength);
DecryptUpdate(handler, dataIn, dataInLength, dataOut, dataOutLength); decryptUpdate(handler, dataIn, dataInLength, dataOut, dataOutLength);
DecryptFinalize(handler, dataOut+*dataOutLength, dataOutLength); decryptFinalize(handler, dataOut+*dataOutLength, dataOutLength);
} }
void DRMProcessorClientImpl::DecryptUpdate(void* handler, const unsigned char* dataIn, unsigned int dataInLength, void DRMProcessorClientImpl::decryptUpdate(void* handler, const unsigned char* dataIn, unsigned int dataInLength,
unsigned char* dataOut, unsigned int* dataOutLength) unsigned char* dataOut, unsigned int* dataOutLength)
{ {
EVP_DecryptUpdate((EVP_CIPHER_CTX*)handler, dataOut, (int*)dataOutLength, dataIn, dataInLength); int ret = EVP_DecryptUpdate((EVP_CIPHER_CTX*)handler, dataOut, (int*)dataOutLength, dataIn, dataInLength);
if (ret <= 0)
EXCEPTION(gourou::CLIENT_CRYPT_ERROR, ERR_error_string(ERR_get_error(), NULL));
} }
void DRMProcessorClientImpl::DecryptFinalize(void* handler, unsigned char* dataOut, unsigned int* dataOutLength) void DRMProcessorClientImpl::decryptFinalize(void* handler, unsigned char* dataOut, unsigned int* dataOutLength)
{ {
int len; int len, ret;
EVP_DecryptFinal_ex((EVP_CIPHER_CTX*)handler, dataOut, &len);
ret = EVP_DecryptFinal_ex((EVP_CIPHER_CTX*)handler, dataOut, &len);
*dataOutLength += len; *dataOutLength += len;
EVP_CIPHER_CTX_free((EVP_CIPHER_CTX*)handler); EVP_CIPHER_CTX_free((EVP_CIPHER_CTX*)handler);
if (ret <= 0)
EXCEPTION(gourou::CLIENT_CRYPT_ERROR, ERR_error_string(ERR_get_error(), NULL));
} }
void* DRMProcessorClientImpl::zipOpen(const std::string& path) void* DRMProcessorClientImpl::zipOpen(const std::string& path)

View File

@@ -31,16 +31,23 @@
#include <string> #include <string>
#if OPENSSL_VERSION_MAJOR >= 3
#include <openssl/provider.h>
#endif
#include <drmprocessorclient.h> #include <drmprocessorclient.h>
class DRMProcessorClientImpl : public gourou::DRMProcessorClient class DRMProcessorClientImpl : public gourou::DRMProcessorClient
{ {
public: public:
DRMProcessorClientImpl();
~DRMProcessorClientImpl();
/* Digest interface */ /* Digest interface */
virtual void* createDigest(const std::string& digestName); virtual void* createDigest(const std::string& digestName);
virtual int digestUpdate(void* handler, unsigned char* data, unsigned int length); virtual void digestUpdate(void* handler, unsigned char* data, unsigned int length);
virtual int digestFinalize(void* handler,unsigned char* digestOut); virtual void digestFinalize(void* handler,unsigned char* digestOut);
virtual int digest(const std::string& digestName, unsigned char* data, unsigned int length, unsigned char* digestOut); virtual void digest(const std::string& digestName, unsigned char* data, unsigned int length, unsigned char* digestOut);
/* Random interface */ /* Random interface */
virtual void randBytes(unsigned char* bytesOut, unsigned int length); virtual void randBytes(unsigned char* bytesOut, unsigned int length);
@@ -73,34 +80,34 @@ public:
unsigned char** certOut, unsigned int* certOutLength); unsigned char** certOut, unsigned int* certOutLength);
/* Crypto interface */ /* Crypto interface */
virtual void Encrypt(CRYPTO_ALGO algo, CHAINING_MODE chaining, virtual void encrypt(CRYPTO_ALGO algo, CHAINING_MODE chaining,
const unsigned char* key, unsigned int keyLength, const unsigned char* key, unsigned int keyLength,
const unsigned char* iv, unsigned int ivLength, const unsigned char* iv, unsigned int ivLength,
const unsigned char* dataIn, unsigned int dataInLength, const unsigned char* dataIn, unsigned int dataInLength,
unsigned char* dataOut, unsigned int* dataOutLength); unsigned char* dataOut, unsigned int* dataOutLength);
virtual void* EncryptInit(CRYPTO_ALGO algo, CHAINING_MODE chaining, virtual void* encryptInit(CRYPTO_ALGO algo, CHAINING_MODE chaining,
const unsigned char* key, unsigned int keyLength, const unsigned char* key, unsigned int keyLength,
const unsigned char* iv=0, unsigned int ivLength=0); const unsigned char* iv=0, unsigned int ivLength=0);
virtual void EncryptUpdate(void* handler, const unsigned char* dataIn, unsigned int dataInLength, virtual void encryptUpdate(void* handler, const unsigned char* dataIn, unsigned int dataInLength,
unsigned char* dataOut, unsigned int* dataOutLength); unsigned char* dataOut, unsigned int* dataOutLength);
virtual void EncryptFinalize(void* handler, unsigned char* dataOut, unsigned int* dataOutLength); virtual void encryptFinalize(void* handler, unsigned char* dataOut, unsigned int* dataOutLength);
virtual void Decrypt(CRYPTO_ALGO algo, CHAINING_MODE chaining, virtual void decrypt(CRYPTO_ALGO algo, CHAINING_MODE chaining,
const unsigned char* key, unsigned int keyLength, const unsigned char* key, unsigned int keyLength,
const unsigned char* iv, unsigned int ivLength, const unsigned char* iv, unsigned int ivLength,
const unsigned char* dataIn, unsigned int dataInLength, const unsigned char* dataIn, unsigned int dataInLength,
unsigned char* dataOut, unsigned int* dataOutLength); unsigned char* dataOut, unsigned int* dataOutLength);
virtual void* DecryptInit(CRYPTO_ALGO algo, CHAINING_MODE chaining, virtual void* decryptInit(CRYPTO_ALGO algo, CHAINING_MODE chaining,
const unsigned char* key, unsigned int keyLength, const unsigned char* key, unsigned int keyLength,
const unsigned char* iv=0, unsigned int ivLength=0); const unsigned char* iv=0, unsigned int ivLength=0);
virtual void DecryptUpdate(void* handler, const unsigned char* dataIn, unsigned int dataInLength, virtual void decryptUpdate(void* handler, const unsigned char* dataIn, unsigned int dataInLength,
unsigned char* dataOut, unsigned int* dataOutLength); unsigned char* dataOut, unsigned int* dataOutLength);
virtual void DecryptFinalize(void* handler, unsigned char* dataOut, unsigned int* dataOutLength); virtual void decryptFinalize(void* handler, unsigned char* dataOut, unsigned int* dataOutLength);
/* ZIP Interface */ /* ZIP Interface */
virtual void* zipOpen(const std::string& path); virtual void* zipOpen(const std::string& path);
@@ -118,6 +125,17 @@ public:
virtual void deflate(gourou::ByteArray& data, gourou::ByteArray& result, virtual void deflate(gourou::ByteArray& data, gourou::ByteArray& result,
int wbits=-15, int compressionLevel=8); int wbits=-15, int compressionLevel=8);
private:
void padWithPKCS1(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
}; };
#endif #endif

40
utils/launcher.cpp Normal file
View File

@@ -0,0 +1,40 @@
#include <iostream>
#include <unistd.h>
#include <libgen.h>
#include <string.h>
#include "utils_common.h"
#ifndef DEFAULT_UTIL
#define DEFAULT_UTIL "acsmdownloader"
#endif
/* Inspired from https://discourse.appimage.org/t/call-alternative-binary-from-appimage/93/10*/
int main(int argc, char** argv)
{
char* util, *argv0;
char* mountPoint = getenv("APPDIR");
std::string fullPath;
/* Original command is in ARGV0 env variable*/
argv0 = strdup(getenv("ARGV0"));
util = basename(argv0);
fullPath = std::string(mountPoint) + util;
if (std::string(util) == "launcher" || !fileExists(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;
}