forked from soutade/libgourou
Initial commit
This commit is contained in:
commit
d5ce4d625e
8
.gitignore
vendored
Normal file
8
.gitignore
vendored
Normal file
|
@ -0,0 +1,8 @@
|
|||
obj
|
||||
lib
|
||||
*.a
|
||||
*.so
|
||||
*~
|
||||
utils/acsmdownloader
|
||||
utils/activate
|
||||
.adept
|
165
LICENSE
Normal file
165
LICENSE
Normal file
|
@ -0,0 +1,165 @@
|
|||
GNU LESSER GENERAL PUBLIC LICENSE
|
||||
Version 3, 29 June 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
|
||||
This version of the GNU Lesser General Public License incorporates
|
||||
the terms and conditions of version 3 of the GNU General Public
|
||||
License, supplemented by the additional permissions listed below.
|
||||
|
||||
0. Additional Definitions.
|
||||
|
||||
As used herein, "this License" refers to version 3 of the GNU Lesser
|
||||
General Public License, and the "GNU GPL" refers to version 3 of the GNU
|
||||
General Public License.
|
||||
|
||||
"The Library" refers to a covered work governed by this License,
|
||||
other than an Application or a Combined Work as defined below.
|
||||
|
||||
An "Application" is any work that makes use of an interface provided
|
||||
by the Library, but which is not otherwise based on the Library.
|
||||
Defining a subclass of a class defined by the Library is deemed a mode
|
||||
of using an interface provided by the Library.
|
||||
|
||||
A "Combined Work" is a work produced by combining or linking an
|
||||
Application with the Library. The particular version of the Library
|
||||
with which the Combined Work was made is also called the "Linked
|
||||
Version".
|
||||
|
||||
The "Minimal Corresponding Source" for a Combined Work means the
|
||||
Corresponding Source for the Combined Work, excluding any source code
|
||||
for portions of the Combined Work that, considered in isolation, are
|
||||
based on the Application, and not on the Linked Version.
|
||||
|
||||
The "Corresponding Application Code" for a Combined Work means the
|
||||
object code and/or source code for the Application, including any data
|
||||
and utility programs needed for reproducing the Combined Work from the
|
||||
Application, but excluding the System Libraries of the Combined Work.
|
||||
|
||||
1. Exception to Section 3 of the GNU GPL.
|
||||
|
||||
You may convey a covered work under sections 3 and 4 of this License
|
||||
without being bound by section 3 of the GNU GPL.
|
||||
|
||||
2. Conveying Modified Versions.
|
||||
|
||||
If you modify a copy of the Library, and, in your modifications, a
|
||||
facility refers to a function or data to be supplied by an Application
|
||||
that uses the facility (other than as an argument passed when the
|
||||
facility is invoked), then you may convey a copy of the modified
|
||||
version:
|
||||
|
||||
a) under this License, provided that you make a good faith effort to
|
||||
ensure that, in the event an Application does not supply the
|
||||
function or data, the facility still operates, and performs
|
||||
whatever part of its purpose remains meaningful, or
|
||||
|
||||
b) under the GNU GPL, with none of the additional permissions of
|
||||
this License applicable to that copy.
|
||||
|
||||
3. Object Code Incorporating Material from Library Header Files.
|
||||
|
||||
The object code form of an Application may incorporate material from
|
||||
a header file that is part of the Library. You may convey such object
|
||||
code under terms of your choice, provided that, if the incorporated
|
||||
material is not limited to numerical parameters, data structure
|
||||
layouts and accessors, or small macros, inline functions and templates
|
||||
(ten or fewer lines in length), you do both of the following:
|
||||
|
||||
a) Give prominent notice with each copy of the object code that the
|
||||
Library is used in it and that the Library and its use are
|
||||
covered by this License.
|
||||
|
||||
b) Accompany the object code with a copy of the GNU GPL and this license
|
||||
document.
|
||||
|
||||
4. Combined Works.
|
||||
|
||||
You may convey a Combined Work under terms of your choice that,
|
||||
taken together, effectively do not restrict modification of the
|
||||
portions of the Library contained in the Combined Work and reverse
|
||||
engineering for debugging such modifications, if you also do each of
|
||||
the following:
|
||||
|
||||
a) Give prominent notice with each copy of the Combined Work that
|
||||
the Library is used in it and that the Library and its use are
|
||||
covered by this License.
|
||||
|
||||
b) Accompany the Combined Work with a copy of the GNU GPL and this license
|
||||
document.
|
||||
|
||||
c) For a Combined Work that displays copyright notices during
|
||||
execution, include the copyright notice for the Library among
|
||||
these notices, as well as a reference directing the user to the
|
||||
copies of the GNU GPL and this license document.
|
||||
|
||||
d) Do one of the following:
|
||||
|
||||
0) Convey the Minimal Corresponding Source under the terms of this
|
||||
License, and the Corresponding Application Code in a form
|
||||
suitable for, and under terms that permit, the user to
|
||||
recombine or relink the Application with a modified version of
|
||||
the Linked Version to produce a modified Combined Work, in the
|
||||
manner specified by section 6 of the GNU GPL for conveying
|
||||
Corresponding Source.
|
||||
|
||||
1) Use a suitable shared library mechanism for linking with the
|
||||
Library. A suitable mechanism is one that (a) uses at run time
|
||||
a copy of the Library already present on the user's computer
|
||||
system, and (b) will operate properly with a modified version
|
||||
of the Library that is interface-compatible with the Linked
|
||||
Version.
|
||||
|
||||
e) Provide Installation Information, but only if you would otherwise
|
||||
be required to provide such information under section 6 of the
|
||||
GNU GPL, and only to the extent that such information is
|
||||
necessary to install and execute a modified version of the
|
||||
Combined Work produced by recombining or relinking the
|
||||
Application with a modified version of the Linked Version. (If
|
||||
you use option 4d0, the Installation Information must accompany
|
||||
the Minimal Corresponding Source and Corresponding Application
|
||||
Code. If you use option 4d1, you must provide the Installation
|
||||
Information in the manner specified by section 6 of the GNU GPL
|
||||
for conveying Corresponding Source.)
|
||||
|
||||
5. Combined Libraries.
|
||||
|
||||
You may place library facilities that are a work based on the
|
||||
Library side by side in a single library together with other library
|
||||
facilities that are not Applications and are not covered by this
|
||||
License, and convey such a combined library under terms of your
|
||||
choice, if you do both of the following:
|
||||
|
||||
a) Accompany the combined library with a copy of the same work based
|
||||
on the Library, uncombined with any other library facilities,
|
||||
conveyed under the terms of this License.
|
||||
|
||||
b) Give prominent notice with the combined library that part of it
|
||||
is a work based on the Library, and explaining where to find the
|
||||
accompanying uncombined form of the same work.
|
||||
|
||||
6. Revised Versions of the GNU Lesser General Public License.
|
||||
|
||||
The Free Software Foundation may publish revised and/or new versions
|
||||
of the GNU Lesser General Public License from time to time. Such new
|
||||
versions will be similar in spirit to the present version, but may
|
||||
differ in detail to address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the
|
||||
Library as you received it specifies that a certain numbered version
|
||||
of the GNU Lesser General Public License "or any later version"
|
||||
applies to it, you have the option of following the terms and
|
||||
conditions either of that published version or of any later version
|
||||
published by the Free Software Foundation. If the Library as you
|
||||
received it does not specify a version number of the GNU Lesser
|
||||
General Public License, you may choose any version of the GNU Lesser
|
||||
General Public License ever published by the Free Software Foundation.
|
||||
|
||||
If the Library as you received it specifies that a proxy can decide
|
||||
whether future versions of the GNU Lesser General Public License shall
|
||||
apply, that proxy's public statement of acceptance of any version is
|
||||
permanent authorization for you to choose that version for the
|
||||
Library.
|
55
Makefile
Normal file
55
Makefile
Normal file
|
@ -0,0 +1,55 @@
|
|||
|
||||
AR ?= $(CROSS)ar
|
||||
CXX ?= $(CROSS)g++
|
||||
|
||||
CXXFLAGS=-Wall -fPIC -I./include -I./lib -I./lib/pugixml/src/
|
||||
LDFLAGS=
|
||||
|
||||
ifneq ($(DEBUG),)
|
||||
CXXFLAGS += -ggdb -O0
|
||||
else
|
||||
CXXFLAGS += -O2
|
||||
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/bytearray.cpp src/pugixml.cpp
|
||||
OBJECTS := $(patsubst $(SRCDIR)/%,$(BUILDDIR)/%,$(SOURCES:.$(SRCEXT)=.$(OBJEXT)))
|
||||
|
||||
.PHONY: utils
|
||||
|
||||
all: lib obj libgourou utils
|
||||
|
||||
lib:
|
||||
mkdir lib
|
||||
./scripts/setup.sh
|
||||
|
||||
obj:
|
||||
mkdir obj
|
||||
|
||||
$(BUILDDIR)/%.$(OBJEXT): $(SRCDIR)/%.$(SRCEXT)
|
||||
$(CXX) $(CXXFLAGS) -c $^ -o $@
|
||||
|
||||
libgourou: libgourou.a libgourou.so
|
||||
|
||||
libgourou.a: $(OBJECTS)
|
||||
$(AR) crs $@ obj/*.o
|
||||
|
||||
libgourou.so: libgourou.a
|
||||
$(CXX) obj/*.o $(LDFLAGS) -o $@ -shared
|
||||
|
||||
utils:
|
||||
make -C utils ROOT=$(PWD) CXX=$(CXX) AR=$(AR) DEBUG=$(DEBUG)
|
||||
|
||||
clean:
|
||||
rm -rf libgourou.a libgourou.so obj
|
||||
make -C utils clean
|
||||
|
||||
ultraclean: clean
|
||||
rm -rf lib
|
||||
make -C utils ultraclean
|
85
README.md
Normal file
85
README.md
Normal file
|
@ -0,0 +1,85 @@
|
|||
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.
|
||||
|
||||
|
||||
Architecture
|
||||
------------
|
||||
|
||||
Like RMSDK, libgourou has a client/server scheme. All platform specific functions (crypto, network...) has to be implemented in a client class (that derives from DRMProcessorClient) while server implements ADEPT protocol.
|
||||
A reference implementation using Qt, OpenSSL and libzip is provided (in _utils_ directory).
|
||||
|
||||
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()_
|
||||
|
||||
|
||||
You can import configuration from (at least) :
|
||||
|
||||
* Kobo device : .adept/device.xml, .adept/devicesalt and .adept/activation.xml
|
||||
* Bookeen device : .adobe-digital-editions/device.xml, root/devkey.bin and .adobe-digital-editions/activation.xml
|
||||
|
||||
Or create a new one. Be careful : there is a limited number of devices that can be created bye one account.
|
||||
|
||||
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 :
|
||||
|
||||
* None
|
||||
|
||||
For utils :
|
||||
|
||||
* QT5Core
|
||||
* QT5Network
|
||||
* OpenSSL
|
||||
* libzip
|
||||
|
||||
|
||||
Compilation
|
||||
-----------
|
||||
|
||||
User _make_
|
||||
|
||||
_make_ [CROSS=XXX] [DEBUG=1]
|
||||
|
||||
CROSS can define a cross compiler prefix (ie arm-linux-gnueabihf-)
|
||||
|
||||
DEBUG can be set to compile in DEBUG mode
|
||||
|
||||
|
||||
Utils
|
||||
-----
|
||||
|
||||
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 <USERNAME>
|
||||
|
||||
Then a _./.adept_ directory is created with all configuration file
|
||||
|
||||
To download an ePub :
|
||||
|
||||
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$PWD
|
||||
./utils/acsmdownloader -f <ACSM_FILE>
|
||||
|
||||
|
||||
Copyright
|
||||
---------
|
||||
|
||||
Grégory Soutadé
|
||||
|
||||
|
||||
|
||||
License
|
||||
-------
|
||||
|
||||
libgourou : LGPL v3 or later
|
||||
|
||||
utils : BSD
|
143
include/bytearray.h
Normal file
143
include/bytearray.h
Normal file
|
@ -0,0 +1,143 @@
|
|||
/*
|
||||
Copyright 2021 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef _BYTEARRAY_H_
|
||||
#define _BYTEARRAY_H_
|
||||
|
||||
#include <map>
|
||||
#include <string>
|
||||
|
||||
namespace gourou
|
||||
{
|
||||
/**
|
||||
* @brief Utility class for byte array management.
|
||||
*
|
||||
* It's an equivalent of QByteArray
|
||||
*
|
||||
* Data handled is first copied in a newly allocated buffer
|
||||
* and then shared between all copies until last object is destroyed
|
||||
*/
|
||||
class ByteArray
|
||||
{
|
||||
public:
|
||||
|
||||
/**
|
||||
* @brief Create an empty byte array
|
||||
*/
|
||||
ByteArray();
|
||||
|
||||
/**
|
||||
* @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 str
|
||||
*
|
||||
* @param str Use internal data of str
|
||||
*/
|
||||
ByteArray(const std::string& str);
|
||||
|
||||
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 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 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 Append a byte to internal data
|
||||
*/
|
||||
void append(unsigned char c);
|
||||
|
||||
/**
|
||||
* @brief Append data to internal data
|
||||
*/
|
||||
void append(const unsigned char* data, unsigned int length);
|
||||
|
||||
/**
|
||||
* @brief Append str to internal data
|
||||
*/
|
||||
void append(const char* str);
|
||||
|
||||
/**
|
||||
* @brief Append str to internal data
|
||||
*/
|
||||
void append(const std::string& str);
|
||||
|
||||
/**
|
||||
* @brief Get internal data. Must bot be modified nor freed
|
||||
*/
|
||||
const unsigned char* data() {return _data;}
|
||||
|
||||
/**
|
||||
* @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();
|
||||
|
||||
const unsigned char* _data;
|
||||
unsigned int _length;
|
||||
static std::map<const unsigned char*, int> refCounter;
|
||||
};
|
||||
}
|
||||
#endif
|
84
include/device.h
Normal file
84
include/device.h
Normal file
|
@ -0,0 +1,84 @@
|
|||
/*
|
||||
Copyright 2021 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef _DEVICE_H_
|
||||
#define _DEVICE_H_
|
||||
|
||||
namespace gourou
|
||||
{
|
||||
class DRMProcessor;
|
||||
|
||||
/**
|
||||
* @brief This class is a container for device.xml (device info) and devicesalt (device private key). It should not be used by user.
|
||||
*/
|
||||
class Device
|
||||
{
|
||||
public:
|
||||
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 Return value of devicesalt file (DEVICE_KEY_SIZE len)
|
||||
*/
|
||||
const unsigned char* getDeviceKey();
|
||||
|
||||
/**
|
||||
* @brief Get one value of device.xml (deviceClass, deviceSerial, deviceName, deviceType, jobbes, 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;
|
||||
std::string deviceFile;
|
||||
std::string deviceKeyFile;
|
||||
unsigned char deviceKey[DEVICE_KEY_SIZE];
|
||||
std::map<std::string, std::string> 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();
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
350
include/drmprocessorclient.h
Normal file
350
include/drmprocessorclient.h
Normal file
|
@ -0,0 +1,350 @@
|
|||
/*
|
||||
Copyright 2021 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef _DRMPROCESSORCLIENT_H_
|
||||
#define _DRMPROCESSORCLIENT_H_
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace gourou
|
||||
{
|
||||
/**
|
||||
* @brief All fucntions that must be implemented by a client
|
||||
* This allow libgourou to have only few external libraries dependencies
|
||||
* and improve code portability
|
||||
*/
|
||||
|
||||
class DigestInterface
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @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
|
||||
*
|
||||
* @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)
|
||||
*
|
||||
* @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)
|
||||
*
|
||||
* @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;
|
||||
};
|
||||
|
||||
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
|
||||
*/
|
||||
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_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 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 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 Destroy key previously generated
|
||||
*
|
||||
* @param handler Key to destroy
|
||||
*/
|
||||
virtual void destroyRSAHandler(void* handler) = 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;
|
||||
};
|
||||
|
||||
class CryptoInterface
|
||||
{
|
||||
public:
|
||||
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;
|
||||
|
||||
/**
|
||||
* @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 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 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 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 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 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 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 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;
|
||||
};
|
||||
|
||||
class DRMProcessorClient: public DigestInterface, public RandomInterface, public HTTPInterface, \
|
||||
public RSAInterface, public CryptoInterface, public ZIPInterface
|
||||
{};
|
||||
}
|
||||
#endif
|
65
include/fulfillment_item.h
Normal file
65
include/fulfillment_item.h
Normal file
|
@ -0,0 +1,65 @@
|
|||
/*
|
||||
Copyright 2021 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef _FULFILLMENT_ITEM_H_
|
||||
#define _FULFILLMENT_ITEM_H_
|
||||
|
||||
#include "bytearray.h"
|
||||
|
||||
#include <pugixml.hpp>
|
||||
|
||||
namespace gourou
|
||||
{
|
||||
class User;
|
||||
|
||||
/**
|
||||
* @brief This class is a container for a fulfillment object
|
||||
*/
|
||||
class FulfillmentItem
|
||||
{
|
||||
public:
|
||||
FulfillmentItem(pugi::xml_document& doc, User* user);
|
||||
|
||||
/**
|
||||
* @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 epub download URL
|
||||
*/
|
||||
std::string getDownloadURL();
|
||||
|
||||
private:
|
||||
pugi::xml_node metadatas;
|
||||
pugi::xml_document rights;
|
||||
std::string downloadURL;
|
||||
|
||||
void buildRights(const pugi::xml_node& licenseToken, User* user);
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
190
include/libgourou.h
Normal file
190
include/libgourou.h
Normal file
|
@ -0,0 +1,190 @@
|
|||
/*
|
||||
Copyright 2021 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef _LIBGOUROU_H_
|
||||
#define _LIBGOUROU_H_
|
||||
|
||||
#include "bytearray.h"
|
||||
#include "device.h"
|
||||
#include "user.h"
|
||||
#include "fulfillment_item.h"
|
||||
#include "drmprocessorclient.h"
|
||||
|
||||
#include <pugixml.hpp>
|
||||
|
||||
#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
|
||||
|
||||
namespace gourou
|
||||
{
|
||||
/**
|
||||
* @brief Main class that handle all ADEPTS functions (fulfill, download, signIn, activate)
|
||||
*/
|
||||
class DRMProcessor
|
||||
{
|
||||
public:
|
||||
|
||||
/**
|
||||
* @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();
|
||||
|
||||
/**
|
||||
* @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 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 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 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, 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 Set log level (higher number for verbose output)
|
||||
*/
|
||||
static void setLogLevel(int logLevel);
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
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 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 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 Get current user
|
||||
*/
|
||||
User* getUser() { return user; }
|
||||
|
||||
/**
|
||||
* @brief Get current device
|
||||
*/
|
||||
Device* getDevice() { return device; }
|
||||
|
||||
/**
|
||||
* @brief Get current client
|
||||
*/
|
||||
DRMProcessorClient* getClient() { return client; }
|
||||
|
||||
private:
|
||||
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<std::string,std::string> nsHash);
|
||||
void hashNode(const pugi::xml_node& root, unsigned char* sha_out);
|
||||
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);
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
350
include/libgourou_common.h
Normal file
350
include/libgourou_common.h
Normal file
|
@ -0,0 +1,350 @@
|
|||
/*
|
||||
Copyright 2021 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef _LIBGOUROU_COMMON_H_
|
||||
#define _LIBGOUROU_COMMON_H_
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <pugixml.hpp>
|
||||
|
||||
#include <exception>
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
#include <iomanip>
|
||||
#include <algorithm>
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#include <libgourou_log.h>
|
||||
#include "bytearray.h"
|
||||
|
||||
namespace gourou
|
||||
{
|
||||
/**
|
||||
* Some common utilities
|
||||
*/
|
||||
|
||||
#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
|
||||
};
|
||||
|
||||
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
|
||||
};
|
||||
|
||||
enum DOWNLOAD_ERROR {
|
||||
DW_NO_ITEM = 0x1200,
|
||||
};
|
||||
|
||||
enum SIGNIN_ERROR {
|
||||
SIGN_INVALID_CREDENTIALS = 0x1300,
|
||||
};
|
||||
|
||||
enum ACTIVATE_ERROR {
|
||||
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,
|
||||
};
|
||||
|
||||
enum USER_ERROR {
|
||||
USER_MKPATH = 0x3000,
|
||||
USER_INVALID_ACTIVATION_FILE,
|
||||
USER_NO_AUTHENTICATION_URL,
|
||||
USER_NO_PROPERTY,
|
||||
};
|
||||
|
||||
enum FULFILL_ITEM_ERROR {
|
||||
FFI_INVALID_FULFILLMENT_DATA = 0x4000
|
||||
};
|
||||
|
||||
enum CLIENT_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
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* 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 >= DEBUG)
|
||||
msg << "File : " << file << ":" << std::setbase(10) << line << std::endl;
|
||||
fullmessage = strdup(msg.str().c_str());
|
||||
}
|
||||
|
||||
~Exception()
|
||||
{
|
||||
free(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) \
|
||||
{std::stringstream __msg;__msg << message; throw gourou::Exception(code, __msg.str().c_str(), __FILE__, __LINE__);}
|
||||
|
||||
/**
|
||||
* Stream writer for pugi::xml
|
||||
*/
|
||||
class StringXMLWriter : public pugi::xml_writer
|
||||
{
|
||||
public:
|
||||
virtual void write(const void* data, size_t size)
|
||||
{
|
||||
result.append(static_cast<const char*>(data), size);
|
||||
}
|
||||
|
||||
const std::string& getResult() {return result;}
|
||||
|
||||
private:
|
||||
std::string result;
|
||||
};
|
||||
|
||||
static const char* ws = " \t\n\r\f\v";
|
||||
|
||||
/**
|
||||
* @brief trim from end of string (right)
|
||||
*/
|
||||
inline std::string& rtrim(std::string& s, const char* t = ws)
|
||||
{
|
||||
s.erase(s.find_last_not_of(t) + 1);
|
||||
return s;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief trim from beginning of string (left)
|
||||
*/
|
||||
inline std::string& ltrim(std::string& s, const char* t = ws)
|
||||
{
|
||||
s.erase(0, s.find_first_not_of(t));
|
||||
return s;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief trim from both ends of string (right then left)
|
||||
*/
|
||||
inline std::string& trim(std::string& s, const char* t = ws)
|
||||
{
|
||||
return ltrim(rtrim(s, t), t);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Extract text node from tag in document
|
||||
* It can throw an exception if tag does not exists
|
||||
* or just return an empty value
|
||||
*/
|
||||
static inline std::string extractTextElem(const pugi::xml_document& doc, const char* tagName, bool throwOnNull=true)
|
||||
{
|
||||
pugi::xpath_node xpath_node = doc.select_node(tagName);
|
||||
|
||||
if (!xpath_node)
|
||||
{
|
||||
if (throwOnNull)
|
||||
EXCEPTION(GOUROU_TAG_NOT_FOUND, "Tag " << tagName << " not found");
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
pugi::xml_node node = xpath_node.node().first_child();
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Append an element to root with a sub text element
|
||||
*
|
||||
* @param root Root node where to put child
|
||||
* @param name Tag name for child
|
||||
* @param value Text child value of tag element
|
||||
*/
|
||||
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());
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Write data in a file. If it already exists, it's truncated
|
||||
*/
|
||||
static inline void writeFile(std::string path, const unsigned char* data, unsigned int length)
|
||||
{
|
||||
int fd = open(path.c_str(), O_CREAT|O_WRONLY|O_TRUNC, S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH);
|
||||
|
||||
if (fd <= 0)
|
||||
EXCEPTION(GOUROU_FILE_ERROR, "Unable to create " << path);
|
||||
|
||||
if (write(fd, data, length) != length)
|
||||
EXCEPTION(GOUROU_FILE_ERROR, "Write error for file " << path);
|
||||
|
||||
close (fd);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Write data in a file. If it already exists, it's truncated
|
||||
*/
|
||||
static inline void writeFile(std::string path, ByteArray& data)
|
||||
{
|
||||
writeFile(path, data.data(), data.length());
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Write data in a file. If it already exists, it's truncated
|
||||
*/
|
||||
static inline void writeFile(std::string path, const std::string& data)
|
||||
{
|
||||
writeFile(path, (const unsigned char*)data.c_str(), data.length());
|
||||
}
|
||||
|
||||
/**
|
||||
* Read data from file
|
||||
*/
|
||||
static inline void readFile(std::string path, const unsigned char* data, unsigned int length)
|
||||
{
|
||||
int fd = open(path.c_str(), O_RDONLY);
|
||||
|
||||
if (fd <= 0)
|
||||
EXCEPTION(GOUROU_FILE_ERROR, "Unable to open " << path);
|
||||
|
||||
if (read(fd, (void*)data, length) != length)
|
||||
EXCEPTION(GOUROU_FILE_ERROR, "Read error for file " << path);
|
||||
|
||||
close (fd);
|
||||
}
|
||||
|
||||
#define PATH_MAX_STRING_SIZE 256
|
||||
|
||||
// https://gist.github.com/ChisholmKyle/0cbedcd3e64132243a39
|
||||
/* recursive mkdir */
|
||||
static inline int mkdir_p(const char *dir, const mode_t mode) {
|
||||
char tmp[PATH_MAX_STRING_SIZE];
|
||||
char *p = NULL;
|
||||
struct stat sb;
|
||||
size_t len;
|
||||
|
||||
/* copy path */
|
||||
len = strnlen (dir, PATH_MAX_STRING_SIZE);
|
||||
if (len == 0 || len == PATH_MAX_STRING_SIZE) {
|
||||
return -1;
|
||||
}
|
||||
memcpy (tmp, dir, len);
|
||||
tmp[len] = '\0';
|
||||
|
||||
/* remove trailing slash */
|
||||
if(tmp[len - 1] == '/') {
|
||||
tmp[len - 1] = '\0';
|
||||
}
|
||||
|
||||
/* check if path exists and is a directory */
|
||||
if (stat (tmp, &sb) == 0) {
|
||||
if (S_ISDIR (sb.st_mode)) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* recursive mkdir */
|
||||
for(p = tmp + 1; *p; p++) {
|
||||
if(*p == '/') {
|
||||
*p = 0;
|
||||
/* test path */
|
||||
if (stat(tmp, &sb) != 0) {
|
||||
/* path does not exist - create directory */
|
||||
if (mkdir(tmp, mode) < 0) {
|
||||
return -1;
|
||||
}
|
||||
} else if (!S_ISDIR(sb.st_mode)) {
|
||||
/* not a directory */
|
||||
return -1;
|
||||
}
|
||||
*p = '/';
|
||||
}
|
||||
}
|
||||
/* test path */
|
||||
if (stat(tmp, &sb) != 0) {
|
||||
/* path does not exist - create directory */
|
||||
if (mkdir(tmp, mode) < 0) {
|
||||
return -1;
|
||||
}
|
||||
} else if (!S_ISDIR(sb.st_mode)) {
|
||||
/* not a directory */
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
50
include/libgourou_log.h
Normal file
50
include/libgourou_log.h
Normal file
|
@ -0,0 +1,50 @@
|
|||
/*
|
||||
Copyright 2021 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef _LIBGOUROU_LOG_H_
|
||||
#define _LIBGOUROU_LOG_H_
|
||||
|
||||
#include <iostream>
|
||||
|
||||
namespace gourou {
|
||||
enum GOUROU_LOG_LEVEL {
|
||||
ERROR,
|
||||
WARN,
|
||||
INFO,
|
||||
DEBUG,
|
||||
TRACE
|
||||
};
|
||||
|
||||
extern GOUROU_LOG_LEVEL logLevel;
|
||||
|
||||
#define GOUROU_LOG(__lvl, __msg) if (__lvl <= gourou::logLevel) {std::cout << __msg << std::endl << std::flush;}
|
||||
#define GOUROU_LOG_FUNC() GOUROU_LOG(TRACE, __FUNCTION__ << "() @ " << __FILE__ << ":" << __LINE__)
|
||||
|
||||
/**
|
||||
* @brief Get current log level
|
||||
*/
|
||||
GOUROU_LOG_LEVEL getLogLevel();
|
||||
|
||||
/**
|
||||
* @brief Set log level
|
||||
*/
|
||||
void setLogLevel(GOUROU_LOG_LEVEL level);
|
||||
}
|
||||
|
||||
#endif
|
105
include/user.h
Normal file
105
include/user.h
Normal file
|
@ -0,0 +1,105 @@
|
|||
/*
|
||||
Copyright 2021 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef _USER_H_
|
||||
#define _USER_H_
|
||||
|
||||
#include <string>
|
||||
#include "bytearray.h"
|
||||
|
||||
#include <pugixml.hpp>
|
||||
|
||||
namespace gourou
|
||||
{
|
||||
class DRMProcessor;
|
||||
|
||||
/**
|
||||
* @brief This class is a container for activation.xml (activation info). It should not be used by user.
|
||||
*/
|
||||
class User
|
||||
{
|
||||
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& getCertificate();
|
||||
std::string& getAuthenticationCertificate();
|
||||
std::string& getPrivateLicenseKey();
|
||||
|
||||
/**
|
||||
* @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 doc data
|
||||
*/
|
||||
void updateActivationFile(const pugi::xml_document& doc);
|
||||
|
||||
/**
|
||||
* @brief Get one value of activation.xml
|
||||
*/
|
||||
std::string getProperty(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;
|
||||
|
||||
std::string activationFile;
|
||||
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);
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
10
scripts/setup.sh
Executable file
10
scripts/setup.sh
Executable file
|
@ -0,0 +1,10 @@
|
|||
#!/bin/bash
|
||||
|
||||
# Pugixml
|
||||
git clone https://github.com/zeux/pugixml.git lib/pugixml
|
||||
pushd lib/pugixml
|
||||
git checkout latest
|
||||
popd
|
||||
|
||||
# Base64
|
||||
git clone https://gist.github.com/f0fd86b6c73063283afe550bc5d77594.git lib/base64
|
173
src/bytearray.cpp
Normal file
173
src/bytearray.cpp
Normal file
|
@ -0,0 +1,173 @@
|
|||
/*
|
||||
Copyright 2021 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include <string.h>
|
||||
|
||||
#include <base64/Base64.h>
|
||||
|
||||
#include <bytearray.h>
|
||||
|
||||
namespace gourou
|
||||
{
|
||||
std::map<const unsigned char*, int> ByteArray::refCounter;
|
||||
|
||||
ByteArray::ByteArray():_data(0), _length(0)
|
||||
{}
|
||||
|
||||
ByteArray::ByteArray(const unsigned char* data, unsigned int length)
|
||||
{
|
||||
initData(data, length);
|
||||
}
|
||||
|
||||
ByteArray::ByteArray(const char* data, int length)
|
||||
{
|
||||
if (length == -1)
|
||||
length = strlen(data) + 1;
|
||||
|
||||
initData((const unsigned char*)data, (unsigned int) length);
|
||||
}
|
||||
|
||||
ByteArray::ByteArray(const std::string& str)
|
||||
{
|
||||
initData((unsigned char*)str.c_str(), (unsigned int)str.length() + 1);
|
||||
}
|
||||
|
||||
void ByteArray::initData(const unsigned char* data, unsigned int length)
|
||||
{
|
||||
_data = new unsigned char[length];
|
||||
memcpy((void*)_data, data, length);
|
||||
_length = length;
|
||||
|
||||
addRef();
|
||||
}
|
||||
|
||||
ByteArray::ByteArray(const ByteArray& other)
|
||||
{
|
||||
this->_data = other._data;
|
||||
this->_length = other._length;
|
||||
|
||||
addRef();
|
||||
}
|
||||
|
||||
ByteArray& ByteArray::operator=(const ByteArray& other)
|
||||
{
|
||||
delRef();
|
||||
|
||||
this->_data = other._data;
|
||||
this->_length = other._length;
|
||||
|
||||
addRef();
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
ByteArray::~ByteArray()
|
||||
{
|
||||
delRef();
|
||||
}
|
||||
|
||||
void ByteArray::addRef()
|
||||
{
|
||||
if (!_data) return;
|
||||
|
||||
if (refCounter.count(_data) == 0)
|
||||
refCounter[_data] = 1;
|
||||
else
|
||||
refCounter[_data]++;
|
||||
}
|
||||
|
||||
void ByteArray::delRef()
|
||||
{
|
||||
if (!_data) return;
|
||||
|
||||
if (refCounter[_data] == 1)
|
||||
{
|
||||
delete[] _data;
|
||||
refCounter.erase(_data);
|
||||
}
|
||||
else
|
||||
refCounter[_data]--;
|
||||
}
|
||||
|
||||
ByteArray ByteArray::fromBase64(const ByteArray& other)
|
||||
{
|
||||
std::string b64;
|
||||
|
||||
macaron::Base64::Decode(std::string((char*)other._data, other._length), b64);
|
||||
|
||||
return ByteArray(b64);
|
||||
}
|
||||
|
||||
ByteArray ByteArray::fromBase64(const char* data, int length)
|
||||
{
|
||||
std::string b64;
|
||||
|
||||
if (length == -1)
|
||||
length = strlen(data);
|
||||
|
||||
macaron::Base64::Decode(std::string(data, length), b64);
|
||||
|
||||
return ByteArray(b64);
|
||||
}
|
||||
|
||||
ByteArray ByteArray::fromBase64(const std::string& str)
|
||||
{
|
||||
return ByteArray::fromBase64(str.c_str(), str.length());
|
||||
}
|
||||
|
||||
std::string ByteArray::toBase64()
|
||||
{
|
||||
return macaron::Base64::Encode(std::string((char*)_data, _length));
|
||||
}
|
||||
|
||||
std::string ByteArray::toHex()
|
||||
{
|
||||
char* tmp = new char[_length*2+1];
|
||||
|
||||
for(int i=0; i<(int)_length; i++)
|
||||
sprintf(&tmp[i*2], "%02x", _data[i]);
|
||||
|
||||
tmp[_length*2] = 0;
|
||||
|
||||
std::string res = tmp;
|
||||
delete[] tmp;
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
void ByteArray::append(const unsigned char* data, unsigned int length)
|
||||
{
|
||||
const unsigned char* oldData = _data;
|
||||
unsigned char* newData = new unsigned char[_length+length];
|
||||
|
||||
memcpy(newData, oldData, _length);
|
||||
|
||||
delRef();
|
||||
|
||||
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()); }
|
||||
}
|
305
src/device.cpp
Normal file
305
src/device.cpp
Normal file
|
@ -0,0 +1,305 @@
|
|||
/*
|
||||
Copyright 2021 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/utsname.h>
|
||||
#include <pwd.h>
|
||||
#include <locale.h>
|
||||
|
||||
#include <libgourou.h>
|
||||
#include <libgourou_common.h>
|
||||
#include <libgourou_log.h>
|
||||
#include <device.h>
|
||||
|
||||
// From https://stackoverflow.com/questions/1779715/how-to-get-mac-address-of-your-machine-using-a-c-program/35242525
|
||||
#include <sys/ioctl.h>
|
||||
#include <net/if.h>
|
||||
#include <unistd.h>
|
||||
#include <netinet/in.h>
|
||||
#include <string.h>
|
||||
|
||||
int get_mac_address(unsigned char* mac_address)
|
||||
{
|
||||
struct ifreq ifr;
|
||||
struct ifconf ifc;
|
||||
char buf[1024];
|
||||
int success = 0;
|
||||
|
||||
int sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_IP);
|
||||
if (sock == -1) { EXCEPTION(gourou::DEV_MAC_ERROR, "Unable to create socket"); };
|
||||
|
||||
ifc.ifc_len = sizeof(buf);
|
||||
ifc.ifc_buf = buf;
|
||||
if (ioctl(sock, SIOCGIFCONF, &ifc) == -1) { EXCEPTION(gourou::DEV_MAC_ERROR, "SIOCGIFCONF ioctl failed"); }
|
||||
|
||||
struct ifreq* it = ifc.ifc_req;
|
||||
const struct ifreq* const end = it + (ifc.ifc_len / sizeof(struct ifreq));
|
||||
|
||||
for (; it != end; ++it) {
|
||||
strcpy(ifr.ifr_name, it->ifr_name);
|
||||
if (ioctl(sock, SIOCGIFFLAGS, &ifr) == 0) {
|
||||
if (! (ifr.ifr_flags & IFF_LOOPBACK)) { // don't count loopback
|
||||
if (ioctl(sock, SIOCGIFHWADDR, &ifr) == 0) {
|
||||
success = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
else { EXCEPTION(gourou::DEV_MAC_ERROR, "SIOCGIFFLAGS ioctl failed"); }
|
||||
}
|
||||
|
||||
if (success)
|
||||
{
|
||||
memcpy(mac_address, ifr.ifr_hwaddr.sa_data, 6);
|
||||
return 0;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
namespace gourou
|
||||
{
|
||||
Device::Device(DRMProcessor* processor):
|
||||
processor(processor)
|
||||
{}
|
||||
|
||||
Device::Device(DRMProcessor* processor, const std::string& deviceFile, const std::string& deviceKeyFile):
|
||||
processor(processor), deviceFile(deviceFile), deviceKeyFile(deviceKeyFile)
|
||||
{
|
||||
parseDeviceKeyFile();
|
||||
parseDeviceFile();
|
||||
}
|
||||
|
||||
/* SHA1(uid ":" username ":" macaddress ":" */
|
||||
std::string Device::makeSerial(bool random)
|
||||
{
|
||||
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};
|
||||
|
||||
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);
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
/* base64(SHA1 (serial + privateKey)) */
|
||||
std::string Device::makeFingerprint(const std::string& serial)
|
||||
{
|
||||
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);
|
||||
|
||||
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);
|
||||
|
||||
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;
|
||||
|
||||
appendTextElem(root, "adept:deviceClass", "Desktop");
|
||||
appendTextElem(root, "adept:deviceSerial", serial);
|
||||
appendTextElem(root, "adept:deviceName", sysname.nodename);
|
||||
appendTextElem(root, "adept:deviceType", "standalone");
|
||||
|
||||
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:fingerprint", fingerprint);
|
||||
|
||||
StringXMLWriter xmlWriter;
|
||||
deviceDoc.save(xmlWriter, " ");
|
||||
|
||||
GOUROU_LOG(DEBUG, "Create device file " << deviceFile);
|
||||
|
||||
writeFile(deviceFile, xmlWriter.getResult());
|
||||
}
|
||||
|
||||
void Device::createDeviceKeyFile()
|
||||
{
|
||||
unsigned char key[DEVICE_KEY_SIZE];
|
||||
|
||||
GOUROU_LOG(DEBUG, "Create device key file " << deviceKeyFile);
|
||||
|
||||
processor->getClient()->randBytes(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;
|
||||
|
||||
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->deviceFile = dirName + "/device.xml";
|
||||
device->deviceKeyFile = dirName + "/devicesalt";
|
||||
|
||||
try
|
||||
{
|
||||
device->parseDeviceKeyFile();
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
device->createDeviceKeyFile();
|
||||
device->parseDeviceKeyFile();
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
device->parseDeviceFile();
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
device->createDeviceFile(hobbes, randomSerial);
|
||||
device->parseDeviceFile();
|
||||
}
|
||||
|
||||
return device;
|
||||
}
|
||||
|
||||
const unsigned char* Device::getDeviceKey()
|
||||
{
|
||||
return deviceKey;
|
||||
}
|
||||
|
||||
void Device::parseDeviceFile()
|
||||
{
|
||||
pugi::xml_document doc;
|
||||
|
||||
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");
|
||||
|
||||
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");
|
||||
|
||||
properties[name.value()] = value.value();
|
||||
}
|
||||
}
|
||||
catch (gourou::Exception& e)
|
||||
{
|
||||
EXCEPTION(DEV_INVALID_DEVICE_FILE, "Invalid device file");
|
||||
}
|
||||
}
|
||||
|
||||
void Device::parseDeviceKeyFile()
|
||||
{
|
||||
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");
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
return _default;
|
||||
}
|
||||
|
||||
return properties[property];
|
||||
}
|
||||
|
||||
std::string Device::operator[](const std::string& property)
|
||||
{
|
||||
return getProperty(property);
|
||||
}
|
||||
}
|
91
src/fulfillment_item.cpp
Normal file
91
src/fulfillment_item.cpp
Normal file
|
@ -0,0 +1,91 @@
|
|||
/*
|
||||
Copyright 2021 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <fulfillment_item.h>
|
||||
#include <libgourou_common.h>
|
||||
#include "user.h"
|
||||
|
||||
namespace gourou
|
||||
{
|
||||
FulfillmentItem::FulfillmentItem(pugi::xml_document& doc, User* user)
|
||||
{
|
||||
metadatas = doc.select_node("//metadata").node();
|
||||
|
||||
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();
|
||||
|
||||
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 (!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("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
|
||||
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 "";
|
||||
|
||||
return path.node().first_child().value();
|
||||
}
|
||||
|
||||
std::string FulfillmentItem::getRights()
|
||||
{
|
||||
StringXMLWriter xmlWriter;
|
||||
rights.save(xmlWriter, " ");
|
||||
return xmlWriter.getResult();
|
||||
}
|
||||
|
||||
std::string FulfillmentItem::getDownloadURL()
|
||||
{
|
||||
return downloadURL;
|
||||
}
|
||||
}
|
662
src/libgourou.cpp
Normal file
662
src/libgourou.cpp
Normal file
|
@ -0,0 +1,662 @@
|
|||
/*
|
||||
Copyright 2021 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <arpa/inet.h>
|
||||
#include <sys/time.h>
|
||||
#include <time.h>
|
||||
|
||||
#include <libgourou.h>
|
||||
#include <libgourou_common.h>
|
||||
#include <libgourou_log.h>
|
||||
|
||||
#define ASN_NONE 0x00
|
||||
#define ASN_NS_TAG 0x01
|
||||
#define ASN_CHILD 0x02
|
||||
#define ASN_END_TAG 0x03
|
||||
#define ASN_TEXT 0x04
|
||||
#define ASN_ATTRIBUTE 0x05
|
||||
|
||||
namespace gourou
|
||||
{
|
||||
GOUROU_LOG_LEVEL logLevel = WARN;
|
||||
|
||||
DRMProcessor::DRMProcessor(DRMProcessorClient* client):client(client), device(0), user(0)
|
||||
{
|
||||
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)
|
||||
{
|
||||
if (!client)
|
||||
EXCEPTION(GOUROU_INVALID_CLIENT, "DRMProcessorClient is NULL");
|
||||
|
||||
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");
|
||||
}
|
||||
|
||||
DRMProcessor::~DRMProcessor()
|
||||
{
|
||||
if (device) delete device;
|
||||
if (user) delete user;
|
||||
}
|
||||
|
||||
DRMProcessor* DRMProcessor::createDRMProcessor(DRMProcessorClient* client, bool randomSerial, const std::string& dirName,
|
||||
const std::string& hobbes, const std::string& ACSServer)
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
void DRMProcessor::pushString(void* sha_ctx, const std::string& string)
|
||||
{
|
||||
int length = string.length();
|
||||
uint16_t nlength = htons(length);
|
||||
char c;
|
||||
|
||||
if (logLevel >= TRACE)
|
||||
printf("%02x %02x ", ((uint8_t*)&nlength)[0], ((uint8_t*)&nlength)[1]);
|
||||
|
||||
client->digestUpdate(sha_ctx, (unsigned char*)&nlength, sizeof(nlength));
|
||||
|
||||
for(int i=0; i<length; i++)
|
||||
{
|
||||
c = string[i];
|
||||
client->digestUpdate(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 >= TRACE)
|
||||
printf("%02x ", tag);
|
||||
}
|
||||
|
||||
void DRMProcessor::hashNode(const pugi::xml_node& root, void *sha_ctx, std::map<std::string,std::string> nsHash)
|
||||
{
|
||||
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());
|
||||
|
||||
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();
|
||||
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);
|
||||
|
||||
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);
|
||||
|
||||
// Must be parsed in reverse order
|
||||
for (pugi::xml_attribute attr = root.last_attribute();
|
||||
attr; attr = attr.previous_attribute())
|
||||
{
|
||||
if (std::string(attr.name()).find("xmlns") != std::string::npos)
|
||||
continue;
|
||||
|
||||
pushTag(sha_ctx, ASN_ATTRIBUTE);
|
||||
pushString(sha_ctx, "");
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
void DRMProcessor::hashNode(const pugi::xml_node& root, unsigned char* sha_out)
|
||||
{
|
||||
void* sha_ctx = client->createDigest("SHA1");
|
||||
|
||||
std::map<std::string, std::string> 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");
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
ByteArray DRMProcessor::sendRequest(const pugi::xml_document& document, const std::string& url)
|
||||
{
|
||||
StringXMLWriter xmlWriter;
|
||||
document.save(xmlWriter, " ");
|
||||
std::string xmlStr = xmlWriter.getResult();
|
||||
|
||||
return sendRequest(url, xmlStr, (const char*)"application/vnd.adobe.adept+xml");
|
||||
}
|
||||
|
||||
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 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"]);
|
||||
|
||||
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());
|
||||
}
|
||||
|
||||
FulfillmentItem* DRMProcessor::fulfill(const std::string& ACSMFile)
|
||||
{
|
||||
if (!user->getPKCS12().length())
|
||||
EXCEPTION(FF_NOT_ACTIVATED, "Device not activated");
|
||||
|
||||
pugi::xml_document acsmDoc;
|
||||
|
||||
if (!acsmDoc.load_file(ACSMFile.c_str(), pugi::parse_ws_pcdata_single))
|
||||
EXCEPTION(FF_INVALID_ACSM_FILE, "Invalid ACSM file " << ACSMFile);
|
||||
|
||||
GOUROU_LOG(INFO, "Fulfill " << ACSMFile);
|
||||
|
||||
// Build req file
|
||||
pugi::xml_document fulfillReq;
|
||||
|
||||
buildFulfillRequest(acsmDoc, fulfillReq);
|
||||
pugi::xpath_node root = fulfillReq.select_node("//adept:fulfill");
|
||||
pugi::xml_node rootNode = root.node();
|
||||
|
||||
// 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);
|
||||
|
||||
// Compute hash
|
||||
unsigned char sha_out[SHA1_LEN];
|
||||
|
||||
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");
|
||||
}
|
||||
|
||||
// Add removed HMAC
|
||||
appendTextElem(hmacParentNode, hmacNode.name(), hmacNode.first_child().value());
|
||||
|
||||
// Add base64 encoded signature
|
||||
ByteArray signature(res, sizeof(res));
|
||||
std::string b64Signature = signature.toBase64();
|
||||
|
||||
appendTextElem(rootNode, "adept:signature", b64Signature);
|
||||
|
||||
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";
|
||||
|
||||
ByteArray replyData = sendRequest(fulfillReq, operatorURL);
|
||||
|
||||
pugi::xml_document fulfillReply;
|
||||
|
||||
fulfillReply.load_string((const char*)replyData.data());
|
||||
|
||||
return new FulfillmentItem(fulfillReply, user);
|
||||
}
|
||||
|
||||
void DRMProcessor::download(FulfillmentItem* item, std::string path)
|
||||
{
|
||||
if (!item)
|
||||
EXCEPTION(DW_NO_ITEM, "No item");
|
||||
|
||||
ByteArray replyData = sendRequest(item->getDownloadURL());
|
||||
|
||||
writeFile(path, replyData);
|
||||
|
||||
GOUROU_LOG(INFO, "Download into " << path);
|
||||
|
||||
std::string rightsStr = item->getRights();
|
||||
|
||||
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)
|
||||
{
|
||||
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;
|
||||
signIn.append_attribute("method") = user->getLoginMethod().c_str();
|
||||
|
||||
unsigned char encryptedSignInData[RSA_KEY_SIZE];
|
||||
const unsigned char* deviceKey = device->getDeviceKey();
|
||||
|
||||
ByteArray _authenticationCertificate = ByteArray::fromBase64(authenticationCertificate);
|
||||
|
||||
// Build buffer <deviceKey> <len username> <username> <len password> <password>
|
||||
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);
|
||||
|
||||
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);
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
void DRMProcessor::signIn(const std::string& adobeID, const std::string& adobePassword)
|
||||
{
|
||||
pugi::xml_document signInRequest;
|
||||
std::string authenticationCertificate = user->getAuthenticationCertificate();
|
||||
|
||||
buildSignInRequest(signInRequest, adobeID, adobePassword, authenticationCertificate);
|
||||
|
||||
GOUROU_LOG(INFO, "SignIn " << adobeID);
|
||||
|
||||
std::string signInURL = user->getProperty("//adept:authURL");
|
||||
signInURL += "/SignInDirect";
|
||||
|
||||
ByteArray credentials = sendRequest(signInRequest, signInURL);
|
||||
|
||||
pugi::xml_document credentialsDoc;
|
||||
if (!credentialsDoc.load_buffer(credentials.data(), credentials.length()))
|
||||
EXCEPTION(SIGN_INVALID_CREDENTIALS, "Invalid credentials reply");
|
||||
|
||||
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;
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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 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"]);
|
||||
|
||||
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"]);
|
||||
|
||||
/*
|
||||
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());
|
||||
|
||||
time_t _time = time(0) + 10*60; // Cur time + 10 minutes
|
||||
struct tm* tm_info = localtime(&_time);
|
||||
char buffer[32];
|
||||
|
||||
strftime(buffer, sizeof(buffer), "%Y-%m-%dT%H:%M:%SZ", tm_info);
|
||||
appendTextElem(root, "adept:expiration", buffer);
|
||||
|
||||
appendTextElem(root, "adept:user", user->getUUID());
|
||||
}
|
||||
|
||||
void DRMProcessor::activateDevice()
|
||||
{
|
||||
pugi::xml_document activateReq;
|
||||
|
||||
GOUROU_LOG(INFO, "Activate device");
|
||||
|
||||
buildActivateReq(activateReq);
|
||||
|
||||
// Compute hash
|
||||
unsigned char sha_out[SHA1_LEN];
|
||||
|
||||
pugi::xml_node root = activateReq.select_node("adept:activate").node();
|
||||
hashNode(root, sha_out);
|
||||
|
||||
// Sign with private key
|
||||
ByteArray RSAKey = ByteArray::fromBase64(user->getPKCS12());
|
||||
unsigned char res[RSA_KEY_SIZE];
|
||||
ByteArray deviceKey(device->getDeviceKey(), Device::DEVICE_KEY_SIZE);
|
||||
|
||||
client->RSAPrivateEncrypt(RSAKey.data(), RSAKey.length(), RSAInterface::RSA_KEY_PKCS12,
|
||||
deviceKey.toBase64().c_str(),
|
||||
sha_out, sizeof(sha_out),
|
||||
res);
|
||||
|
||||
// Add base64 encoded signature
|
||||
ByteArray signature(res, sizeof(res));
|
||||
std::string b64Signature = signature.toBase64();
|
||||
|
||||
root = activateReq.select_node("adept:activate").node();
|
||||
appendTextElem(root, "adept:signature", b64Signature);
|
||||
|
||||
pugi::xml_document activationDoc;
|
||||
user->readActivation(activationDoc);
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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];
|
||||
|
||||
// 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);
|
||||
|
||||
ByteArray res(encrypted_data, outLen+16);
|
||||
|
||||
delete[] encrypted_data;
|
||||
|
||||
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];
|
||||
|
||||
client->AESDecrypt(CryptoInterface::CHAIN_CBC,
|
||||
deviceKey, 16, data, 16,
|
||||
data+16, len-16,
|
||||
decrypted_data, &outLen);
|
||||
|
||||
ByteArray res(decrypted_data, outLen);
|
||||
|
||||
delete[] decrypted_data;
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
std::string DRMProcessor::serializeRSAPublicKey(void* rsa)
|
||||
{
|
||||
unsigned char* data = 0;
|
||||
unsigned int len;
|
||||
|
||||
client->extractRSAPublicKey(rsa, &data, &len);
|
||||
|
||||
ByteArray res(data, len);
|
||||
|
||||
free(data);
|
||||
|
||||
return res.toBase64();
|
||||
}
|
||||
|
||||
std::string DRMProcessor::serializeRSAPrivateKey(void* rsa)
|
||||
{
|
||||
unsigned char* data = 0;
|
||||
unsigned int len;
|
||||
|
||||
client->extractRSAPrivateKey(rsa, &data, &len);
|
||||
|
||||
ByteArray res = encryptWithDeviceKey(data, len);
|
||||
|
||||
free(data);
|
||||
|
||||
return res.toBase64();
|
||||
}
|
||||
|
||||
int DRMProcessor::getLogLevel() {return (int)gourou::logLevel;}
|
||||
void DRMProcessor::setLogLevel(int logLevel) {gourou::logLevel = (GOUROU_LOG_LEVEL)logLevel;}
|
||||
}
|
1
src/pugixml.cpp
Symbolic link
1
src/pugixml.cpp
Symbolic link
|
@ -0,0 +1 @@
|
|||
../lib/pugixml/src/pugixml.cpp
|
198
src/user.cpp
Normal file
198
src/user.cpp
Normal file
|
@ -0,0 +1,198 @@
|
|||
/*
|
||||
Copyright 2021 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <libgourou.h>
|
||||
#include <libgourou_common.h>
|
||||
#include <libgourou_log.h>
|
||||
#include <user.h>
|
||||
|
||||
namespace gourou {
|
||||
User::User(DRMProcessor* processor):processor(processor) {}
|
||||
|
||||
User::User(DRMProcessor* processor, const std::string& activationFile):
|
||||
processor(processor), activationFile(activationFile)
|
||||
{
|
||||
parseActivationFile();
|
||||
}
|
||||
|
||||
void User::parseActivationFile(bool throwOnNull)
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
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; }
|
||||
std::string& User::getPKCS12() { return pkcs12; }
|
||||
std::string& User::getDeviceUUID() { return deviceUUID; }
|
||||
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");
|
||||
}
|
||||
|
||||
void User::updateActivationFile(const char* data)
|
||||
{
|
||||
GOUROU_LOG(INFO, "Update Activation file : " << std::endl << data);
|
||||
|
||||
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());
|
||||
}
|
||||
|
||||
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");
|
||||
|
||||
std::string res = xpathRes.node().first_child().value();
|
||||
return trim(res);
|
||||
}
|
||||
|
||||
User* User::createUser(DRMProcessor* processor, const std::string& dirName, const std::string& ACSServer)
|
||||
{
|
||||
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)
|
||||
}
|
||||
|
||||
User* user = new User(processor);
|
||||
bool doUpdate = 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");
|
||||
|
||||
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());
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
26
utils/LICENSE
Normal file
26
utils/LICENSE
Normal file
|
@ -0,0 +1,26 @@
|
|||
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.
|
||||
|
24
utils/Makefile
Normal file
24
utils/Makefile
Normal file
|
@ -0,0 +1,24 @@
|
|||
|
||||
TARGETS=acsmdownloader activate
|
||||
|
||||
CXXFLAGS=-Wall `pkg-config --cflags Qt5Core Qt5Network` -fPIC -I$(ROOT)/include -I$(ROOT)/lib/pugixml/src/
|
||||
LDFLAGS=`pkg-config --libs Qt5Core Qt5Network` -L$(ROOT) -lgourou -lcrypto -lzip
|
||||
|
||||
ifneq ($(DEBUG),)
|
||||
CXXFLAGS += -ggdb -O0
|
||||
else
|
||||
CXXFLAGS += -O2
|
||||
endif
|
||||
|
||||
all: $(TARGETS)
|
||||
|
||||
acsmdownloader: drmprocessorclientimpl.cpp acsmdownloader.cpp
|
||||
$(CXX) $(CXXFLAGS) $^ $(LDFLAGS) -o $@
|
||||
|
||||
activate: drmprocessorclientimpl.cpp activate.cpp
|
||||
$(CXX) $(CXXFLAGS) $^ $(LDFLAGS) -o $@
|
||||
|
||||
clean:
|
||||
rm -f $(TARGETS)
|
||||
|
||||
ultraclean: clean
|
255
utils/acsmdownloader.cpp
Normal file
255
utils/acsmdownloader.cpp
Normal file
|
@ -0,0 +1,255 @@
|
|||
/*
|
||||
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 <unistd.h>
|
||||
#include <getopt.h>
|
||||
|
||||
#include <iostream>
|
||||
|
||||
#include <QFile>
|
||||
#include <QDir>
|
||||
#include <QCoreApplication>
|
||||
#include <QRunnable>
|
||||
#include <QThreadPool>
|
||||
|
||||
#include <libgourou.h>
|
||||
#include "drmprocessorclientimpl.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 const char* outputFile = 0;
|
||||
static const char* outputDir = 0;
|
||||
static const char* defaultDirs[] = {
|
||||
".adept/",
|
||||
"./adobe-digital-editions/",
|
||||
"./.adobe-digital-editions/"
|
||||
};
|
||||
|
||||
|
||||
class ACSMDownloader: public QRunnable
|
||||
{
|
||||
public:
|
||||
ACSMDownloader(QCoreApplication* app):
|
||||
app(app)
|
||||
{
|
||||
setAutoDelete(false);
|
||||
}
|
||||
|
||||
void run()
|
||||
{
|
||||
try
|
||||
{
|
||||
DRMProcessorClientImpl client;
|
||||
gourou::DRMProcessor processor(&client, deviceFile, activationFile, devicekeyFile);
|
||||
|
||||
gourou::FulfillmentItem* item = processor.fulfill(acsmFile);
|
||||
|
||||
std::string filename;
|
||||
if (!outputFile)
|
||||
{
|
||||
filename = item->getMetadata("title");
|
||||
if (filename == "")
|
||||
filename = "output.epub";
|
||||
else
|
||||
filename += ".epub";
|
||||
}
|
||||
|
||||
if (outputDir)
|
||||
{
|
||||
QDir dir(outputDir);
|
||||
if (!dir.exists(outputDir))
|
||||
dir.mkpath(outputDir);
|
||||
|
||||
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;
|
||||
this->app->exit(1);
|
||||
}
|
||||
|
||||
this->app->exit(0);
|
||||
}
|
||||
|
||||
private:
|
||||
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 usage(const char* cmd)
|
||||
{
|
||||
std::cout << "Download EPUB file from ACSM request file" << std::endl;
|
||||
|
||||
std::cout << "Usage: " << cmd << " [(-d|--device-file) device.xml] [(-a|--activation-file) activation.xml] [(-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 <title.epub>)" << 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 << " " << "-h|--help" << "\t\t" << "This help" << std::endl;
|
||||
|
||||
std::cout << std::endl;
|
||||
std::cout << "Device file, activation file and device key file are optionals. If not set, they are looked into :" << std::endl;
|
||||
std::cout << " * 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();
|
||||
|
||||
while (1) {
|
||||
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' },
|
||||
{"help", no_argument, 0, 'h' },
|
||||
{0, 0, 0, 0 }
|
||||
};
|
||||
|
||||
c = getopt_long(argc, argv, "d:a:k:O:o:f:vh",
|
||||
long_options, &option_index);
|
||||
if (c == -1)
|
||||
break;
|
||||
|
||||
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 'h':
|
||||
usage(argv[0]);
|
||||
return 0;
|
||||
break;
|
||||
default:
|
||||
usage(argv[0]);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
gourou::DRMProcessor::setLogLevel(verbose);
|
||||
|
||||
if (!acsmFile || (outputDir && !outputDir[0]) ||
|
||||
(outputFile && !outputFile[0]))
|
||||
{
|
||||
usage(argv[0]);
|
||||
return -1;
|
||||
}
|
||||
|
||||
QCoreApplication app(argc, argv);
|
||||
ACSMDownloader downloader(&app);
|
||||
|
||||
int i;
|
||||
for (i=0; i<(int)ARRAY_SIZE(files); i++)
|
||||
{
|
||||
*files[i] = findFile(*files[i]);
|
||||
if (!*files[i])
|
||||
{
|
||||
std::cout << "Error : " << *files[i] << " doesn't exists" << std::endl;
|
||||
ret = -1;
|
||||
goto end;
|
||||
}
|
||||
}
|
||||
|
||||
QFile file(acsmFile);
|
||||
if (!file.exists())
|
||||
{
|
||||
std::cout << "Error : " << acsmFile << " doesn't exists" << std::endl;
|
||||
ret = -1;
|
||||
goto end;
|
||||
}
|
||||
|
||||
QThreadPool::globalInstance()->start(&downloader);
|
||||
|
||||
ret = app.exec();
|
||||
|
||||
end:
|
||||
for (i=0; i<(int)ARRAY_SIZE(files); i++)
|
||||
{
|
||||
if (*files[i])
|
||||
free((void*)*files[i]);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
263
utils/activate.cpp
Normal file
263
utils/activate.cpp
Normal file
|
@ -0,0 +1,263 @@
|
|||
/*
|
||||
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 <unistd.h>
|
||||
#include <getopt.h>
|
||||
#include <stdlib.h>
|
||||
#include <termios.h>
|
||||
|
||||
#include <iostream>
|
||||
#include <ostream>
|
||||
|
||||
#include <QFile>
|
||||
#include <QDir>
|
||||
#include <QCoreApplication>
|
||||
#include <QRunnable>
|
||||
#include <QThreadPool>
|
||||
|
||||
#include <libgourou.h>
|
||||
#include "drmprocessorclientimpl.h"
|
||||
|
||||
#define ARRAY_SIZE(arr) (sizeof(arr)/sizeof(arr[0]))
|
||||
|
||||
static const char* username = 0;
|
||||
static const char* password = 0;
|
||||
static const char* outputDir = 0;
|
||||
static const char* hobbesVersion = HOBBES_DEFAULT_VERSION;
|
||||
static bool randomSerial = false;
|
||||
|
||||
// From http://www.cplusplus.com/articles/E6vU7k9E/
|
||||
static int getch() {
|
||||
int ch;
|
||||
struct termios t_old, t_new;
|
||||
|
||||
tcgetattr(STDIN_FILENO, &t_old);
|
||||
t_new = t_old;
|
||||
t_new.c_lflag &= ~(ICANON | ECHO);
|
||||
tcsetattr(STDIN_FILENO, TCSANOW, &t_new);
|
||||
|
||||
ch = getchar();
|
||||
|
||||
tcsetattr(STDIN_FILENO, TCSANOW, &t_old);
|
||||
return ch;
|
||||
}
|
||||
|
||||
static std::string getpass(const char *prompt, bool show_asterisk=false)
|
||||
{
|
||||
const char BACKSPACE=127;
|
||||
const char RETURN=10;
|
||||
|
||||
std::string password;
|
||||
unsigned char ch=0;
|
||||
|
||||
std::cout <<prompt;
|
||||
|
||||
while((ch=getch())!= RETURN)
|
||||
{
|
||||
if(ch==BACKSPACE)
|
||||
{
|
||||
if(password.length()!=0)
|
||||
{
|
||||
if(show_asterisk)
|
||||
std::cout <<"\b \b";
|
||||
password.resize(password.length()-1);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
password+=ch;
|
||||
if(show_asterisk)
|
||||
std::cout <<'*';
|
||||
}
|
||||
}
|
||||
std::cout <<std::endl;
|
||||
return password;
|
||||
}
|
||||
|
||||
|
||||
class Activate: public QRunnable
|
||||
{
|
||||
public:
|
||||
Activate(QCoreApplication* app):
|
||||
app(app)
|
||||
{
|
||||
setAutoDelete(false);
|
||||
}
|
||||
|
||||
void run()
|
||||
{
|
||||
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;
|
||||
this->app->exit(1);
|
||||
}
|
||||
|
||||
this->app->exit(0);
|
||||
}
|
||||
|
||||
private:
|
||||
QCoreApplication* app;
|
||||
};
|
||||
|
||||
static void usage(const char* cmd)
|
||||
{
|
||||
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 << " " << "-h|--help" << "\t\t" << "This help" << std::endl;
|
||||
|
||||
std::cout << std::endl;
|
||||
}
|
||||
|
||||
static const char* abspath(const char* filename)
|
||||
{
|
||||
const char* root = getcwd(0, PATH_MAX);
|
||||
QString fullPath = QString(root) + QString("/") + QString(filename);
|
||||
const char* res = strdup(fullPath.toStdString().c_str());
|
||||
|
||||
free((void*)root);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
int main(int argc, char** argv)
|
||||
{
|
||||
int c, ret = -1;
|
||||
const char* _outputDir = outputDir;
|
||||
int verbose = gourou::DRMProcessor::getLogLevel();
|
||||
|
||||
while (1) {
|
||||
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' },
|
||||
{"help", no_argument, 0, 'h' },
|
||||
{0, 0, 0, 0 }
|
||||
};
|
||||
|
||||
c = getopt_long(argc, argv, "u:p:O:H:rvh",
|
||||
long_options, &option_index);
|
||||
if (c == -1)
|
||||
break;
|
||||
|
||||
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 'h':
|
||||
usage(argv[0]);
|
||||
return 0;
|
||||
break;
|
||||
case 'r':
|
||||
randomSerial = true;
|
||||
break;
|
||||
default:
|
||||
usage(argv[0]);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
gourou::DRMProcessor::setLogLevel(verbose);
|
||||
|
||||
if (!username)
|
||||
{
|
||||
usage(argv[0]);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!_outputDir || _outputDir[0] == 0)
|
||||
{
|
||||
outputDir = abspath(DEFAULT_ADEPT_DIR);
|
||||
}
|
||||
else
|
||||
{
|
||||
// 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);
|
||||
std::string pass = getpass((const char*)prompt, false);
|
||||
password = pass.c_str();
|
||||
}
|
||||
|
||||
QCoreApplication app(argc, argv);
|
||||
|
||||
Activate activate(&app);
|
||||
QThreadPool::globalInstance()->start(&activate);
|
||||
|
||||
ret = app.exec();
|
||||
|
||||
free((void*)outputDir);
|
||||
return ret;
|
||||
}
|
385
utils/drmprocessorclientimpl.cpp
Normal file
385
utils/drmprocessorclientimpl.cpp
Normal file
|
@ -0,0 +1,385 @@
|
|||
/*
|
||||
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 <openssl/rand.h>
|
||||
#include <openssl/pkcs12.h>
|
||||
#include <openssl/evp.h>
|
||||
#include <openssl/err.h>
|
||||
|
||||
#include <QCoreApplication>
|
||||
#include <QNetworkReply>
|
||||
#include <QNetworkRequest>
|
||||
#include <QNetworkAccessManager>
|
||||
#include <QFile>
|
||||
|
||||
#include <zip.h>
|
||||
|
||||
#include <libgourou_common.h>
|
||||
#include <libgourou_log.h>
|
||||
#include "drmprocessorclientimpl.h"
|
||||
|
||||
/* Digest interface */
|
||||
void* DRMProcessorClientImpl::createDigest(const std::string& digestName)
|
||||
{
|
||||
EVP_MD_CTX *sha_ctx = EVP_MD_CTX_new();
|
||||
const EVP_MD* md = EVP_get_digestbyname(digestName.c_str());
|
||||
EVP_DigestInit(sha_ctx, md);
|
||||
|
||||
return sha_ctx;
|
||||
}
|
||||
|
||||
int DRMProcessorClientImpl::digestUpdate(void* handler, unsigned char* data, unsigned int length)
|
||||
{
|
||||
return EVP_DigestUpdate((EVP_MD_CTX *)handler, data, length);
|
||||
}
|
||||
|
||||
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);
|
||||
return res;
|
||||
}
|
||||
|
||||
int DRMProcessorClientImpl::digest(const std::string& digestName, unsigned char* data, unsigned int length, unsigned char* digestOut)
|
||||
{
|
||||
void* handler = createDigest(digestName);
|
||||
if (!handler)
|
||||
return -1;
|
||||
if (digestUpdate(handler, data, length))
|
||||
return -1;
|
||||
return digestFinalize(handler, digestOut);
|
||||
}
|
||||
|
||||
/* Random interface */
|
||||
void DRMProcessorClientImpl::randBytes(unsigned char* bytesOut, unsigned int length)
|
||||
{
|
||||
RAND_bytes(bytesOut, length);
|
||||
}
|
||||
|
||||
/* HTTP interface */
|
||||
std::string DRMProcessorClientImpl::sendHTTPRequest(const std::string& URL, const std::string& POSTData, const std::string& contentType)
|
||||
{
|
||||
QNetworkRequest request(QUrl(URL.c_str()));
|
||||
QNetworkAccessManager networkManager;
|
||||
QByteArray replyData;
|
||||
|
||||
GOUROU_LOG(gourou::INFO, "Send request to " << URL);
|
||||
if (POSTData.size())
|
||||
{
|
||||
GOUROU_LOG(gourou::DEBUG, "<<< " << std::endl << POSTData);
|
||||
}
|
||||
|
||||
request.setRawHeader("Accept", "*/*");
|
||||
request.setRawHeader("User-Agent", "book2png");
|
||||
if (contentType.size())
|
||||
request.setRawHeader("Content-Type", contentType.c_str());
|
||||
|
||||
QNetworkReply* reply;
|
||||
|
||||
if (POSTData.size())
|
||||
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();
|
||||
|
||||
replyData = reply->readAll();
|
||||
if (reply->rawHeader("Content-Type") == "application/vnd.adobe.adept+xml")
|
||||
{
|
||||
GOUROU_LOG(gourou::DEBUG, ">>> " << std::endl << replyData.data());
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
PKCS12 * pkcs12;
|
||||
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));
|
||||
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 (ret < 0)
|
||||
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();
|
||||
RSA * rsa = RSA_new();
|
||||
BN_set_word(bn, 0x10001);
|
||||
RSA_generate_key_ex(rsa, keyLengthBits, bn, 0);
|
||||
BN_free(bn);
|
||||
|
||||
return rsa;
|
||||
}
|
||||
|
||||
void DRMProcessorClientImpl::destroyRSAHandler(void* 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, 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)
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
/* Crypto interface */
|
||||
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 = AESEncryptInit(chaining, key, keyLength, iv, ivLength);
|
||||
AESEncryptUpdate(handler, dataIn, dataInLength, dataOut, dataOutLength);
|
||||
AESEncryptFinalize(handler, dataOut+*dataOutLength, dataOutLength);
|
||||
}
|
||||
|
||||
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();
|
||||
|
||||
switch(keyLength)
|
||||
{
|
||||
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::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();
|
||||
|
||||
switch(keyLength)
|
||||
{
|
||||
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::AESEncryptUpdate(void* handler, const unsigned char* dataIn, unsigned int dataInLength,
|
||||
unsigned char* dataOut, unsigned int* dataOutLength)
|
||||
{
|
||||
EVP_EncryptUpdate((EVP_CIPHER_CTX*)handler, dataOut, (int*)dataOutLength, dataIn, dataInLength);
|
||||
}
|
||||
|
||||
void DRMProcessorClientImpl::AESEncryptFinalize(void* handler,
|
||||
unsigned char* dataOut, unsigned int* dataOutLength)
|
||||
{
|
||||
int len;
|
||||
EVP_EncryptFinal_ex((EVP_CIPHER_CTX*)handler, dataOut, &len);
|
||||
*dataOutLength += len;
|
||||
EVP_CIPHER_CTX_free((EVP_CIPHER_CTX*)handler);
|
||||
}
|
||||
|
||||
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 = AESDecryptInit(chaining, key, keyLength, iv, ivLength);
|
||||
AESDecryptUpdate(handler, dataIn, dataInLength, dataOut, dataOutLength);
|
||||
AESDecryptFinalize(handler, dataOut+*dataOutLength, dataOutLength);
|
||||
}
|
||||
|
||||
void DRMProcessorClientImpl::AESDecryptUpdate(void* handler, const unsigned char* dataIn, unsigned int dataInLength,
|
||||
unsigned char* dataOut, unsigned int* dataOutLength)
|
||||
{
|
||||
EVP_DecryptUpdate((EVP_CIPHER_CTX*)handler, dataOut, (int*)dataOutLength, dataIn, dataInLength);
|
||||
}
|
||||
|
||||
void DRMProcessorClientImpl::AESDecryptFinalize(void* handler, unsigned char* dataOut, unsigned int* dataOutLength)
|
||||
{
|
||||
int len;
|
||||
EVP_DecryptFinal_ex((EVP_CIPHER_CTX*)handler, dataOut, &len);
|
||||
*dataOutLength += len;
|
||||
EVP_CIPHER_CTX_free((EVP_CIPHER_CTX*)handler);
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
return handler;
|
||||
}
|
||||
|
||||
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 " << zip_strerror((zip_t *)handler));
|
||||
|
||||
if (!(sb.valid & (ZIP_STAT_INDEX|ZIP_STAT_SIZE)))
|
||||
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);
|
||||
|
||||
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, const std::string& content)
|
||||
{
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
||||
void DRMProcessorClientImpl::zipDeleteFile(void* handler, const std::string& path)
|
||||
{
|
||||
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());
|
||||
|
||||
if (zip_delete((zip_t*)handler, idx))
|
||||
EXCEPTION(gourou::CLIENT_ZIP_ERROR, "Zip error " << zip_strerror((zip_t *)handler));
|
||||
}
|
||||
|
||||
void DRMProcessorClientImpl::zipClose(void* handler)
|
||||
{
|
||||
zip_close((zip_t*)handler);
|
||||
}
|
110
utils/drmprocessorclientimpl.h
Normal file
110
utils/drmprocessorclientimpl.h
Normal file
|
@ -0,0 +1,110 @@
|
|||
/*
|
||||
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.
|
||||
*/
|
||||
|
||||
#ifndef _DRMPROCESSORCLIENTIMPL_H_
|
||||
#define _DRMPROCESSORCLIENTIMPL_H_
|
||||
|
||||
#include <string>
|
||||
|
||||
#include <drmprocessorclient.h>
|
||||
|
||||
class DRMProcessorClientImpl : public gourou::DRMProcessorClient
|
||||
{
|
||||
public:
|
||||
/* Digest interface */
|
||||
virtual void* createDigest(const std::string& digestName);
|
||||
virtual int digestUpdate(void* handler, unsigned char* data, unsigned int length);
|
||||
virtual int digestFinalize(void* handler,unsigned char* digestOut);
|
||||
virtual int digest(const std::string& digestName, unsigned char* data, unsigned int length, unsigned char* digestOut);
|
||||
|
||||
/* 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(""));
|
||||
|
||||
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 RSAPublicEncrypt(const unsigned char* RSAKey, unsigned int RSAKeyLength,
|
||||
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);
|
||||
|
||||
/* Crypto interface */
|
||||
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* AESEncryptInit(CHAINING_MODE chaining,
|
||||
const unsigned char* key, unsigned int keyLength,
|
||||
const unsigned char* iv=0, unsigned int ivLength=0);
|
||||
|
||||
|
||||
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 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* AESDecryptInit(CHAINING_MODE chaining,
|
||||
const unsigned char* key, unsigned int keyLength,
|
||||
const unsigned char* iv=0, unsigned int ivLength=0);
|
||||
|
||||
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 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
|
Loading…
Reference in New Issue
Block a user