From 2f2e4e193e241d82c929a548d02b2da5fbe4dd8e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9gory=20Soutad=C3=A9?= Date: Wed, 23 Mar 2022 21:05:56 +0100 Subject: [PATCH 01/94] Add resume option to acsmdownloader --- include/drmprocessorclient.h | 3 ++- include/libgourou.h | 6 ++++-- include/libgourou_common.h | 12 +++++++++--- src/libgourou.cpp | 10 +++++----- utils/acsmdownloader.cpp | 12 +++++++++--- utils/drmprocessorclientimpl.cpp | 20 ++++++++++++++++---- utils/drmprocessorclientimpl.h | 2 +- 7 files changed, 46 insertions(+), 19 deletions(-) diff --git a/include/drmprocessorclient.h b/include/drmprocessorclient.h index 7a28988..6b02b81 100644 --- a/include/drmprocessorclient.h +++ b/include/drmprocessorclient.h @@ -99,10 +99,11 @@ namespace gourou * @param contentType Optional content type of POST Data * @param responseHeaders Optional Response headers of HTTP request * @param fd Optional file descriptor to write request result + * @param resume false if target file should be truncated, true to try resume download (works only in combination with a valid fd) * * @return data of HTTP response */ - virtual std::string sendHTTPRequest(const std::string& URL, const std::string& POSTData=std::string(""), const std::string& contentType=std::string(""), std::map* responseHeaders=0, int fd=0) = 0; + virtual std::string sendHTTPRequest(const std::string& URL, const std::string& POSTData=std::string(""), const std::string& contentType=std::string(""), std::map* responseHeaders=0, int fd=0, bool resume=false) = 0; }; class RSAInterface diff --git a/include/libgourou.h b/include/libgourou.h index 8146814..abf72d5 100644 --- a/include/libgourou.h +++ b/include/libgourou.h @@ -81,10 +81,11 @@ namespace gourou * * @param item Item from fulfill() method * @param path Output file path + * @param resume false if target file should be truncated, true to try resume download * * @return Type of downloaded item */ - ITEM_TYPE download(FulfillmentItem* item, std::string path); + ITEM_TYPE download(FulfillmentItem* item, std::string path, bool resume=false); /** * @brief SignIn into ACS Server (required to activate device) @@ -135,10 +136,11 @@ namespace gourou * @param contentType Optional content type of POST Data * @param responseHeaders Optional Response headers of HTTP request * @param fd Optional File descriptor to write received data + * @param resume false if target file should be truncated, true to try resume download (works only in combination of a valid fd) * * @return data of HTTP response */ - ByteArray sendRequest(const std::string& URL, const std::string& POSTData=std::string(), const char* contentType=0, std::map* responseHeaders=0, int fd=0); + ByteArray sendRequest(const std::string& URL, const std::string& POSTData=std::string(), const char* contentType=0, std::map* responseHeaders=0, int fd=0, bool resume=false); /** * @brief Send HTTP POST request to URL with document as POSTData diff --git a/include/libgourou_common.h b/include/libgourou_common.h index f640a9f..298ec1f 100644 --- a/include/libgourou_common.h +++ b/include/libgourou_common.h @@ -287,13 +287,19 @@ namespace gourou } /** - * @brief Open a file descriptor on path. If it already exists, it's truncated + * @brief Open a file descriptor on path. If it already exists and truncate == true, it's truncated * * @return Created fd, must be closed */ - static inline int createNewFile(std::string path) + static inline int createNewFile(std::string path, bool truncate=true) { - int fd = open(path.c_str(), O_CREAT|O_WRONLY|O_TRUNC, S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH); + int options = O_CREAT|O_WRONLY; + if (truncate) + options |= O_TRUNC; + else + options |= O_APPEND; + + int fd = open(path.c_str(), options, S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH); if (fd <= 0) EXCEPTION(GOUROU_FILE_ERROR, "Unable to create " << path); diff --git a/src/libgourou.cpp b/src/libgourou.cpp index a8e3625..6ec3e07 100644 --- a/src/libgourou.cpp +++ b/src/libgourou.cpp @@ -300,11 +300,11 @@ namespace gourou appendTextElem(root, "adept:expiration", buffer); } - ByteArray DRMProcessor::sendRequest(const std::string& URL, const std::string& POSTdata, const char* contentType, std::map* responseHeaders, int fd) + ByteArray DRMProcessor::sendRequest(const std::string& URL, const std::string& POSTdata, const char* contentType, std::map* responseHeaders, int fd, bool resume) { if (contentType == 0) contentType = ""; - std::string reply = client->sendHTTPRequest(URL, POSTdata, contentType, responseHeaders, fd); + std::string reply = client->sendHTTPRequest(URL, POSTdata, contentType, responseHeaders, fd, resume); if (fd) return ByteArray(); @@ -583,7 +583,7 @@ namespace gourou return new FulfillmentItem(fulfillReply, user); } - DRMProcessor::ITEM_TYPE DRMProcessor::download(FulfillmentItem* item, std::string path) + DRMProcessor::ITEM_TYPE DRMProcessor::download(FulfillmentItem* item, std::string path, bool resume) { ITEM_TYPE res = EPUB; @@ -592,9 +592,9 @@ namespace gourou std::map headers; - int fd = createNewFile(path); + int fd = createNewFile(path, !resume); - sendRequest(item->getDownloadURL(), "", 0, &headers, fd); + sendRequest(item->getDownloadURL(), "", 0, &headers, fd, resume); close(fd); diff --git a/utils/acsmdownloader.cpp b/utils/acsmdownloader.cpp index aeb31b1..360cfda 100644 --- a/utils/acsmdownloader.cpp +++ b/utils/acsmdownloader.cpp @@ -42,6 +42,7 @@ static const char* acsmFile = 0; static bool exportPrivateKey = false; static const char* outputFile = 0; static const char* outputDir = 0; +static bool resume = false; class ACSMDownloader @@ -104,7 +105,7 @@ public: filename = std::string(outputDir) + "/" + filename; } - gourou::DRMProcessor::ITEM_TYPE type = processor.download(item, filename); + gourou::DRMProcessor::ITEM_TYPE type = processor.download(item, filename, resume); if (!outputFile) { @@ -133,7 +134,7 @@ static void usage(const char* cmd) { std::cout << "Download EPUB file from ACSM request file" << std::endl; - std::cout << "Usage: " << cmd << " [(-d|--device-file) device.xml] [(-a|--activation-file) activation.xml] [(-k|--device-key-file) devicesalt] [(-O|--output-dir) dir] [(-o|--output-file) output(.epub|.pdf|.der)] [(-v|--verbose)] [(-h|--help)] (-f|--acsm-file) file.acsm|(-e|--export-private-key)" << std::endl << std::endl; + std::cout << "Usage: " << cmd << " [(-d|--device-file) device.xml] [(-a|--activation-file) activation.xml] [(-k|--device-key-file) devicesalt] [(-O|--output-dir) dir] [(-o|--output-file) output(.epub|.pdf|.der)] [(-r|--resume)] [(-v|--verbose)] [(-h|--help)] (-f|--acsm-file) file.acsm|(-e|--export-private-key)" << std::endl << std::endl; std::cout << " " << "-d|--device-file" << "\t" << "device.xml file from eReader" << std::endl; std::cout << " " << "-a|--activation-file" << "\t" << "activation.xml file from eReader" << std::endl; @@ -142,6 +143,7 @@ static void usage(const char* cmd) std::cout << " " << "-o|--output-file" << "\t" << "Optional output filename (default )" << std::endl; std::cout << " " << "-f|--acsm-file" << "\t" << "ACSM request file for epub download" << std::endl; std::cout << " " << "-e|--export-private-key"<< "\t" << "Export private key in DER format" << std::endl; + std::cout << " " << "-r|--resume" << "\t\t" << "Try to resume download (in case of previous failure)" << std::endl; std::cout << " " << "-v|--verbose" << "\t\t" << "Increase verbosity, can be set multiple times" << std::endl; std::cout << " " << "-V|--version" << "\t\t" << "Display libgourou version" << std::endl; std::cout << " " << "-h|--help" << "\t\t" << "This help" << std::endl; @@ -171,13 +173,14 @@ int main(int argc, char** argv) {"output-file", required_argument, 0, 'o' }, {"acsm-file", required_argument, 0, 'f' }, {"export-private-key",no_argument, 0, 'e' }, + {"resume", no_argument, 0, 'r' }, {"verbose", no_argument, 0, 'v' }, {"version", no_argument, 0, 'V' }, {"help", no_argument, 0, 'h' }, {0, 0, 0, 0 } }; - c = getopt_long(argc, argv, "d:a:k:O:o:f:evVh", + c = getopt_long(argc, argv, "d:a:k:O:o:f:ervVh", long_options, &option_index); if (c == -1) break; @@ -204,6 +207,9 @@ int main(int argc, char** argv) case 'e': exportPrivateKey = true; break; + case 'r': + resume = true; + break; case 'v': verbose++; break; diff --git a/utils/drmprocessorclientimpl.cpp b/utils/drmprocessorclientimpl.cpp index c9ebaad..795d3c9 100644 --- a/utils/drmprocessorclientimpl.cpp +++ b/utils/drmprocessorclientimpl.cpp @@ -175,7 +175,7 @@ static size_t curlHeaders(char *buffer, size_t size, size_t nitems, void *userda return size*nitems; } -std::string DRMProcessorClientImpl::sendHTTPRequest(const std::string& URL, const std::string& POSTData, const std::string& contentType, std::map* responseHeaders, int fd) +std::string DRMProcessorClientImpl::sendHTTPRequest(const std::string& URL, const std::string& POSTData, const std::string& contentType, std::map* responseHeaders, int fd, bool resume) { gourou::ByteArray replyData; std::map localHeaders; @@ -191,6 +191,18 @@ std::string DRMProcessorClientImpl::sendHTTPRequest(const std::string& URL, cons unsigned prevDownloadedBytes; downloadedBytes = 0; + if (fd && resume) + { + struct stat _stat; + if (!fstat(fd, &_stat)) + { + GOUROU_LOG(gourou::WARN, "Resume download @ " << _stat.st_size << " bytes"); + downloadedBytes = _stat.st_size; + } + else + GOUROU_LOG(gourou::WARN, "Want to resume, but fstat failed"); + } + CURL *curl = curl_easy_init(); CURLcode res; curl_easy_setopt(curl, CURLOPT_URL, URL.c_str()); @@ -244,7 +256,7 @@ std::string DRMProcessorClientImpl::sendHTTPRequest(const std::string& URL, cons // Connexion failed, wait & retry if (res == CURLE_COULDNT_CONNECT) { - GOUROU_LOG(gourou::WARN, "Connection failed, attempt " << (i+1) << "/" << HTTP_REQ_MAX_RETRY); + GOUROU_LOG(gourou::WARN, "\nConnection failed, attempt " << (i+1) << "/" << HTTP_REQ_MAX_RETRY); } // Transfer failed but some data has been received // --> try again without incrementing tries @@ -252,11 +264,11 @@ std::string DRMProcessorClientImpl::sendHTTPRequest(const std::string& URL, cons { if (prevDownloadedBytes != downloadedBytes) { - GOUROU_LOG(gourou::WARN, "Connection broken, but data received, try again"); + GOUROU_LOG(gourou::WARN, "\nConnection broken, but data received, try again"); i--; } else - GOUROU_LOG(gourou::WARN, "Connection broken and no data received, attempt " << (i+1) << "/" << HTTP_REQ_MAX_RETRY); + GOUROU_LOG(gourou::WARN, "\nConnection broken and no data received, attempt " << (i+1) << "/" << HTTP_REQ_MAX_RETRY); } // Other error --> fail else diff --git a/utils/drmprocessorclientimpl.h b/utils/drmprocessorclientimpl.h index d368f00..53a4a03 100644 --- a/utils/drmprocessorclientimpl.h +++ b/utils/drmprocessorclientimpl.h @@ -46,7 +46,7 @@ public: virtual void randBytes(unsigned char* bytesOut, unsigned int length); /* HTTP interface */ - virtual std::string sendHTTPRequest(const std::string& URL, const std::string& POSTData=std::string(""), const std::string& contentType=std::string(""), std::map* responseHeaders=0, int fd=0); + virtual std::string sendHTTPRequest(const std::string& URL, const std::string& POSTData=std::string(""), const std::string& contentType=std::string(""), std::map* responseHeaders=0, int fd=0, bool resume=false); virtual void RSAPrivateEncrypt(const unsigned char* RSAKey, unsigned int RSAKeyLength, const RSA_KEY_TYPE keyType, const std::string& password, From 9556fe862fd461451ad7bd5e5dc1753db0c0bb9b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9gory=20Soutad=C3=A9?= Date: Sun, 3 Apr 2022 09:28:19 +0200 Subject: [PATCH 02/94] Optimization : Add signature node into signNode() instead of returing it and be added after --- include/libgourou.h | 2 +- src/libgourou.cpp | 19 ++++++------------- 2 files changed, 7 insertions(+), 14 deletions(-) diff --git a/include/libgourou.h b/include/libgourou.h index abf72d5..88d8c63 100644 --- a/include/libgourou.h +++ b/include/libgourou.h @@ -210,7 +210,7 @@ namespace gourou void pushTag(void* sha_ctx, uint8_t tag); void hashNode(const pugi::xml_node& root, void *sha_ctx, std::map nsHash); void hashNode(const pugi::xml_node& root, unsigned char* sha_out); - std::string signNode(const pugi::xml_node& rootNode); + void signNode(pugi::xml_node& rootNode); void addNonce(pugi::xml_node& root); void buildAuthRequest(pugi::xml_document& authReq); void buildInitLicenseServiceRequest(pugi::xml_document& initLicReq, std::string operatorURL); diff --git a/src/libgourou.cpp b/src/libgourou.cpp index 6ec3e07..b77bea8 100644 --- a/src/libgourou.cpp +++ b/src/libgourou.cpp @@ -236,7 +236,7 @@ namespace gourou } } - std::string DRMProcessor::signNode(const pugi::xml_node& rootNode) + void DRMProcessor::signNode(pugi::xml_node& rootNode) { // Compute hash unsigned char sha_out[SHA1_LEN]; @@ -260,9 +260,8 @@ namespace gourou printf("\n"); } - ByteArray signature(res, sizeof(res)); - - return signature.toBase64(); + std::string signature = ByteArray(res, sizeof(res)).toBase64(); + appendTextElem(rootNode, "adept:signature", signature); } void DRMProcessor::addNonce(pugi::xml_node& root) @@ -368,8 +367,7 @@ namespace gourou addNonce(root); appendTextElem(root, "adept:user", user->getUUID()); - std::string signature = signNode(root); - appendTextElem(root, "adept:signature", signature); + signNode(root); } void DRMProcessor::doOperatorAuth(std::string operatorURL) @@ -530,12 +528,10 @@ namespace gourou hmacParentNode.remove_child(hmacNode); - std::string signature = signNode(rootNode); + signNode(rootNode); // Add removed HMAC appendTextElem(hmacParentNode, hmacNode.name(), hmacNode.first_child().value()); - - appendTextElem(rootNode, "adept:signature", signature); pugi::xpath_node node = acsmDoc.select_node("//operatorURL"); if (!node) @@ -823,10 +819,7 @@ namespace gourou pugi::xml_node root = activateReq.select_node("adept:activate").node(); - std::string signature = signNode(root); - - root = activateReq.select_node("adept:activate").node(); - appendTextElem(root, "adept:signature", signature); + signNode(root); pugi::xml_document activationDoc; user->readActivation(activationDoc); From 2e7e352e3547a60b63adca2491cb965091b357d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9gory=20Soutad=C3=A9?= Date: Sun, 3 Apr 2022 09:29:40 +0200 Subject: [PATCH 03/94] Utils: use trim functions from libgourou_common.h (avoid code duplication) --- utils/drmprocessorclientimpl.cpp | 25 ++----------------------- 1 file changed, 2 insertions(+), 23 deletions(-) diff --git a/utils/drmprocessorclientimpl.cpp b/utils/drmprocessorclientimpl.cpp index 795d3c9..39a7dfa 100644 --- a/utils/drmprocessorclientimpl.cpp +++ b/utils/drmprocessorclientimpl.cpp @@ -45,27 +45,6 @@ #include #include "drmprocessorclientimpl.h" -// https://stackoverflow.com/questions/216823/how-to-trim-a-stdstring -// trim from start (in place) -static inline void ltrim(std::string &s) { - s.erase(s.begin(), std::find_if(s.begin(), s.end(), [](unsigned char ch) { - return !std::isspace(ch); - })); -} - -// trim from end (in place) -static inline void rtrim(std::string &s) { - s.erase(std::find_if(s.rbegin(), s.rend(), [](unsigned char ch) { - return !std::isspace(ch); - }).base(), s.end()); -} - -// trim from both ends (in place) -static inline void trim(std::string &s) { - ltrim(s); - rtrim(s); -} - /* Digest interface */ void* DRMProcessorClientImpl::createDigest(const std::string& digestName) { @@ -163,8 +142,8 @@ static size_t curlHeaders(char *buffer, size_t size, size_t nitems, void *userda std::string key = std::string(buffer, pos); std::string value = std::string(&buffer[pos+1], (size*nitems)-(pos+1)); - trim(key); - trim(value); + key = gourou::trim(key); + value = gourou::trim(value); (*responseHeaders)[key] = value; From 570ad837472b600f3dfcca108786fd852f207ee1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9gory=20Soutad=C3=A9?= Date: Sun, 3 Apr 2022 09:32:06 +0200 Subject: [PATCH 04/94] Manage loan tokens --- Makefile | 2 +- include/fulfillment_item.h | 12 +++++- include/libgourou.h | 11 +++++- include/libgourou_common.h | 6 ++- include/loan_token.h | 54 ++++++++++++++++++++++++++ src/fulfillment_item.cpp | 26 ++++++++++++- src/libgourou.cpp | 27 +++++++++++++ src/loan_token.cpp | 77 ++++++++++++++++++++++++++++++++++++++ 8 files changed, 207 insertions(+), 8 deletions(-) create mode 100644 include/loan_token.h create mode 100644 src/loan_token.cpp diff --git a/Makefile b/Makefile index e4db7ae..2ce770e 100644 --- a/Makefile +++ b/Makefile @@ -36,7 +36,7 @@ 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 +SOURCES = src/libgourou.cpp src/user.cpp src/device.cpp src/fulfillment_item.cpp src/loan_token.cpp src/bytearray.cpp src/pugixml.cpp OBJECTS := $(patsubst $(SRCDIR)/%,$(BUILDDIR)/%,$(SOURCES:.$(SRCEXT)=.$(OBJEXT))) all: lib obj $(TARGETS) diff --git a/include/fulfillment_item.h b/include/fulfillment_item.h index 0cfcae0..100bcdf 100644 --- a/include/fulfillment_item.h +++ b/include/fulfillment_item.h @@ -20,7 +20,7 @@ #ifndef _FULFILLMENT_ITEM_H_ #define _FULFILLMENT_ITEM_H_ -#include "bytearray.h" +#include "loan_token.h" #include @@ -42,6 +42,8 @@ namespace gourou */ FulfillmentItem(pugi::xml_document& doc, User* user); + ~FulfillmentItem(); + /** * @brief Return metadata value from ACSM metadata section * @@ -64,13 +66,19 @@ namespace gourou */ std::string getResource(); + /** + * @brief Return loan token if there is one + */ + LoanToken* getLoanToken(); + private: pugi::xml_document fulfillDoc; pugi::xml_node metadatas; pugi::xml_document rights; std::string downloadURL; std::string resource; - + LoanToken* loanToken; + void buildRights(const pugi::xml_node& licenseToken, User* user); }; } diff --git a/include/libgourou.h b/include/libgourou.h index 88d8c63..625dc2e 100644 --- a/include/libgourou.h +++ b/include/libgourou.h @@ -40,7 +40,7 @@ #define ACS_SERVER "http://adeactivate.adobe.com/adept" #endif -#define LIBGOUROU_VERSION "0.6" +#define LIBGOUROU_VERSION "0.7" namespace gourou { @@ -100,6 +100,14 @@ namespace gourou */ void activateDevice(); + /** + * @brief Return loaned book to server + * + * @param loanID Loan ID received during fulfill + * @param operatorURL URL of operator that loans this book + */ + void returnLoan(const std::string& loanID, const std::string& operatorURL); + /** * @brief Create a new ADEPT environment (device.xml, devicesalt and activation.xml). * @@ -218,6 +226,7 @@ namespace gourou void operatorAuth(std::string operatorURL); void buildFulfillRequest(pugi::xml_document& acsmDoc, pugi::xml_document& fulfillReq); void buildActivateReq(pugi::xml_document& activateReq); + void buildReturnReq(pugi::xml_document& returnReq, const std::string& loanID, const std::string& operatorURL); ByteArray sendFulfillRequest(const pugi::xml_document& document, const std::string& url); void buildSignInRequest(pugi::xml_document& signInRequest, const std::string& adobeID, const std::string& adobePassword, const std::string& authenticationCertificate); void fetchLicenseServiceCertificate(const std::string& licenseURL, diff --git a/include/libgourou_common.h b/include/libgourou_common.h index 298ec1f..f876f60 100644 --- a/include/libgourou_common.h +++ b/include/libgourou_common.h @@ -55,7 +55,8 @@ namespace gourou GOUROU_INVALID_CLIENT, GOUROU_TAG_NOT_FOUND, GOUROU_ADEPT_ERROR, - GOUROU_FILE_ERROR + GOUROU_FILE_ERROR, + GOUROU_INVALID_PROPERTY }; enum FULFILL_ERROR { @@ -96,7 +97,8 @@ namespace gourou }; enum FULFILL_ITEM_ERROR { - FFI_INVALID_FULFILLMENT_DATA = 0x4000 + FFI_INVALID_FULFILLMENT_DATA = 0x4000, + FFI_INVALID_LOAN_TOKEN }; enum CLIENT_ERROR { diff --git a/include/loan_token.h b/include/loan_token.h new file mode 100644 index 0000000..945f68d --- /dev/null +++ b/include/loan_token.h @@ -0,0 +1,54 @@ +/* + Copyright 2022 Grégory Soutadé + + This file is part of libgourou. + + libgourou is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + libgourou is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with libgourou. If not, see . +*/ + +#ifndef _LOAN_TOKEN_H_ +#define _LOAN_TOKEN_H_ + +#include + +#include + +namespace gourou +{ + /** + * @brief This class is a container for a fulfillment object + */ + class LoanToken + { + public: + /** + * @brief Main constructor. Not to be called by user + * + * @param doc Fulfill reply + */ + LoanToken(pugi::xml_document& doc); + + /** + * @brief Get a property (id, operatorURL, validity) + */ + std::string getProperty(const std::string& property, const std::string& _default=std::string("")); + std::string operator[](const std::string& property); + + private: + std::map properties; + }; +} + + +#endif diff --git a/src/fulfillment_item.cpp b/src/fulfillment_item.cpp index 679ac68..924c63f 100644 --- a/src/fulfillment_item.cpp +++ b/src/fulfillment_item.cpp @@ -24,7 +24,7 @@ namespace gourou { FulfillmentItem::FulfillmentItem(pugi::xml_document& doc, User* user) - : fulfillDoc() + : fulfillDoc(), loanToken(0) { fulfillDoc.reset(doc); /* We must keep a copy */ metadatas = fulfillDoc.select_node("//metadata").node(); @@ -50,8 +50,25 @@ namespace gourou EXCEPTION(FFI_INVALID_FULFILLMENT_DATA, "Any license token in document"); buildRights(licenseToken, user); + + node = doc.select_node("/envelope/fulfillmentResult/returnable").node(); + try + { + if (node && node.first_child().value() == std::string("true")) + loanToken = new LoanToken(doc); + } + catch(std::exception& e) + { + GOUROU_LOG(ERROR, "Book is returnable, but contains invalid loan token"); + GOUROU_LOG(ERROR, e.what()); + } } - + + FulfillmentItem::~FulfillmentItem() + { + if (loanToken) delete loanToken; + } + void FulfillmentItem::buildRights(const pugi::xml_node& licenseToken, User* user) { pugi::xml_node decl = rights.append_child(pugi::node_declaration); @@ -103,4 +120,9 @@ namespace gourou { return resource; } + + LoanToken* FulfillmentItem::getLoanToken() + { + return loanToken; + } } diff --git a/src/libgourou.cpp b/src/libgourou.cpp index b77bea8..d0f22ae 100644 --- a/src/libgourou.cpp +++ b/src/libgourou.cpp @@ -837,6 +837,33 @@ namespace gourou user->updateActivationFile(activationDoc); } + void DRMProcessor::buildReturnReq(pugi::xml_document& returnReq, const std::string& loanID, const std::string& operatorURL) + { + pugi::xml_node decl = returnReq.append_child(pugi::node_declaration); + decl.append_attribute("version") = "1.0"; + + pugi::xml_node root = returnReq.append_child("adept:loanReturn"); + root.append_attribute("xmlns:adept") = ADOBE_ADEPT_NS; + + appendTextElem(root, "adept:user", user->getUUID()); + appendTextElem(root, "adept:device", user->getDeviceUUID()); + appendTextElem(root, "adept:loan", loanID); + + addNonce(root); + signNode(root); + } + + void DRMProcessor::returnLoan(const std::string& loanID, const std::string& operatorURL) + { + pugi::xml_document returnReq; + + GOUROU_LOG(INFO, "Return loan " << loanID); + + buildReturnReq(returnReq, loanID, operatorURL); + + sendRequest(returnReq, operatorURL + "/LoanReturn"); + } + ByteArray DRMProcessor::encryptWithDeviceKey(const unsigned char* data, unsigned int len) { const unsigned char* deviceKey = device->getDeviceKey(); diff --git a/src/loan_token.cpp b/src/loan_token.cpp new file mode 100644 index 0000000..1d10086 --- /dev/null +++ b/src/loan_token.cpp @@ -0,0 +1,77 @@ +/* + Copyright 2022 Grégory Soutadé + + This file is part of libgourou. + + libgourou is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + libgourou is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with libgourou. If not, see . +*/ + +#include "libgourou_common.h" +#include "loan_token.h" + +namespace gourou +{ + LoanToken::LoanToken(pugi::xml_document& doc) + { + pugi::xml_node node = doc.select_node("/envelope/loanToken").node(); + + if (!node) + EXCEPTION(FFI_INVALID_LOAN_TOKEN, "No loanToken element in document"); + + node = doc.select_node("/envelope/loanToken/loan").node(); + + if (!node) + EXCEPTION(FFI_INVALID_LOAN_TOKEN, "No loanToken/loan element in document"); + + properties["id"] = node.first_child().value(); + + node = doc.select_node("/envelope/loanToken/operatorURL").node(); + + if (!node) + EXCEPTION(FFI_INVALID_LOAN_TOKEN, "No loanToken/operatorURL element in document"); + + properties["operatorURL"] = node.first_child().value(); + + node = doc.select_node("/envelope/fulfillmentResult/resourceItemInfo/licenseToken/permissions/display/until").node(); + + if (node) + properties["validity"] = node.first_child().value(); + else + { + node = doc.select_node("/envelope/fulfillmentResult/resourceItemInfo/licenseToken/permissions/play/until").node(); + if (node) + properties["validity"] = node.first_child().value(); + else + EXCEPTION(FFI_INVALID_LOAN_TOKEN, "No loanToken/operatorURL element in document"); + } + } + + std::string LoanToken::getProperty(const std::string& property, const std::string& _default) + { + if (properties.find(property) == properties.end()) + { + if (_default == "") + EXCEPTION(GOUROU_INVALID_PROPERTY, "Invalid property " << property); + + return _default; + } + + return properties[property]; + } + + std::string LoanToken::operator[](const std::string& property) + { + return getProperty(property); + } +} From 8fe8ba2808cbf5c44324bb3499bf35137a13183d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9gory=20Soutad=C3=A9?= Date: Sun, 3 Apr 2022 09:36:23 +0200 Subject: [PATCH 05/94] Add adept_loan_mgt util --- utils/Makefile | 19 +- utils/acsmdownloader.cpp | 54 ++++- utils/adept_loan_mgt.cpp | 479 +++++++++++++++++++++++++++++++++++++++ utils/utils_common.h | 3 + 4 files changed, 548 insertions(+), 7 deletions(-) create mode 100644 utils/adept_loan_mgt.cpp diff --git a/utils/Makefile b/utils/Makefile index 318cf92..7d7882a 100644 --- a/utils/Makefile +++ b/utils/Makefile @@ -1,5 +1,5 @@ -TARGETS=acsmdownloader adept_activate adept_remove +TARGETS=acsmdownloader adept_activate adept_remove adept_loan_mgt CXXFLAGS=-Wall -fPIC -I$(ROOT)/include -I$(ROOT)/lib/pugixml/src/ @@ -18,17 +18,26 @@ else CXXFLAGS += -O2 endif -COMMON_DEPS = drmprocessorclientimpl.cpp utils_common.cpp $(STATIC_DEP) +COMMON_DEPS = drmprocessorclientimpl.cpp utils_common.cpp +COMMON_OBJECTS = $(COMMON_DEPS:.cpp=.o) +COMMON_LIB = utils.a all: $(TARGETS) -acsmdownloader: acsmdownloader.cpp $(COMMON_DEPS) +${COMMON_LIB}: ${COMMON_DEPS} ${STATIC_DEP} + $(CXX) $(CXXFLAGS) ${COMMON_DEPS} $(LDFLAGS) -c + $(AR) crs $@ ${COMMON_OBJECTS} $(STATIC_DEP) + +acsmdownloader: acsmdownloader.cpp ${COMMON_LIB} $(CXX) $(CXXFLAGS) $^ $(LDFLAGS) -o $@ -adept_activate: adept_activate.cpp $(COMMON_DEPS) +adept_activate: adept_activate.cpp ${COMMON_LIB} $(CXX) $(CXXFLAGS) $^ $(LDFLAGS) -o $@ -adept_remove: adept_remove.cpp $(COMMON_DEPS) +adept_remove: adept_remove.cpp ${COMMON_LIB} + $(CXX) $(CXXFLAGS) $^ $(LDFLAGS) -o $@ + +adept_loan_mgt: adept_loan_mgt.cpp ${COMMON_LIB} $(CXX) $(CXXFLAGS) $^ $(LDFLAGS) -o $@ clean: diff --git a/utils/acsmdownloader.cpp b/utils/acsmdownloader.cpp index 360cfda..dbe551f 100644 --- a/utils/acsmdownloader.cpp +++ b/utils/acsmdownloader.cpp @@ -26,12 +26,15 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ - #include +#include +#include #include #include #include +#include + #include "drmprocessorclientimpl.h" #include "utils_common.h" @@ -54,7 +57,6 @@ public: int ret = 0; try { - DRMProcessorClientImpl client; gourou::DRMProcessor processor(&client, deviceFile, activationFile, devicekeyFile); gourou::User* user = processor.getUser(); @@ -118,6 +120,8 @@ public: filename = finalName; } std::cout << "Created " << filename << std::endl; + + serializeLoanToken(item); } } catch(std::exception& e) { @@ -127,6 +131,52 @@ public: return ret; } + + void serializeLoanToken(gourou::FulfillmentItem* item) + { + gourou::LoanToken* token = item->getLoanToken(); + + // No loan token available + if (!token) + return; + + pugi::xml_document doc; + + pugi::xml_node decl = doc.append_child(pugi::node_declaration); + decl.append_attribute("version") = "1.0"; + + pugi::xml_node root = doc.append_child("loanToken"); + gourou::appendTextElem(root, "id", (*token)["id"]); + gourou::appendTextElem(root, "operatorURL", (*token)["operatorURL"]); + gourou::appendTextElem(root, "validity", (*token)["validity"]); + gourou::appendTextElem(root, "name", item->getMetadata("title")); + + char * activationDir = strdup(deviceFile); + activationDir = dirname(activationDir); + + gourou::StringXMLWriter xmlWriter; + doc.save(xmlWriter, " "); + std::string xmlStr = xmlWriter.getResult(); + + // Use first bytes of SHA1(id) as filename + unsigned char sha1[gourou::SHA1_LEN]; + client.digest("SHA1", (unsigned char*)(*token)["id"].c_str(), (*token)["id"].size(), sha1); + gourou::ByteArray tmp(sha1, sizeof(sha1)); + std::string filenameHex = tmp.toHex(); + std::string filename(filenameHex.c_str(), ID_HASH_SIZE); + std::string fullPath = std::string(activationDir); + fullPath += std::string ("/") + std::string(LOANS_DIR); + mkpath(fullPath.c_str()); + fullPath += filename + std::string(".xml"); + gourou::writeFile(fullPath, xmlStr); + + std::cout << "Loan token serialized into " << fullPath << std::endl; + + free(activationDir); + } + +private: + DRMProcessorClientImpl client; }; diff --git a/utils/adept_loan_mgt.cpp b/utils/adept_loan_mgt.cpp new file mode 100644 index 0000000..a8ac487 --- /dev/null +++ b/utils/adept_loan_mgt.cpp @@ -0,0 +1,479 @@ +/* + Copyright (c) 2022, Grégory Soutadé + + All rights reserved. + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY + EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS BE LIABLE FOR ANY + DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include + +#include +#include + +#define _XOPEN_SOURCE 700 +#include +#include +#include +#include +#include + +#include +#include +#include "drmprocessorclientimpl.h" +#include "utils_common.h" + +#define MAX_SIZE_BOOK_NAME 30 + +static char* activationDir = 0; +static const char* deviceFile = "device.xml"; +static const char* activationFile = "activation.xml"; +static const char* devicekeyFile = "devicesalt"; +static bool list = false; +static const char* returnID = 0; +static const char* deleteID = 0; + +struct Loan +{ + std::string id; + std::string operatorURL; + std::string validity; + std::string bookName; + + std::string path; +}; + +class LoanMGT +{ +public: + ~LoanMGT() + { + for (const auto& kv : loanedBooks) + delete kv.second; + } + + int run() + { + int ret = 0; + try + { + DRMProcessorClientImpl client; + gourou::DRMProcessor processor(&client, deviceFile, activationFile, devicekeyFile); + + loadLoanedBooks(); + + if (list) + displayLoanList(); + else if (returnID) + returnBook(processor); + else if (deleteID) + deleteLoan(); + } catch(std::exception& e) + { + std::cout << e.what() << std::endl; + ret = 1; + } + + return ret; + } + +private: + void loadLoanedBooks() + { + DIR *dp; + struct dirent *ep; + int entryLen; + struct Loan* loan; + char * res; + + std::string loanDir = std::string(activationDir) + std::string("/") + LOANS_DIR; + + if (!fileExists(loanDir.c_str())) + return; + + dp = opendir (loanDir.c_str()); + + if(!dp) + EXCEPTION(gourou::USER_INVALID_INPUT, "Cannot read directory " << loanDir); + + while ((ep = readdir (dp))) + { + if (ep->d_type != DT_LNK && + ep->d_type != DT_REG) + continue; + + entryLen = strlen(ep->d_name); + + if (entryLen <= 4 || + ep->d_name[entryLen-4] != '.' || + ep->d_name[entryLen-3] != 'x' || + ep->d_name[entryLen-2] != 'm' || + ep->d_name[entryLen-1] != 'l') + continue; + + std::string id = std::string(ep->d_name, entryLen-4); + + loan = new Loan; + loan->path = loanDir + std::string("/") + ep->d_name; + + pugi::xml_document xmlDoc; + pugi::xml_node node; + + if (!xmlDoc.load_file(loan->path.c_str(), pugi::parse_ws_pcdata_single|pugi::parse_escapes, pugi::encoding_utf8)) + { + std::cout << "Invalid loan entry " << loan->path << std::endl; + goto error; + } + + // id + node = xmlDoc.select_node("//id").node(); + if (!node) + { + std::cout << "Invalid loan entry " << ep->d_name << ", no id element" << std::endl; + goto error; + } + loan->id = node.first_child().value(); + + // operatorURL + node = xmlDoc.select_node("//operatorURL").node(); + if (!node) + { + std::cout << "Invalid loan entry " << ep->d_name << ", no operatorURL element" << std::endl; + goto error; + } + loan->operatorURL = node.first_child().value(); + + // validity + node = xmlDoc.select_node("//validity").node(); + if (!node) + { + std::cout << "Invalid loan entry " << ep->d_name << ", no validity element" << std::endl; + goto error; + } + loan->validity = node.first_child().value(); + + // bookName + node = xmlDoc.select_node("//name").node(); + if (!node) + { + std::cout << "Invalid loan entry " << ep->d_name << ", no name element" << std::endl; + goto error; + } + loan->bookName = node.first_child().value(); + + struct tm tm; + res = strptime(loan->validity.c_str(), "%Y-%m-%dT%H:%M:%S%Z", &tm); + if (*res == 0) + { + if (mktime(&tm) <= time(NULL)) + loan->validity = " (Expired)"; + } + else + { + std::cout << "Unable to parse validity timestamp :" << loan->validity << std::endl; + loan->validity = " (Unknown)"; + } + + loanedBooks[id] = loan; + continue; + + error: + if (loan) + delete loan; + } + + closedir (dp); + } + + void displayLoanList() + { + if (!loanedBooks.size()) + { + std::cout << "Any book loaned" << std::endl; + return; + } + + struct Loan* loan; + unsigned int maxSizeBookName=0; + // Compute max size + for (const auto& kv : loanedBooks) + { + loan = kv.second; + if (loan->bookName.size() > maxSizeBookName) + maxSizeBookName = loan->bookName.size(); + } + + if (maxSizeBookName > MAX_SIZE_BOOK_NAME) + maxSizeBookName = MAX_SIZE_BOOK_NAME; + else if ((maxSizeBookName % 2)) + maxSizeBookName++; + + // std::cout << " ID Book Expiration" << std::endl; + // std::cout << "------------------------------" << std::endl; + + int fillID, fillBookName, fillExpiration=(20 - 10)/2; + + fillID = (ID_HASH_SIZE - 2) / 2; + fillBookName = (maxSizeBookName - 4) / 2; + + std::cout.width (fillID); + std::cout << ""; + std::cout << "ID" ; + std::cout.width (fillID); + std::cout << ""; + std::cout << " " ; + + std::cout.width (fillBookName); + std::cout << ""; + std::cout << "Book" ; + std::cout.width (fillBookName); + std::cout << ""; + std::cout << " " ; + + std::cout.width (fillExpiration); + std::cout << ""; + std::cout << "Exipration"; + std::cout.width (fillExpiration); + std::cout << "" << std::endl; + + std::cout.fill ('-'); + std::cout.width (ID_HASH_SIZE + 4 + maxSizeBookName + 4 + 20); + std::cout << "" << std::endl; + std::cout.fill (' '); + + std::string bookName; + + for (const auto& kv : loanedBooks) + { + loan = kv.second; + + std::cout << kv.first; + std::cout << " "; + + if (loan->bookName.size() > MAX_SIZE_BOOK_NAME) + bookName = std::string(loan->bookName.c_str(), MAX_SIZE_BOOK_NAME); + else + bookName = loan->bookName; + + std::cout << bookName; + std::cout.width (maxSizeBookName - bookName.size()); + std::cout << ""; + std::cout << " "; + + std::cout << loan->validity << std::endl; + } + + std::cout << std::endl; + } + + void returnBook(gourou::DRMProcessor& processor) + { + struct Loan* loan = loanedBooks[std::string(returnID)]; + + if (!loan) + { + std::cout << "Error : Loan " << returnID << " doesn't exists" << std::endl; + return; + } + + processor.returnLoan(loan->id, loan->operatorURL); + + deleteID = returnID; + if (deleteLoan(false)) + { + std::cout << "Loan " << returnID << " successfully returned" << std::endl; + } + } + + bool deleteLoan(bool displayResult=true) + { + struct Loan* loan = loanedBooks[std::string(deleteID)]; + + if (!loan) + { + std::cout << "Error : Loan " << deleteID << " doesn't exists" << std::endl; + return false; + } + + if (unlink(loan->path.c_str())) + { + std::cout << "Error : Cannot delete " << loan->path << std::endl; + return false; + } + else if (displayResult) + { + std::cout << "Loan " << deleteID << " deleted" << std::endl; + } + + return true; + } + + std::map loanedBooks; +}; + + +static void usage(const char* cmd) +{ + std::cout << "Manage loaned books" << std::endl; + + std::cout << "Usage: " << cmd << " [(-d|--activation-dir) dir] (-l|--list)|(-D|--delete loanID)|(-R|--delete loanID) [(-v|--verbose)] [(-h|--help)]" << std::endl << std::endl; + + std::cout << " " << "-d|--activation-dir" << "\t" << "Directory of device.xml/activation.xml and device key" << std::endl; + std::cout << " " << "-l|--list" << "\t\t" << "List all loaned books" << std::endl; + std::cout << " " << "-r|--return" << "\t\t" << "Return a loaned book" << std::endl; + std::cout << " " << "-D|--delete" << "\t\t" << "Delete a loan entry without returning it" << std::endl; + std::cout << " " << "-v|--verbose" << "\t\t" << "Increase verbosity, can be set multiple times" << std::endl; + std::cout << " " << "-V|--version" << "\t\t" << "Display libgourou version" << std::endl; + std::cout << " " << "-h|--help" << "\t\t" << "This help" << std::endl; + + std::cout << std::endl; + std::cout << "Activation directory is optional. If not set, it's 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(); + int actions = 0; + + while (1) { + int option_index = 0; + static struct option long_options[] = { + {"activation-dir", required_argument, 0, 'd' }, + {"list", no_argument, 0, 'l' }, + {"return", no_argument, 0, 'r' }, + {"delete", no_argument, 0, 'D' }, + {"verbose", no_argument, 0, 'v' }, + {"version", no_argument, 0, 'V' }, + {"help", no_argument, 0, 'h' }, + {0, 0, 0, 0 } + }; + + c = getopt_long(argc, argv, "d:lr:D:vVh", + long_options, &option_index); + if (c == -1) + break; + + switch (c) { + case 'd': + activationDir = optarg; + break; + case 'l': + list = true; + actions++; + break; + case 'r': + returnID = optarg; + actions++; + break; + case 'D': + deleteID = optarg; + actions++; + break; + case 'v': + verbose++; + break; + case 'V': + version(); + return 0; + case 'h': + usage(argv[0]); + return 0; + default: + usage(argv[0]); + return -1; + } + } + + gourou::DRMProcessor::setLogLevel(verbose); + + // By default, simply list books loaned + if (actions == 0) + list = true; + else if (actions != 1) + { + usage(argv[0]); + return -1; + } + + LoanMGT loanMGT; + + int i; + bool hasErrors = false; + const char* orig; + char *filename; + for (i=0; i<(int)ARRAY_SIZE(files); i++) + { + orig = *files[i]; + + if (activationDir) + { + std::string path = std::string(activationDir) + std::string("/") + orig; + filename = strdup(path.c_str()); + } + else + filename = strdup(orig); + *files[i] = findFile(filename); + free(filename); + if (!*files[i]) + { + std::cout << "Error : " << orig << " doesn't exists, did you activate your device ?" << std::endl; + hasErrors = true; + } + } + + if (hasErrors) + { + // In case of activation dir was provided by user + activationDir = 0; + goto end; + } + + if (activationDir) + activationDir = strdup(activationDir); // For below free + else + { + activationDir = strdup(deviceFile); + activationDir = dirname(activationDir); + } + + ret = loanMGT.run(); + +end: + for (i=0; i<(int)ARRAY_SIZE(files); i++) + { + if (*files[i]) + free((void*)*files[i]); + } + + if (activationDir) + free(activationDir); + + return ret; +} diff --git a/utils/utils_common.h b/utils/utils_common.h index 376a7c8..5bad2a9 100644 --- a/utils/utils_common.h +++ b/utils/utils_common.h @@ -29,6 +29,9 @@ #ifndef _UTILS_COMMON_H_ #define _UTILS_COMMON_H_ +#define LOANS_DIR "loans/" +#define ID_HASH_SIZE 16 + #define ARRAY_SIZE(arr) (sizeof(arr)/sizeof(arr[0])) /** From 8c413b4f3419de1ed15e497358d0afe66f2e9f3a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9gory=20Soutad=C3=A9?= Date: Sun, 3 Apr 2022 09:37:08 +0200 Subject: [PATCH 06/94] Update .gitignore --- .gitignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 214f97a..675508f 100644 --- a/.gitignore +++ b/.gitignore @@ -6,4 +6,5 @@ lib utils/acsmdownloader utils/adept_activate utils/adept_remove -.adept +utils/adept_loan_mgt +.adept* From f568b5d3a8b3b4666af634c774c7dc7faaa98d3f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9gory=20Soutad=C3=A9?= Date: Sun, 3 Apr 2022 09:47:47 +0200 Subject: [PATCH 07/94] Update README.md --- README.md | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 5baa62d..7f7842a 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ Main fucntions to use from gourou::DRMProcessor are : * Create a new device : _createDRMProcessor()_ * Register a new device : _signIn()_ and _activateDevice()_ * Remove DRM : _removeDRM()_ - + * Return loaned book : _returnLoan()_ You can import configuration from (at least) : @@ -91,6 +91,16 @@ To remove ADEPT DRM : export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$PWD ./utils/adept_remove -f +To list loaned books : + + export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$PWD + ./utils/adept_loan_mgt [-l] + +To return a loaned book : + + export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$PWD + ./utils/adept_loan_mgt -r + You can get utils full options description with -h or --help switch From 4f9b2de5a5c313a1a50057bde3ce2eb70933b3eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9gory=20Soutad=C3=A9?= Date: Sat, 23 Apr 2022 17:41:54 +0200 Subject: [PATCH 08/94] Remove use of tempnam function and fix bug (bad check of rename return) --- utils/adept_remove.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/utils/adept_remove.cpp b/utils/adept_remove.cpp index 556e409..a56f6b0 100644 --- a/utils/adept_remove.cpp +++ b/utils/adept_remove.cpp @@ -114,16 +114,16 @@ public: // Use temp file for PDF if (type == gourou::DRMProcessor::ITEM_TYPE::PDF) { - char* tempFile = tempnam("/tmp", NULL); - processor.removeDRM(inputFile, tempFile, type, encryptionKey, encryptionKeySize); + std::string tempFile = filename + ".tmp"; + /* Be sure there is not already a temp file */ + unlink(tempFile.c_str()); + processor.removeDRM(filename, tempFile, type, encryptionKey, encryptionKeySize); /* Original file must be removed before doing a copy... */ - unlink(inputFile); - if (!rename(tempFile, filename.c_str())) + unlink(filename.c_str()); + if (rename(tempFile.c_str(), filename.c_str())) { - free(tempFile); EXCEPTION(gourou::DRM_FILE_ERROR, "Unable to copy " << tempFile << " into " << filename); } - free(tempFile); } else processor.removeDRM(inputFile, filename, type, encryptionKey, encryptionKeySize); From 7b6b1471fefb27e79e06e5d686cb8842c539cd0c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9gory=20Soutad=C3=A9?= Date: Sat, 23 Apr 2022 17:51:19 +0200 Subject: [PATCH 09/94] Update version --- include/libgourou.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/libgourou.h b/include/libgourou.h index 625dc2e..e88e960 100644 --- a/include/libgourou.h +++ b/include/libgourou.h @@ -40,7 +40,7 @@ #define ACS_SERVER "http://adeactivate.adobe.com/adept" #endif -#define LIBGOUROU_VERSION "0.7" +#define LIBGOUROU_VERSION "0.7.1" namespace gourou { From 3d4e6e3918b2326413f9276e0f546042d9ceb4bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9gory=20Soutad=C3=A9?= Date: Sun, 5 Jun 2022 13:51:57 +0200 Subject: [PATCH 10/94] Look for element in node in addition to one --- src/loan_token.cpp | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/src/loan_token.cpp b/src/loan_token.cpp index 1d10086..8977ef2 100644 --- a/src/loan_token.cpp +++ b/src/loan_token.cpp @@ -31,10 +31,23 @@ namespace gourou node = doc.select_node("/envelope/loanToken/loan").node(); - if (!node) - EXCEPTION(FFI_INVALID_LOAN_TOKEN, "No loanToken/loan element in document"); + if (node) + properties["id"] = node.first_child().value(); + else + { + node = doc.select_node("/envelope/fulfillmentResult/resourceItemInfo/licenseToken/permissions/display/loan").node(); - properties["id"] = node.first_child().value(); + if (node) + properties["id"] = node.first_child().value(); + else + { + node = doc.select_node("/envelope/fulfillmentResult/resourceItemInfo/licenseToken/permissions/play/loan").node(); + if (node) + properties["id"] = node.first_child().value(); + else + EXCEPTION(FFI_INVALID_LOAN_TOKEN, "No loanToken/loan element in document"); + } + } node = doc.select_node("/envelope/loanToken/operatorURL").node(); @@ -50,6 +63,7 @@ namespace gourou else { node = doc.select_node("/envelope/fulfillmentResult/resourceItemInfo/licenseToken/permissions/play/until").node(); + if (node) properties["validity"] = node.first_child().value(); else From 4f288f4e241e3cc711a10fdcf4078fd4ddfab7c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9gory=20Soutad=C3=A9?= Date: Sun, 5 Jun 2022 15:29:20 +0200 Subject: [PATCH 11/94] Add support for OpenSSL 3 --- include/libgourou_common.h | 3 ++- utils/Makefile | 3 +++ utils/drmprocessorclientimpl.cpp | 35 ++++++++++++++++++++++++++++++++ utils/drmprocessorclientimpl.h | 14 +++++++++++++ 4 files changed, 54 insertions(+), 1 deletion(-) diff --git a/include/libgourou_common.h b/include/libgourou_common.h index f876f60..2328208 100644 --- a/include/libgourou_common.h +++ b/include/libgourou_common.h @@ -114,7 +114,8 @@ namespace gourou CLIENT_GENERIC_EXCEPTION, CLIENT_NETWORK_ERROR, CLIENT_INVALID_PKCS8, - CLIENT_FILE_ERROR + CLIENT_FILE_ERROR, + CLIENT_OSSL_ERROR, }; enum DRM_REMOVAL_ERROR { diff --git a/utils/Makefile b/utils/Makefile index 7d7882a..386bd97 100644 --- a/utils/Makefile +++ b/utils/Makefile @@ -3,6 +3,9 @@ TARGETS=acsmdownloader adept_activate adept_remove adept_loan_mgt CXXFLAGS=-Wall -fPIC -I$(ROOT)/include -I$(ROOT)/lib/pugixml/src/ +# OpenSSL 1.1.0 compat +CXXFLAGS += -DOPENSSL_API_COMPAT=0x10100000L + STATIC_DEP= LDFLAGS=-L$(ROOT) -lcrypto -lzip -lz -lcurl diff --git a/utils/drmprocessorclientimpl.cpp b/utils/drmprocessorclientimpl.cpp index 39a7dfa..2edc25f 100644 --- a/utils/drmprocessorclientimpl.cpp +++ b/utils/drmprocessorclientimpl.cpp @@ -35,6 +35,8 @@ #include #include #include +#include +#include #include @@ -45,6 +47,31 @@ #include #include "drmprocessorclientimpl.h" +DRMProcessorClientImpl::DRMProcessorClientImpl(): + legacy(0), deflt(0) +{ +#if OPENSSL_VERSION_MAJOR >= 3 + legacy = OSSL_PROVIDER_load(NULL, "legacy"); + if (!legacy) + EXCEPTION(gourou::CLIENT_OSSL_ERROR, "Error, OpenSSL legacy provider not available"); + + deflt = OSSL_PROVIDER_load(NULL, "default"); + if (!deflt) + EXCEPTION(gourou::CLIENT_OSSL_ERROR, "Error, OpenSSL default provider not available"); +#endif +} + +DRMProcessorClientImpl::~DRMProcessorClientImpl() +{ +#if OPENSSL_VERSION_MAJOR >= 3 + if (legacy) + OSSL_PROVIDER_unload(legacy); + + if (deflt) + OSSL_PROVIDER_unload(deflt); +#endif +} + /* Digest interface */ void* DRMProcessorClientImpl::createDigest(const std::string& digestName) { @@ -289,7 +316,12 @@ void DRMProcessorClientImpl::RSAPrivateEncrypt(const unsigned char* RSAKey, unsi 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); + + if (!pkey) + EXCEPTION(gourou::CLIENT_INVALID_PKCS12, ERR_error_string(ERR_get_error(), NULL)); + rsa = EVP_PKEY_get1_RSA(pkey); int ret = RSA_private_encrypt(dataLength, data, res, rsa, RSA_PKCS1_PADDING); @@ -413,6 +445,9 @@ void DRMProcessorClientImpl::extractCertificate(const unsigned char* RSAKey, uns EXCEPTION(gourou::CLIENT_INVALID_PKCS12, ERR_error_string(ERR_get_error(), NULL)); PKCS12_parse(pkcs12, password.c_str(), &pkey, &cert, &ca); + if (!cert) + EXCEPTION(gourou::CLIENT_INVALID_PKCS12, ERR_error_string(ERR_get_error(), NULL)); + *certOutLength = i2d_X509(cert, certOut); EVP_PKEY_free(pkey); diff --git a/utils/drmprocessorclientimpl.h b/utils/drmprocessorclientimpl.h index 53a4a03..528cfee 100644 --- a/utils/drmprocessorclientimpl.h +++ b/utils/drmprocessorclientimpl.h @@ -31,11 +31,18 @@ #include +#if OPENSSL_VERSION_MAJOR >= 3 +#include +#endif + #include class DRMProcessorClientImpl : public gourou::DRMProcessorClient { public: + DRMProcessorClientImpl(); + ~DRMProcessorClientImpl(); + /* Digest interface */ virtual void* createDigest(const std::string& digestName); virtual int digestUpdate(void* handler, unsigned char* data, unsigned int length); @@ -118,6 +125,13 @@ public: virtual void deflate(gourou::ByteArray& data, gourou::ByteArray& result, int wbits=-15, int compressionLevel=8); + +private: +#if OPENSSL_VERSION_MAJOR >= 3 + OSSL_PROVIDER *legacy, *deflt; +#else + void *legacy, *deflt; +#endif }; #endif From 22880c71c669edb8f33e25d13d76e756a6e37579 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9gory=20Soutad=C3=A9?= Date: Wed, 8 Jun 2022 11:39:05 +0200 Subject: [PATCH 12/94] Update Makefile to support separated OpenSSL3 compilation --- Makefile | 2 +- utils/Makefile | 10 +++++++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 2ce770e..de87f9a 100644 --- a/Makefile +++ b/Makefile @@ -60,7 +60,7 @@ libgourou.so: $(OBJECTS) $(UPDFPARSERLIB) $(CXX) obj/*.o $(LDFLAGS) -o $@ -shared build_utils: - make -C utils ROOT=$(PWD) CXX=$(CXX) AR=$(AR) DEBUG=$(DEBUG) STATIC_UTILS=$(STATIC_UTILS) + make -C utils ROOT=$(PWD) CXX=$(CXX) AR=$(AR) DEBUG=$(DEBUG) STATIC_UTILS=$(STATIC_UTILS) OPENSSL3=$(OPENSSL3) clean: rm -rf libgourou.a libgourou.so obj diff --git a/utils/Makefile b/utils/Makefile index 386bd97..3d1de32 100644 --- a/utils/Makefile +++ b/utils/Makefile @@ -3,11 +3,18 @@ TARGETS=acsmdownloader adept_activate adept_remove adept_loan_mgt CXXFLAGS=-Wall -fPIC -I$(ROOT)/include -I$(ROOT)/lib/pugixml/src/ +LDFLAGS= + +ifneq ($(OPENSSL3),) # OpenSSL 1.1.0 compat CXXFLAGS += -DOPENSSL_API_COMPAT=0x10100000L +CXXFLAGS += -I/tmp/openssl3/usr/include/ -I/tmp/openssl3/usr/include/x86_64-linux-gnu +LDFLAGS += -L/tmp/openssl3/usr/lib/x86_64-linux-gnu -L/tmp/openssl3/usr/lib/x86_64-linux-gnu/ossl-modules +endif + STATIC_DEP= -LDFLAGS=-L$(ROOT) -lcrypto -lzip -lz -lcurl +LDFLAGS += -L$(ROOT) -lcrypto -lzip -lz -lcurl ifneq ($(STATIC_UTILS),) STATIC_DEP = $(ROOT)/libgourou.a @@ -21,6 +28,7 @@ else CXXFLAGS += -O2 endif + COMMON_DEPS = drmprocessorclientimpl.cpp utils_common.cpp COMMON_OBJECTS = $(COMMON_DEPS:.cpp=.o) COMMON_LIB = utils.a From 81563056e0c14cf51131b0c6fa4f258608d7b0ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9gory=20Soutad=C3=A9?= Date: Wed, 8 Jun 2022 11:39:24 +0200 Subject: [PATCH 13/94] Update README --- README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/README.md b/README.md index 7f7842a..aa30c8e 100644 --- a/README.md +++ b/README.md @@ -104,6 +104,11 @@ To return a loaned book : You can get utils full options description with -h or --help switch +Docker +------ + +A docker image (by bcliang) is available at [https://github.com/bcliang/docker-libgourou/](https://github.com/bcliang/docker-libgourou/) + Copyright --------- From 5e018ddbd8c7841254755cb87d986ecfa359932f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9gory=20Soutad=C3=A9?= Date: Wed, 8 Jun 2022 11:39:59 +0200 Subject: [PATCH 14/94] Update version --- include/libgourou.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/libgourou.h b/include/libgourou.h index e88e960..0bc9269 100644 --- a/include/libgourou.h +++ b/include/libgourou.h @@ -40,7 +40,7 @@ #define ACS_SERVER "http://adeactivate.adobe.com/adept" #endif -#define LIBGOUROU_VERSION "0.7.1" +#define LIBGOUROU_VERSION "0.7.2" namespace gourou { From 201ec69b11ae6d54971c14a5dbeea67b7ab4b857 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9gory=20Soutad=C3=A9?= Date: Sun, 12 Jun 2022 15:00:38 +0200 Subject: [PATCH 15/94] Add scripts/update_lib.sh --- Makefile | 3 +++ scripts/update_lib.sh | 21 +++++++++++++++++++++ 2 files changed, 24 insertions(+) create mode 100755 scripts/update_lib.sh diff --git a/Makefile b/Makefile index de87f9a..fac27c2 100644 --- a/Makefile +++ b/Makefile @@ -45,6 +45,9 @@ lib: mkdir lib ./scripts/setup.sh +update_lib: + ./scripts/update_lib.sh + obj: mkdir obj diff --git a/scripts/update_lib.sh b/scripts/update_lib.sh new file mode 100755 index 0000000..c742310 --- /dev/null +++ b/scripts/update_lib.sh @@ -0,0 +1,21 @@ +#!/bin/bash + +if [ ! -d lib/pugixml -o ! -d lib/updfparser ] ; then + echo "Some libraries are missing" + echo "You must run this script at the top of libgourou working direcotry." + echo "./lib/setup.sh must be called first (make all)" + exit 1 +fi + +# Pugixml +pushd lib/pugixml +git pull origin latest +popd + +# Base64 +# Nothing to do + +# uPDFParser +pushd lib/updfparser +git pull origin master +make clean all BUILD_STATIC=1 BUILD_SHARED=0 From 7666d2a241224f7d18d0b9c91524715517f8341e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9gory=20Soutad=C3=A9?= Date: Sun, 12 Jun 2022 15:00:48 +0200 Subject: [PATCH 16/94] Update README --- README.md | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index aa30c8e..0cffdd7 100644 --- a/README.md +++ b/README.md @@ -35,8 +35,16 @@ Dependencies For libgourou : +_externals_ : + * None +_internals_ : + + * PugiXML + * Base64 + * uPDFParser + For utils : * libcurl @@ -44,12 +52,18 @@ For utils : * libzip +Internal libraries are automatically fetched and statically compiled during the first run. +When you update libgourou's repository, **don't forget to update internal libraries** with : + + make update_lib + + Compilation ----------- Use _make_ command - make [CROSS=XXX] [DEBUG=(0*|1)] [STATIC_UTILS=(0*|1)] [BUILD_UTILS=(0|1*)] [BUILD_STATIC=(0*|1)] [BUILD_SHARED=(0|1*)] + make [CROSS=XXX] [DEBUG=(0*|1)] [STATIC_UTILS=(0*|1)] [BUILD_UTILS=(0|1*)] [BUILD_STATIC=(0*|1)] [BUILD_SHARED=(0|1*)] [all*|clean|ultraclean|build_utils] CROSS can define a cross compiler prefix (ie arm-linux-gnueabihf-) @@ -116,7 +130,6 @@ Copyright Grégory Soutadé - License ------- @@ -125,7 +138,6 @@ libgourou : LGPL v3 or later utils : BSD - Special thanks -------------- From 4acf401031dc8e32d3f62cfc17aea1fee25cc146 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9gory=20Soutad=C3=A9?= Date: Sun, 3 Jul 2022 09:20:05 +0200 Subject: [PATCH 17/94] =?UTF-8?q?Don't=20clone=20base64=20repository=20at?= =?UTF-8?q?=20first=20build,=20use=20a=20static=20version=20of=20Base64.h?= =?UTF-8?q?=20(not=20modified=20since=20many=20years)=20Patch=20from=20Ngu?= =?UTF-8?q?y=E1=BB=85n=20Gia=20Phong?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Makefile | 2 +- include/Base64.h | 124 ++++++++++++++++++++++++++++++++++++++++++ scripts/setup.sh | 5 -- scripts/update_lib.sh | 3 - src/bytearray.cpp | 2 +- 5 files changed, 126 insertions(+), 10 deletions(-) create mode 100644 include/Base64.h diff --git a/Makefile b/Makefile index fac27c2..67d37cf 100644 --- a/Makefile +++ b/Makefile @@ -4,7 +4,7 @@ CXX ?= $(CROSS)g++ UPDFPARSERLIB = ./lib/updfparser/libupdfparser.a -CXXFLAGS=-Wall -fPIC -I./include -I./lib -I./lib/pugixml/src/ -I./lib/updfparser/include +CXXFLAGS=-Wall -fPIC -I./include -I./lib/pugixml/src/ -I./lib/updfparser/include LDFLAGS = $(UPDFPARSERLIB) BUILD_STATIC ?= 0 diff --git a/include/Base64.h b/include/Base64.h new file mode 100644 index 0000000..cdfdc04 --- /dev/null +++ b/include/Base64.h @@ -0,0 +1,124 @@ +#ifndef _MACARON_BASE64_H_ +#define _MACARON_BASE64_H_ + +/** + * The MIT License (MIT) + * Copyright (c) 2016 tomykaira + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#include + +namespace macaron { + +class Base64 { + public: + + static std::string Encode(const std::string data) { + static constexpr char sEncodingTable[] = { + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', + 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', + 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', + 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', + 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', + 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', + 'w', 'x', 'y', 'z', '0', '1', '2', '3', + '4', '5', '6', '7', '8', '9', '+', '/' + }; + + size_t in_len = data.size(); + size_t out_len = 4 * ((in_len + 2) / 3); + std::string ret(out_len, '\0'); + size_t i; + char *p = const_cast(ret.c_str()); + + for (i = 0; i < in_len - 2; i += 3) { + *p++ = sEncodingTable[(data[i] >> 2) & 0x3F]; + *p++ = sEncodingTable[((data[i] & 0x3) << 4) | ((int) (data[i + 1] & 0xF0) >> 4)]; + *p++ = sEncodingTable[((data[i + 1] & 0xF) << 2) | ((int) (data[i + 2] & 0xC0) >> 6)]; + *p++ = sEncodingTable[data[i + 2] & 0x3F]; + } + if (i < in_len) { + *p++ = sEncodingTable[(data[i] >> 2) & 0x3F]; + if (i == (in_len - 1)) { + *p++ = sEncodingTable[((data[i] & 0x3) << 4)]; + *p++ = '='; + } + else { + *p++ = sEncodingTable[((data[i] & 0x3) << 4) | ((int) (data[i + 1] & 0xF0) >> 4)]; + *p++ = sEncodingTable[((data[i + 1] & 0xF) << 2)]; + } + *p++ = '='; + } + + return ret; + } + + static std::string Decode(const std::string& input, std::string& out) { + static constexpr unsigned char kDecodingTable[] = { + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 62, 64, 64, 64, 63, + 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 64, 64, 64, 64, 64, 64, + 64, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, + 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 64, 64, 64, 64, 64, + 64, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, + 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64 + }; + + size_t in_len = input.size(); + if (in_len % 4 != 0) return "Input data size is not a multiple of 4"; + + size_t out_len = in_len / 4 * 3; + if (input[in_len - 1] == '=') out_len--; + if (input[in_len - 2] == '=') out_len--; + + out.resize(out_len); + + for (size_t i = 0, j = 0; i < in_len;) { + uint32_t a = input[i] == '=' ? 0 & i++ : kDecodingTable[static_cast(input[i++])]; + uint32_t b = input[i] == '=' ? 0 & i++ : kDecodingTable[static_cast(input[i++])]; + uint32_t c = input[i] == '=' ? 0 & i++ : kDecodingTable[static_cast(input[i++])]; + uint32_t d = input[i] == '=' ? 0 & i++ : kDecodingTable[static_cast(input[i++])]; + + uint32_t triple = (a << 3 * 6) + (b << 2 * 6) + (c << 1 * 6) + (d << 0 * 6); + + if (j < out_len) out[j++] = (triple >> 2 * 8) & 0xFF; + if (j < out_len) out[j++] = (triple >> 1 * 8) & 0xFF; + if (j < out_len) out[j++] = (triple >> 0 * 8) & 0xFF; + } + + return ""; + } + +}; + +} + +#endif /* _MACARON_BASE64_H_ */ diff --git a/scripts/setup.sh b/scripts/setup.sh index 865fe1e..a3965f4 100755 --- a/scripts/setup.sh +++ b/scripts/setup.sh @@ -8,11 +8,6 @@ if [ ! -d lib/pugixml ] ; then popd fi -# Base64 -if [ ! -d lib/base64 ] ; then - git clone https://gist.github.com/f0fd86b6c73063283afe550bc5d77594.git lib/base64 -fi - # uPDFParser if [ ! -d lib/updfparser ] ; then git clone git://soutade.fr/updfparser.git lib/updfparser diff --git a/scripts/update_lib.sh b/scripts/update_lib.sh index c742310..1605450 100755 --- a/scripts/update_lib.sh +++ b/scripts/update_lib.sh @@ -12,9 +12,6 @@ pushd lib/pugixml git pull origin latest popd -# Base64 -# Nothing to do - # uPDFParser pushd lib/updfparser git pull origin master diff --git a/src/bytearray.cpp b/src/bytearray.cpp index 0bfbbab..fdaeed9 100644 --- a/src/bytearray.cpp +++ b/src/bytearray.cpp @@ -18,7 +18,7 @@ */ #include -#include +#include #include From 3c73b8ccb3ec2d5f068caacff87185f71e393710 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9gory=20Soutad=C3=A9?= Date: Sun, 3 Jul 2022 09:22:06 +0200 Subject: [PATCH 18/94] =?UTF-8?q?Update=20.gitignore=20Patch=20from=20Nguy?= =?UTF-8?q?=E1=BB=85n=20Gia=20Phong?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 675508f..c806eaa 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ obj lib +*.o *.a *.so *~ From 33bb9832836446118fc05f3b8853ddfc116bde77 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9gory=20Soutad=C3=A9?= Date: Sun, 7 Aug 2022 16:44:14 +0200 Subject: [PATCH 19/94] Change log levels names to avoid collisions --- include/libgourou_common.h | 2 +- include/libgourou_log.h | 12 ++++++------ src/libgourou.cpp | 14 +++++++------- utils/drmprocessorclientimpl.cpp | 24 ++++++++++++------------ 4 files changed, 26 insertions(+), 26 deletions(-) diff --git a/include/libgourou_common.h b/include/libgourou_common.h index 2328208..2f7b098 100644 --- a/include/libgourou_common.h +++ b/include/libgourou_common.h @@ -140,7 +140,7 @@ namespace gourou std::stringstream msg; msg << "Exception code : 0x" << std::setbase(16) << code << std::endl; msg << "Message : " << message << std::endl; - if (logLevel >= DEBUG) + if (logLevel >= LG_LOG_DEBUG) msg << "File : " << file << ":" << std::setbase(10) << line << std::endl; fullmessage = strdup(msg.str().c_str()); } diff --git a/include/libgourou_log.h b/include/libgourou_log.h index 3aa342a..0d98300 100644 --- a/include/libgourou_log.h +++ b/include/libgourou_log.h @@ -24,16 +24,16 @@ namespace gourou { enum GOUROU_LOG_LEVEL { - ERROR, - WARN, - INFO, - DEBUG, - TRACE + LG_LOG_ERROR, + LG_LOG_WARN, + LG_LOG_INFO, + LG_LOG_DEBUG, + LG_LOG_TRACE }; extern GOUROU_LOG_LEVEL logLevel; -#define GOUROU_LOG(__lvl, __msg) if (__lvl <= gourou::logLevel) {std::cout << __msg << std::endl << std::flush;} +#define GOUROU_LOG(__lvl, __msg) if (gourou::LG_LOG_##__lvl <= gourou::logLevel) {std::cout << __msg << std::endl << std::flush;} #define GOUROU_LOG_FUNC() GOUROU_LOG(TRACE, __FUNCTION__ << "() @ " << __FILE__ << ":" << __LINE__) /** diff --git a/src/libgourou.cpp b/src/libgourou.cpp index d0f22ae..148e8af 100644 --- a/src/libgourou.cpp +++ b/src/libgourou.cpp @@ -37,7 +37,7 @@ namespace gourou { - GOUROU_LOG_LEVEL logLevel = WARN; + GOUROU_LOG_LEVEL logLevel = LG_LOG_WARN; const std::string DRMProcessor::VERSION = LIBGOUROU_VERSION; DRMProcessor::DRMProcessor(DRMProcessorClient* client):client(client), device(0), user(0) @@ -89,7 +89,7 @@ namespace gourou uint16_t nlength = htons(length); char c; - if (logLevel >= TRACE) + if (logLevel >= LG_LOG_TRACE) printf("%02x %02x ", ((uint8_t*)&nlength)[0], ((uint8_t*)&nlength)[1]); client->digestUpdate(sha_ctx, (unsigned char*)&nlength, sizeof(nlength)); @@ -98,17 +98,17 @@ namespace gourou { c = string[i]; client->digestUpdate(sha_ctx, (unsigned char*)&c, 1); - if (logLevel >= TRACE) + if (logLevel >= LG_LOG_TRACE) printf("%c", c); } - if (logLevel >= TRACE) + if (logLevel >= LG_LOG_TRACE) printf("\n"); } void DRMProcessor::pushTag(void* sha_ctx, uint8_t tag) { client->digestUpdate(sha_ctx, &tag, sizeof(tag)); - if (logLevel >= TRACE) + if (logLevel >= LG_LOG_TRACE) printf("%02x ", tag); } @@ -226,7 +226,7 @@ namespace gourou client->digestFinalize(sha_ctx, sha_out); - if (logLevel >= DEBUG) + if (logLevel >= LG_LOG_DEBUG) { printf("\nSHA OUT : "); for(int i=0; i<(int)SHA1_LEN; i++) @@ -252,7 +252,7 @@ namespace gourou client->RSAPrivateEncrypt(privateRSAKey.data(), privateRSAKey.length(), RSAInterface::RSA_KEY_PKCS12, deviceKey.toBase64().data(), sha_out, sizeof(sha_out), res); - if (logLevel >= DEBUG) + if (logLevel >= LG_LOG_DEBUG) { printf("Sig : "); for(int i=0; i<(int)sizeof(res); i++) diff --git a/utils/drmprocessorclientimpl.cpp b/utils/drmprocessorclientimpl.cpp index 2edc25f..555e5ee 100644 --- a/utils/drmprocessorclientimpl.cpp +++ b/utils/drmprocessorclientimpl.cpp @@ -124,7 +124,7 @@ static int downloadProgress(void *clientp, curl_off_t dltotal, curl_off_t dlnow, curl_off_t ultotal, curl_off_t ulnow) { // For "big" files only - if (dltotal >= DISPLAY_THRESHOLD && gourou::logLevel >= gourou::WARN) + if (dltotal >= DISPLAY_THRESHOLD && gourou::logLevel >= gourou::LG_LOG_WARN) { int percent = 0; if (dltotal) @@ -174,7 +174,7 @@ static size_t curlHeaders(char *buffer, size_t size, size_t nitems, void *userda (*responseHeaders)[key] = value; - if (gourou::logLevel >= gourou::DEBUG) + if (gourou::logLevel >= gourou::LG_LOG_DEBUG) std::cout << key << " : " << value << std::endl; } @@ -189,10 +189,10 @@ std::string DRMProcessorClientImpl::sendHTTPRequest(const std::string& URL, cons if (!responseHeaders) responseHeaders = &localHeaders; - GOUROU_LOG(gourou::INFO, "Send request to " << URL); + GOUROU_LOG(INFO, "Send request to " << URL); if (POSTData.size()) { - GOUROU_LOG(gourou::DEBUG, "<<< " << std::endl << POSTData); + GOUROU_LOG(DEBUG, "<<< " << std::endl << POSTData); } unsigned prevDownloadedBytes; @@ -202,11 +202,11 @@ std::string DRMProcessorClientImpl::sendHTTPRequest(const std::string& URL, cons struct stat _stat; if (!fstat(fd, &_stat)) { - GOUROU_LOG(gourou::WARN, "Resume download @ " << _stat.st_size << " bytes"); + GOUROU_LOG(WARN, "Resume download @ " << _stat.st_size << " bytes"); downloadedBytes = _stat.st_size; } else - GOUROU_LOG(gourou::WARN, "Want to resume, but fstat failed"); + GOUROU_LOG(WARN, "Want to resume, but fstat failed"); } CURL *curl = curl_easy_init(); @@ -262,7 +262,7 @@ std::string DRMProcessorClientImpl::sendHTTPRequest(const std::string& URL, cons // Connexion failed, wait & retry if (res == CURLE_COULDNT_CONNECT) { - GOUROU_LOG(gourou::WARN, "\nConnection failed, attempt " << (i+1) << "/" << HTTP_REQ_MAX_RETRY); + GOUROU_LOG(WARN, "\nConnection failed, attempt " << (i+1) << "/" << HTTP_REQ_MAX_RETRY); } // Transfer failed but some data has been received // --> try again without incrementing tries @@ -270,11 +270,11 @@ std::string DRMProcessorClientImpl::sendHTTPRequest(const std::string& URL, cons { if (prevDownloadedBytes != downloadedBytes) { - GOUROU_LOG(gourou::WARN, "\nConnection broken, but data received, try again"); + GOUROU_LOG(WARN, "\nConnection broken, but data received, try again"); i--; } else - GOUROU_LOG(gourou::WARN, "\nConnection broken and no data received, attempt " << (i+1) << "/" << HTTP_REQ_MAX_RETRY); + GOUROU_LOG(WARN, "\nConnection broken and no data received, attempt " << (i+1) << "/" << HTTP_REQ_MAX_RETRY); } // Other error --> fail else @@ -291,12 +291,12 @@ std::string DRMProcessorClientImpl::sendHTTPRequest(const std::string& URL, cons EXCEPTION(gourou::CLIENT_NETWORK_ERROR, "Error " << curl_easy_strerror(res)); if ((downloadedBytes >= DISPLAY_THRESHOLD || replyData.size() >= DISPLAY_THRESHOLD) && - gourou::logLevel >= gourou::WARN) + gourou::logLevel >= gourou::LG_LOG_WARN) std::cout << std::endl; if ((*responseHeaders)["Content-Type"] == "application/vnd.adobe.adept+xml") { - GOUROU_LOG(gourou::DEBUG, ">>> " << std::endl << replyData.data()); + GOUROU_LOG(DEBUG, ">>> " << std::endl << replyData.data()); } return std::string((char*)replyData.data(), replyData.length()); @@ -360,7 +360,7 @@ void DRMProcessorClientImpl::RSAPrivateDecrypt(const unsigned char* RSAKey, unsi if (ret < 0) EXCEPTION(gourou::CLIENT_RSA_ERROR, ERR_error_string(ERR_get_error(), NULL)); - if (gourou::logLevel >= gourou::DEBUG) + if (gourou::logLevel >= gourou::LG_LOG_DEBUG) { printf("Decrypted : "); for(int i=0; i Date: Sun, 7 Aug 2022 16:45:12 +0200 Subject: [PATCH 20/94] Forward DEBUG flag in Makefile --- Makefile | 2 +- utils/Makefile | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 67d37cf..329341e 100644 --- a/Makefile +++ b/Makefile @@ -24,7 +24,7 @@ endif ifneq ($(DEBUG),) -CXXFLAGS += -ggdb -O0 +CXXFLAGS += -ggdb -O0 -DDEBUG else CXXFLAGS += -O2 endif diff --git a/utils/Makefile b/utils/Makefile index 3d1de32..b9fd76f 100644 --- a/utils/Makefile +++ b/utils/Makefile @@ -23,7 +23,7 @@ LDFLAGS += -lgourou endif ifneq ($(DEBUG),) -CXXFLAGS += -ggdb -O0 +CXXFLAGS += -ggdb -O0 -DDEBUG else CXXFLAGS += -O2 endif From 57c3a5899436ac3ef3b1761962d2820725c4a66b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9gory=20Soutad=C3=A9?= Date: Sun, 7 Aug 2022 16:45:59 +0200 Subject: [PATCH 21/94] Add STATIC_NONCE option for build (developper mode) --- Makefile | 4 ++++ src/libgourou.cpp | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/Makefile b/Makefile index 329341e..3637eac 100644 --- a/Makefile +++ b/Makefile @@ -29,6 +29,10 @@ else CXXFLAGS += -O2 endif +ifneq ($(STATIC_NONCE),) +CXXFLAGS += -DSTATIC_NONCE=1 +endif + SRCDIR := src INCDIR := inc BUILDDIR := obj diff --git a/src/libgourou.cpp b/src/libgourou.cpp index 148e8af..cdae6a7 100644 --- a/src/libgourou.cpp +++ b/src/libgourou.cpp @@ -282,7 +282,11 @@ namespace gourou struct timeval tv; gettimeofday(&tv, 0); uint32_t nonce32[2] = {0x6f046000, 0x388a}; +#ifdef STATIC_NONCE + uint64_t bigtime = 0xAA001122BBCCAAULL; +#else uint64_t bigtime = tv.tv_sec*1000; +#endif nonce32[0] += (bigtime & 0xFFFFFFFF) + (tv.tv_usec/1000); nonce32[1] += ((bigtime >> 32) & 0xFFFFFFFF); From 600535d52c78718b93c3b8fc0ccdf479f3d590ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9gory=20Soutad=C3=A9?= Date: Wed, 10 Aug 2022 21:37:43 +0200 Subject: [PATCH 22/94] Utils: Migration to OpenSSL3 --- Makefile | 3 +- include/libgourou_common.h | 1 + utils/Makefile | 10 -- utils/drmprocessorclientimpl.cpp | 155 +++++++++++++++++++++---------- utils/drmprocessorclientimpl.h | 4 + 5 files changed, 114 insertions(+), 59 deletions(-) diff --git a/Makefile b/Makefile index 3637eac..d05d86d 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,3 @@ - AR ?= $(CROSS)ar CXX ?= $(CROSS)g++ @@ -67,7 +66,7 @@ libgourou.so: $(OBJECTS) $(UPDFPARSERLIB) $(CXX) obj/*.o $(LDFLAGS) -o $@ -shared build_utils: - make -C utils ROOT=$(PWD) CXX=$(CXX) AR=$(AR) DEBUG=$(DEBUG) STATIC_UTILS=$(STATIC_UTILS) OPENSSL3=$(OPENSSL3) + make -C utils ROOT=$(PWD) CXX=$(CXX) AR=$(AR) DEBUG=$(DEBUG) STATIC_UTILS=$(STATIC_UTILS) clean: rm -rf libgourou.a libgourou.so obj diff --git a/include/libgourou_common.h b/include/libgourou_common.h index 2f7b098..f8ab290 100644 --- a/include/libgourou_common.h +++ b/include/libgourou_common.h @@ -106,6 +106,7 @@ namespace gourou CLIENT_INVALID_PKCS12, CLIENT_INVALID_CERTIFICATE, CLIENT_NO_PRIV_KEY, + CLIENT_NO_PUB_KEY, CLIENT_RSA_ERROR, CLIENT_BAD_CHAINING, CLIENT_BAD_KEY_SIZE, diff --git a/utils/Makefile b/utils/Makefile index b9fd76f..4d7b9e4 100644 --- a/utils/Makefile +++ b/utils/Makefile @@ -3,16 +3,6 @@ TARGETS=acsmdownloader adept_activate adept_remove adept_loan_mgt CXXFLAGS=-Wall -fPIC -I$(ROOT)/include -I$(ROOT)/lib/pugixml/src/ -LDFLAGS= - -ifneq ($(OPENSSL3),) -# OpenSSL 1.1.0 compat -CXXFLAGS += -DOPENSSL_API_COMPAT=0x10100000L -CXXFLAGS += -I/tmp/openssl3/usr/include/ -I/tmp/openssl3/usr/include/x86_64-linux-gnu -LDFLAGS += -L/tmp/openssl3/usr/lib/x86_64-linux-gnu -L/tmp/openssl3/usr/lib/x86_64-linux-gnu/ossl-modules -endif - - STATIC_DEP= LDFLAGS += -L$(ROOT) -lcrypto -lzip -lz -lcurl diff --git a/utils/drmprocessorclientimpl.cpp b/utils/drmprocessorclientimpl.cpp index 555e5ee..c62e2d8 100644 --- a/utils/drmprocessorclientimpl.cpp +++ b/utils/drmprocessorclientimpl.cpp @@ -31,6 +31,8 @@ #include #include +#define OPENSSL_NO_DEPRECATED 1 + #include #include #include @@ -302,68 +304,110 @@ std::string DRMProcessorClientImpl::sendHTTPRequest(const std::string& URL, cons return std::string((char*)replyData.data(), replyData.length()); } +void DRMProcessorClientImpl::padWithPKCS1(unsigned char* out, unsigned int outLength, + const unsigned char* in, unsigned int inLength) +{ + if (outLength < (inLength + 3)) + EXCEPTION(gourou::CLIENT_RSA_ERROR, "Not enough space for PKCS1 padding"); + + /* + PKCS1v5 Padding is : + 0x00 0x01 0xff * n 0x00 dataIn + */ + + memset(out, 0xFF, outLength); + + out[0] = 0x0; + out[1] = 0x1; + out[outLength - inLength - 1] = 0x00; + memcpy(&out[outLength - inLength], in, inLength); +} + + void DRMProcessorClientImpl::RSAPrivateEncrypt(const unsigned char* RSAKey, unsigned int RSAKeyLength, const RSA_KEY_TYPE keyType, const std::string& password, const unsigned char* data, unsigned dataLength, unsigned char* res) { PKCS12 * pkcs12; - EVP_PKEY* pkey; - X509* cert; - STACK_OF(X509)* ca; - RSA * rsa; - + EVP_PKEY_CTX *ctx; + EVP_PKEY* pkey = NULL; + size_t outlen; + unsigned char* tmp; + int ret; + pkcs12 = d2i_PKCS12(NULL, &RSAKey, RSAKeyLength); if (!pkcs12) EXCEPTION(gourou::CLIENT_INVALID_PKCS12, ERR_error_string(ERR_get_error(), NULL)); - PKCS12_parse(pkcs12, password.c_str(), &pkey, &cert, &ca); - - if (!pkey) + if (PKCS12_parse(pkcs12, password.c_str(), &pkey, NULL, NULL) <= 0) EXCEPTION(gourou::CLIENT_INVALID_PKCS12, ERR_error_string(ERR_get_error(), NULL)); - rsa = EVP_PKEY_get1_RSA(pkey); + outlen = EVP_PKEY_get_size(pkey); - int ret = RSA_private_encrypt(dataLength, data, res, rsa, RSA_PKCS1_PADDING); + ctx = EVP_PKEY_CTX_new(pkey, NULL); - if (ret < 0) + /* Use RSA private key */ + if (EVP_PKEY_decrypt_init(ctx) <= 0) EXCEPTION(gourou::CLIENT_RSA_ERROR, ERR_error_string(ERR_get_error(), NULL)); - if (gourou::logLevel >= gourou::DEBUG) - { - printf("Encrypted : "); - for(int i=0; i= gourou::LG_LOG_DEBUG) { printf("Decrypted : "); - for(int i=0; i= 3 OSSL_PROVIDER *legacy, *deflt; #else From 086e9b0610fb00d21a859a20dfc80ab152fab414 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9gory=20Soutad=C3=A9?= Date: Thu, 11 Aug 2022 21:07:18 +0200 Subject: [PATCH 23/94] Update version --- include/libgourou.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/libgourou.h b/include/libgourou.h index 0bc9269..1fe4988 100644 --- a/include/libgourou.h +++ b/include/libgourou.h @@ -40,7 +40,7 @@ #define ACS_SERVER "http://adeactivate.adobe.com/adept" #endif -#define LIBGOUROU_VERSION "0.7.2" +#define LIBGOUROU_VERSION "0.7.3" namespace gourou { From 7f5b787cb98a4ded5586b4ab189f7ec43c5d0192 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9gory=20Soutad=C3=A9?= Date: Mon, 15 Aug 2022 09:54:13 +0200 Subject: [PATCH 24/94] Add launcher util for AppImage --- utils/Makefile | 13 ++----------- utils/acsmdownloader.cpp | 2 +- utils/adept_activate.cpp | 2 +- utils/adept_loan_mgt.cpp | 2 +- utils/adept_remove.cpp | 2 +- utils/launcher.cpp | 40 ++++++++++++++++++++++++++++++++++++++++ 6 files changed, 46 insertions(+), 15 deletions(-) create mode 100644 utils/launcher.cpp diff --git a/utils/Makefile b/utils/Makefile index 4d7b9e4..4827a94 100644 --- a/utils/Makefile +++ b/utils/Makefile @@ -1,5 +1,5 @@ -TARGETS=acsmdownloader adept_activate adept_remove adept_loan_mgt +TARGETS=acsmdownloader adept_activate adept_remove adept_loan_mgt launcher CXXFLAGS=-Wall -fPIC -I$(ROOT)/include -I$(ROOT)/lib/pugixml/src/ @@ -29,16 +29,7 @@ ${COMMON_LIB}: ${COMMON_DEPS} ${STATIC_DEP} $(CXX) $(CXXFLAGS) ${COMMON_DEPS} $(LDFLAGS) -c $(AR) crs $@ ${COMMON_OBJECTS} $(STATIC_DEP) -acsmdownloader: acsmdownloader.cpp ${COMMON_LIB} - $(CXX) $(CXXFLAGS) $^ $(LDFLAGS) -o $@ - -adept_activate: adept_activate.cpp ${COMMON_LIB} - $(CXX) $(CXXFLAGS) $^ $(LDFLAGS) -o $@ - -adept_remove: adept_remove.cpp ${COMMON_LIB} - $(CXX) $(CXXFLAGS) $^ $(LDFLAGS) -o $@ - -adept_loan_mgt: adept_loan_mgt.cpp ${COMMON_LIB} +%: %.cpp ${COMMON_LIB} $(CXX) $(CXXFLAGS) $^ $(LDFLAGS) -o $@ clean: diff --git a/utils/acsmdownloader.cpp b/utils/acsmdownloader.cpp index dbe551f..48ab0f2 100644 --- a/utils/acsmdownloader.cpp +++ b/utils/acsmdownloader.cpp @@ -184,7 +184,7 @@ static void usage(const char* cmd) { std::cout << "Download EPUB file from ACSM request file" << std::endl; - std::cout << "Usage: " << cmd << " [(-d|--device-file) device.xml] [(-a|--activation-file) activation.xml] [(-k|--device-key-file) devicesalt] [(-O|--output-dir) dir] [(-o|--output-file) output(.epub|.pdf|.der)] [(-r|--resume)] [(-v|--verbose)] [(-h|--help)] (-f|--acsm-file) file.acsm|(-e|--export-private-key)" << std::endl << std::endl; + std::cout << "Usage: " << basename((char*)cmd) << " [(-d|--device-file) device.xml] [(-a|--activation-file) activation.xml] [(-k|--device-key-file) devicesalt] [(-O|--output-dir) dir] [(-o|--output-file) output(.epub|.pdf|.der)] [(-r|--resume)] [(-v|--verbose)] [(-h|--help)] (-f|--acsm-file) file.acsm|(-e|--export-private-key)" << std::endl << std::endl; std::cout << " " << "-d|--device-file" << "\t" << "device.xml file from eReader" << std::endl; std::cout << " " << "-a|--activation-file" << "\t" << "activation.xml file from eReader" << std::endl; diff --git a/utils/adept_activate.cpp b/utils/adept_activate.cpp index 3937939..bee58d3 100644 --- a/utils/adept_activate.cpp +++ b/utils/adept_activate.cpp @@ -126,7 +126,7 @@ static void usage(const char* cmd) { std::cout << "Create new device files used by ADEPT DRM" << std::endl; - std::cout << "Usage: " << cmd << " (-a|--anonymous) | ( (-u|--username) username [(-p|--password) password] ) [(-O|--output-dir) dir] [(-r|--random-serial)] [(-v|--verbose)] [(-h|--help)]" << std::endl << std::endl; + std::cout << "Usage: " << basename((char*)cmd) << " (-a|--anonymous) | ( (-u|--username) username [(-p|--password) password] ) [(-O|--output-dir) dir] [(-r|--random-serial)] [(-v|--verbose)] [(-h|--help)]" << std::endl << std::endl; std::cout << " " << "-a|--anonymous" << "\t" << "Anonymous account, no need for username/password (Use it only with a DRM removal software)" << std::endl; std::cout << " " << "-u|--username" << "\t\t" << "AdobeID username (ie adobe.com email account)" << std::endl; diff --git a/utils/adept_loan_mgt.cpp b/utils/adept_loan_mgt.cpp index a8ac487..791ab9e 100644 --- a/utils/adept_loan_mgt.cpp +++ b/utils/adept_loan_mgt.cpp @@ -336,7 +336,7 @@ static void usage(const char* cmd) { std::cout << "Manage loaned books" << std::endl; - std::cout << "Usage: " << cmd << " [(-d|--activation-dir) dir] (-l|--list)|(-D|--delete loanID)|(-R|--delete loanID) [(-v|--verbose)] [(-h|--help)]" << std::endl << std::endl; + std::cout << "Usage: " << basename((char*)cmd) << " [(-d|--activation-dir) dir] (-l|--list)|(-D|--delete loanID)|(-R|--delete loanID) [(-v|--verbose)] [(-h|--help)]" << std::endl << std::endl; std::cout << " " << "-d|--activation-dir" << "\t" << "Directory of device.xml/activation.xml and device key" << std::endl; std::cout << " " << "-l|--list" << "\t\t" << "List all loaned books" << std::endl; diff --git a/utils/adept_remove.cpp b/utils/adept_remove.cpp index a56f6b0..1a16c6d 100644 --- a/utils/adept_remove.cpp +++ b/utils/adept_remove.cpp @@ -143,7 +143,7 @@ static void usage(const char* cmd) { std::cout << "Remove ADEPT DRM (from Adobe) of EPUB/PDF file" << std::endl; - std::cout << "Usage: " << cmd << " [(-d|--device-file) device.xml] [(-a|--activation-file) activation.xml] [(-k|--device-key-file) devicesalt] [(-O|--output-dir) dir] [(-o|--output-file) output(.epub|.pdf|.der)] [(-v|--verbose)] [(-h|--help)] (-f|--input-file) file(.epub|pdf)" << std::endl << std::endl; + std::cout << "Usage: " << basename((char*)cmd) << " [(-d|--device-file) device.xml] [(-a|--activation-file) activation.xml] [(-k|--device-key-file) devicesalt] [(-O|--output-dir) dir] [(-o|--output-file) output(.epub|.pdf|.der)] [(-v|--verbose)] [(-h|--help)] (-f|--input-file) file(.epub|pdf)" << std::endl << std::endl; std::cout << " " << "-d|--device-file" << "\t" << "device.xml file from eReader" << std::endl; std::cout << " " << "-a|--activation-file" << "\t" << "activation.xml file from eReader" << std::endl; diff --git a/utils/launcher.cpp b/utils/launcher.cpp new file mode 100644 index 0000000..99f8495 --- /dev/null +++ b/utils/launcher.cpp @@ -0,0 +1,40 @@ +#include +#include +#include +#include + +#include "utils_common.h" + +#ifndef DEFAULT_UTIL +#define DEFAULT_UTIL "acsmdownloader" +#endif + +/* Inspired from https://discourse.appimage.org/t/call-alternative-binary-from-appimage/93/10*/ + +int main(int argc, char** argv) +{ + char* util, *argv0; + char* mountPoint = getenv("APPDIR"); + std::string fullPath; + + /* Original command is in ARGV0 env variable*/ + argv0 = strdup(getenv("ARGV0")); + util = basename(argv0); + + fullPath = std::string(mountPoint) + util; + + if (std::string(util) == "launcher" || !fileExists(fullPath.c_str())) + fullPath = std::string(mountPoint) + DEFAULT_UTIL; + + free(argv0); + + argv[0] = strdup(fullPath.c_str()); + + if (execvp(argv[0], argv)) + std::cout << "Unable to launch '" << argv[0] << "'" << std::endl; + + /* Should not happens */ + free(argv[0]); + + return 0; +} From 7084fb70257871d60a3ad70a2c98fec67a8f7d52 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9gory=20Soutad=C3=A9?= Date: Sat, 27 Aug 2022 15:40:32 +0200 Subject: [PATCH 25/94] Add fromHex() static function to ByteArray --- include/bytearray.h | 9 ++++++++- src/bytearray.cpp | 42 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 50 insertions(+), 1 deletion(-) diff --git a/include/bytearray.h b/include/bytearray.h index e2f8d29..8fb7680 100644 --- a/include/bytearray.h +++ b/include/bytearray.h @@ -104,6 +104,13 @@ namespace gourou */ std::string toBase64(); + /** + * @brief Convert hex string into bytes + * + * @param str Hex string + */ + static ByteArray fromHex(const std::string& str); + /** * @brief Return a string with human readable hex encoded internal data */ @@ -130,7 +137,7 @@ namespace gourou void append(const std::string& str); /** - * @brief Get internal data. Must bot be freed + * @brief Get internal data. Must not be freed */ unsigned char* data() {return _data;} diff --git a/src/bytearray.cpp b/src/bytearray.cpp index fdaeed9..f7ec87f 100644 --- a/src/bytearray.cpp +++ b/src/bytearray.cpp @@ -17,6 +17,7 @@ along with libgourou. If not, see . */ #include +#include #include @@ -155,6 +156,47 @@ namespace gourou return macaron::Base64::Encode(std::string((char*)_data, _length)); } + ByteArray ByteArray::fromHex(const std::string& str) + { + if (str.size() % 2) + throw std::invalid_argument("Size of hex string not multiple of 2"); + + ByteArray res((unsigned int)(str.size()/2)); + unsigned int i; + + unsigned char* data = res.data(); + unsigned char cur, tmp; + + for (i=0; i= 'a' && tmp <= 'f') + cur = (tmp - 'a' + 10) << 4; + else if (tmp >= 'A' && tmp <= 'F') + cur = (tmp - 'A' + 10) << 4; + else if (tmp >= '0' && tmp <= '9') + cur = (tmp - '0') << 4; + else + throw std::invalid_argument("Invalid character in hex string"); + + tmp = str[i+1]; + if (tmp >= 'a' && tmp <= 'f') + cur += tmp - 'a' + 10; + else if (tmp >= 'A' && tmp <= 'F') + cur += tmp - 'A' + 10; + else if (tmp >= '0' && tmp <= '9') + cur += tmp - '0'; + else + throw std::invalid_argument("Invalid character in hex string"); + + data[i/2] = cur; + } + + return res; + } + std::string ByteArray::toHex() { char* tmp = new char[_length*2+1]; From 56b3231f92e327d841d5775ebcf9cd757e630c28 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9gory=20Soutad=C3=A9?= Date: Sat, 27 Aug 2022 15:42:11 +0200 Subject: [PATCH 26/94] Add dumpBuffer() in libgourou_common --- include/libgourou_common.h | 14 ++++++++++++++ src/libgourou.cpp | 18 +++--------------- utils/drmprocessorclientimpl.cpp | 9 --------- 3 files changed, 17 insertions(+), 24 deletions(-) diff --git a/include/libgourou_common.h b/include/libgourou_common.h index f8ab290..bd4117b 100644 --- a/include/libgourou_common.h +++ b/include/libgourou_common.h @@ -415,6 +415,20 @@ namespace gourou } return 0; } + + static inline void dumpBuffer(GOUROU_LOG_LEVEL level, const char* title, const unsigned char* data, unsigned int len) + { + if (gourou::logLevel < level) + return; + + printf("%s", title); + for(unsigned int i=0; idigestFinalize(sha_ctx, sha_out); - if (logLevel >= LG_LOG_DEBUG) - { - printf("\nSHA OUT : "); - for(int i=0; i<(int)SHA1_LEN; i++) - printf("%02x ", sha_out[i]); - printf("\n"); - - } + dumpBuffer(gourou::LG_LOG_DEBUG, "\nSHA OUT : ", sha_out, SHA1_LEN); } void DRMProcessor::signNode(pugi::xml_node& rootNode) @@ -252,13 +245,8 @@ namespace gourou client->RSAPrivateEncrypt(privateRSAKey.data(), privateRSAKey.length(), RSAInterface::RSA_KEY_PKCS12, deviceKey.toBase64().data(), sha_out, sizeof(sha_out), res); - if (logLevel >= LG_LOG_DEBUG) - { - printf("Sig : "); - for(int i=0; i<(int)sizeof(res); i++) - printf("%02x ", res[i]); - printf("\n"); - } + + dumpBuffer(gourou::LG_LOG_DEBUG, "Sig : ", res, sizeof(res)); std::string signature = ByteArray(res, sizeof(res)).toBase64(); appendTextElem(rootNode, "adept:signature", signature); diff --git a/utils/drmprocessorclientimpl.cpp b/utils/drmprocessorclientimpl.cpp index c62e2d8..9f17171 100644 --- a/utils/drmprocessorclientimpl.cpp +++ b/utils/drmprocessorclientimpl.cpp @@ -46,7 +46,6 @@ #include #include -#include #include "drmprocessorclientimpl.h" DRMProcessorClientImpl::DRMProcessorClientImpl(): @@ -403,14 +402,6 @@ void DRMProcessorClientImpl::RSAPrivateDecrypt(const unsigned char* RSAKey, unsi if (ret <= 0) EXCEPTION(gourou::CLIENT_RSA_ERROR, ERR_error_string(ERR_get_error(), NULL)); - - if (gourou::logLevel >= gourou::LG_LOG_DEBUG) - { - printf("Decrypted : "); - for(int i=0; i<(int)outlen; i++) - printf("%02x ", res[i]); - printf("\n"); - } } void DRMProcessorClientImpl::RSAPublicEncrypt(const unsigned char* RSAKey, unsigned int RSAKeyLength, From 7b8c7acbadf5dbdbba534899ea0ab55db1102a64 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9gory=20Soutad=C3=A9?= Date: Sat, 27 Aug 2022 15:44:27 +0200 Subject: [PATCH 27/94] Compute first pass for encryptedKey if keyType attribute is set --- include/drmprocessorclient.h | 1 + include/libgourou.h | 1 + include/libgourou_common.h | 87 +++++++++++++++++++---------- src/libgourou.cpp | 104 +++++++++++++++++++++++++++++++---- utils/Makefile | 2 +- 5 files changed, 153 insertions(+), 42 deletions(-) diff --git a/include/drmprocessorclient.h b/include/drmprocessorclient.h index 6b02b81..3926595 100644 --- a/include/drmprocessorclient.h +++ b/include/drmprocessorclient.h @@ -111,6 +111,7 @@ namespace gourou public: enum RSA_KEY_TYPE { RSA_KEY_PKCS12 = 0, + RSA_KEY_PKCS8, RSA_KEY_X509 }; diff --git a/include/libgourou.h b/include/libgourou.h index 1fe4988..60f7fe6 100644 --- a/include/libgourou.h +++ b/include/libgourou.h @@ -231,6 +231,7 @@ namespace gourou void buildSignInRequest(pugi::xml_document& signInRequest, const std::string& adobeID, const std::string& adobePassword, const std::string& authenticationCertificate); void fetchLicenseServiceCertificate(const std::string& licenseURL, const std::string& operatorURL); + std::string encryptedKeyFirstPass(pugi::xml_document& rightsDoc, const std::string& encryptedKey, const std::string& keyType); void decryptADEPTKey(const std::string& encryptedKey, unsigned char* decryptedKey); void removeEPubDRM(const std::string& filenameIn, const std::string& filenameOut, const unsigned char* encryptionKey, unsigned encryptionKeySize); void generatePDFObjectKey(int version, diff --git a/include/libgourou_common.h b/include/libgourou_common.h index bd4117b..4a71aa3 100644 --- a/include/libgourou_common.h +++ b/include/libgourou_common.h @@ -126,7 +126,8 @@ namespace gourou DRM_FORMAT_NOT_SUPPORTED, DRM_IN_OUT_EQUALS, DRM_MISSING_PARAMETER, - DRM_INVALID_KEY_SIZE + DRM_INVALID_KEY_SIZE, + DRM_ERR_ENCRYPTION_KEY_FP }; /** @@ -225,35 +226,9 @@ namespace gourou * It can throw an exception if tag does not exists * or just return an empty value */ - static inline std::string extractTextElem(const pugi::xml_document& doc, const char* tagName, bool throwOnNull=true) + static inline std::string extractTextElem(const pugi::xml_node& root, const char* tagName, bool throwOnNull=true) { - pugi::xpath_node xpath_node = doc.select_node(tagName); - - if (!xpath_node) - { - if (throwOnNull) - EXCEPTION(GOUROU_TAG_NOT_FOUND, "Tag " << tagName << " not found"); - - return ""; - } - - pugi::xml_node node = xpath_node.node().first_child(); - - if (!node) - { - if (throwOnNull) - EXCEPTION(GOUROU_TAG_NOT_FOUND, "Text element for tag " << tagName << " not found"); - - return ""; - } - - std::string res = node.value(); - return trim(res); - } - - static inline std::string extractTextElem(const pugi::xml_node& doc, const char* tagName, bool throwOnNull=true) - { - pugi::xpath_node xpath_node = doc.select_node(tagName); + pugi::xpath_node xpath_node = root.select_node(tagName); if (!xpath_node) { @@ -277,6 +252,37 @@ namespace gourou return trim(res); } + /** + * @brief Extract text attribute from tag in document + * It can throw an exception if attribute does not exists + * or just return an empty value + */ + static inline std::string extractTextAttribute(const pugi::xml_node& root, const char* tagName, const char* attributeName, bool throwOnNull=true) + { + pugi::xpath_node xpath_node = root.select_node(tagName); + + if (!xpath_node) + { + if (throwOnNull) + EXCEPTION(GOUROU_TAG_NOT_FOUND, "Tag " << tagName << " not found"); + + return ""; + } + + pugi::xml_attribute attr = xpath_node.node().attribute(attributeName); + + if (!attr) + { + if (throwOnNull) + EXCEPTION(GOUROU_TAG_NOT_FOUND, "Attribute element " << attributeName << " for tag " << tagName << " not found"); + + return ""; + } + + std::string res = attr.value(); + return trim(res); + } + /** * @brief Append an element to root with a sub text element * @@ -290,6 +296,29 @@ namespace gourou node.append_child(pugi::node_pcdata).set_value(value.c_str()); } + /** + * Remove "urn:uuid:" prefix and all '-' from uuid + * urn:uuid:9cb786e8-586a-4950-8901-fff8d2ee6025 + * -> + * 9cb786e8586a49508901fff8d2ee6025 + */ + static inline std::string extractIdFromUUID(const std::string& uuid) + { + unsigned int i = 0; + std::string res; + + if (uuid.find("urn:uuid:") == 0) + i = 9; + + for(; igetPrivateLicenseKey(); ByteArray privateRSAKey = ByteArray::fromBase64(privateKeyData); - - ByteArray deviceKey(device->getDeviceKey(), Device::DEVICE_KEY_SIZE); - std::string pkcs12 = user->getPKCS12(); - client->RSAPrivateDecrypt(privateRSAKey.data(), privateRSAKey.length(), - RSAInterface::RSA_KEY_PKCS12, deviceKey.toBase64().data(), - arrayEncryptedKey.data(), arrayEncryptedKey.length(), decryptedKey); + dumpBuffer(gourou::LG_LOG_DEBUG, "To decrypt : ", arrayEncryptedKey.data(), arrayEncryptedKey.length()); - if (decryptedKey[0] != 0x00 || decryptedKey[1] != 0x02 || - decryptedKey[RSA_KEY_SIZE-16-1] != 0x00) - EXCEPTION(DRM_ERR_ENCRYPTION_KEY, "Unable to retrieve encryption key"); + client->RSAPrivateDecrypt(privateRSAKey.data(), privateRSAKey.length(), + RSAInterface::RSA_KEY_PKCS8, "", + arrayEncryptedKey.data(), arrayEncryptedKey.length(), decryptedKey); } + /** + * RSA Key can be over encrypted with AES128-CBC if keyType attribute is set + * Key = SHA256(keyType)[14:22] || SHA256(keyType)[7:13] + * IV = DeviceID ^ FulfillmentId ^ VoucherId + * + * @return Base64 encoded decrypted key + */ + std::string DRMProcessor::encryptedKeyFirstPass(pugi::xml_document& rightsDoc, const std::string& encryptedKey, const std::string& keyType) + { + unsigned char digest[32], key[16], iv[16]; + unsigned int dataOutLength; + std::string id; + + client->digest("SHA256", (unsigned char*)keyType.c_str(), keyType.size(), digest); + memcpy(key, &digest[14], 9); + memcpy(&key[9], &digest[7], 7); + + id = extractTextElem(rightsDoc, "/adept:rights/licenseToken/device"); + if (id == "") + EXCEPTION(DRM_ERR_ENCRYPTION_KEY_FP, "Device id not found in rights.xml"); + ByteArray deviceId = ByteArray::fromHex(extractIdFromUUID(id)); + unsigned char* _deviceId = deviceId.data(); + + id = extractTextElem(rightsDoc, "/adept:rights/licenseToken/fulfillment"); + if (id == "") + EXCEPTION(DRM_ERR_ENCRYPTION_KEY_FP, "Fulfillment id not found in rights.xml"); + ByteArray fulfillmentId = ByteArray::fromHex(extractIdFromUUID(id)); + unsigned char* _fulfillmentId = fulfillmentId.data(); + + id = extractTextElem(rightsDoc, "/adept:rights/licenseToken/voucher"); + if (id == "") + EXCEPTION(DRM_ERR_ENCRYPTION_KEY_FP, "Voucher id not found in rights.xml"); + ByteArray voucherId = ByteArray::fromHex(extractIdFromUUID(id)); + unsigned char* _voucherId = voucherId.data(); + + if (deviceId.size() < sizeof(iv) || fulfillmentId.size() < sizeof(iv) || voucherId.size() < sizeof(iv)) + EXCEPTION(DRM_ERR_ENCRYPTION_KEY_FP, "One id has a bad length"); + + for(unsigned int i=0; iDecrypt(CryptoInterface::ALGO_AES, CryptoInterface::CHAIN_CBC, + (const unsigned char*)key, (unsigned int)sizeof(key), + (const unsigned char*)iv, (unsigned int)sizeof(iv), + (const unsigned char*)arrayEncryptedKey.data(), arrayEncryptedKey.size(), + (unsigned char*)clearRSAKey, &dataOutLength); + + dumpBuffer(gourou::LG_LOG_DEBUG, "\nDecrypted key : ", clearRSAKey, dataOutLength); + + /* Last block could be 0x10*16 which is OpenSSL padding, remove it if it's the case */ + bool skipLastLine = true; + for(unsigned int i=dataOutLength-16; i Date: Mon, 29 Aug 2022 12:18:29 +0200 Subject: [PATCH 28/94] Make DRMProcessorClient API more consistent --- include/drmprocessorclient.h | 30 ++--- include/libgourou_common.h | 2 + src/libgourou.cpp | 12 +- utils/drmprocessorclientimpl.cpp | 185 +++++++++++++++++++------------ utils/drmprocessorclientimpl.h | 22 ++-- 5 files changed, 143 insertions(+), 108 deletions(-) diff --git a/include/drmprocessorclient.h b/include/drmprocessorclient.h index 3926595..8d24ed7 100644 --- a/include/drmprocessorclient.h +++ b/include/drmprocessorclient.h @@ -47,20 +47,16 @@ namespace gourou * @param handler Digest handler * @param data Data to digest * @param length Length of data - * - * @return OK/KO */ - virtual int digestUpdate(void* handler, unsigned char* data, unsigned int length) = 0; + virtual void digestUpdate(void* handler, unsigned char* data, unsigned int length) = 0; /** * @brief Finalize digest with remained buffered data and destroy handler * * @param handler Digest handler * @param digestOut Digest result (buffer must be pre allocated with right size) - * - * @return OK/KO */ - virtual int digestFinalize(void* handler, unsigned char* digestOut) = 0; + virtual void digestFinalize(void* handler, unsigned char* digestOut) = 0; /** * @brief Global digest function @@ -69,10 +65,8 @@ namespace gourou * @param data Data to digest * @param length Length of data * @param digestOut Digest result (buffer must be pre allocated with right size) - * - * @return OK/KO */ - virtual int digest(const std::string& digestName, unsigned char* data, unsigned int length, unsigned char* digestOut) = 0; + virtual void digest(const std::string& digestName, unsigned char* data, unsigned int length, unsigned char* digestOut) = 0; }; class RandomInterface @@ -239,7 +233,7 @@ namespace gourou * @param dataOut Encrypted data * @param dataOutLength Length of encrypted data */ - virtual void Encrypt(CRYPTO_ALGO algo, CHAINING_MODE chaining, + virtual void encrypt(CRYPTO_ALGO algo, CHAINING_MODE chaining, const unsigned char* key, unsigned int keyLength, const unsigned char* iv, unsigned int ivLength, const unsigned char* dataIn, unsigned int dataInLength, @@ -256,7 +250,7 @@ namespace gourou * * @return AES handler */ - virtual void* EncryptInit(CRYPTO_ALGO algo, CHAINING_MODE chaining, + virtual void* encryptInit(CRYPTO_ALGO algo, CHAINING_MODE chaining, const unsigned char* key, unsigned int keyLength, const unsigned char* iv=0, unsigned int ivLength=0) = 0; @@ -269,18 +263,18 @@ namespace gourou * @param dataOut Encrypted data * @param dataOutLength Length of encrypted data */ - virtual void EncryptUpdate(void* handler, const unsigned char* dataIn, unsigned int dataInLength, + virtual void encryptUpdate(void* handler, const unsigned char* dataIn, unsigned int dataInLength, unsigned char* dataOut, unsigned int* dataOutLength) = 0; /** - * @brief Finalizeencryption (pad and encrypt last block if needed) + * @brief Finalize encryption (pad and encrypt last block if needed) * Destroy handler at the end * * @param handler Crypto handler * @param dataOut Last block of encrypted data * @param dataOutLength Length of encrypted data */ - virtual void EncryptFinalize(void* handler, unsigned char* dataOut, unsigned int* dataOutLength) = 0; + virtual void encryptFinalize(void* handler, unsigned char* dataOut, unsigned int* dataOutLength) = 0; /** * @brief Do decryption. If length of data is not multiple of block size, PKCS#5 padding is done @@ -296,7 +290,7 @@ namespace gourou * @param dataOut Encrypted data * @param dataOutLength Length of encrypted data */ - virtual void Decrypt(CRYPTO_ALGO algo, CHAINING_MODE chaining, + virtual void decrypt(CRYPTO_ALGO algo, CHAINING_MODE chaining, const unsigned char* key, unsigned int keyLength, const unsigned char* iv, unsigned int ivLength, const unsigned char* dataIn, unsigned int dataInLength, @@ -313,7 +307,7 @@ namespace gourou * * @return AES handler */ - virtual void* DecryptInit(CRYPTO_ALGO algo, CHAINING_MODE chaining, + virtual void* decryptInit(CRYPTO_ALGO algo, CHAINING_MODE chaining, const unsigned char* key, unsigned int keyLength, const unsigned char* iv=0, unsigned int ivLength=0) = 0; @@ -326,7 +320,7 @@ namespace gourou * @param dataOut Decrypted data * @param dataOutLength Length of decrypted data */ - virtual void DecryptUpdate(void* handler, const unsigned char* dataIn, unsigned int dataInLength, + virtual void decryptUpdate(void* handler, const unsigned char* dataIn, unsigned int dataInLength, unsigned char* dataOut, unsigned int* dataOutLength) = 0; /** * @brief Finalize decryption (decrypt last block and remove padding if it is set). @@ -336,7 +330,7 @@ namespace gourou * @param dataOut Last block decrypted data * @param dataOutLength Length of decrypted data */ - virtual void DecryptFinalize(void* handler, unsigned char* dataOut, unsigned int* dataOutLength) = 0; + virtual void decryptFinalize(void* handler, unsigned char* dataOut, unsigned int* dataOutLength) = 0; }; diff --git a/include/libgourou_common.h b/include/libgourou_common.h index 4a71aa3..826a1a3 100644 --- a/include/libgourou_common.h +++ b/include/libgourou_common.h @@ -117,6 +117,8 @@ namespace gourou CLIENT_INVALID_PKCS8, CLIENT_FILE_ERROR, CLIENT_OSSL_ERROR, + CLIENT_CRYPT_ERROR, + CLIENT_DIGEST_ERROR, }; enum DRM_REMOVAL_ERROR { diff --git a/src/libgourou.cpp b/src/libgourou.cpp index d059e93..e828910 100644 --- a/src/libgourou.cpp +++ b/src/libgourou.cpp @@ -869,7 +869,7 @@ namespace gourou // Generate IV in front client->randBytes(encrypted_data, 16); - client->Encrypt(CryptoInterface::ALGO_AES, CryptoInterface::CHAIN_CBC, + client->encrypt(CryptoInterface::ALGO_AES, CryptoInterface::CHAIN_CBC, deviceKey, 16, encrypted_data, 16, data, len, encrypted_data+16, &outLen); @@ -888,7 +888,7 @@ namespace gourou const unsigned char* deviceKey = device->getDeviceKey(); unsigned char* decrypted_data = new unsigned char[len-16]; - client->Decrypt(CryptoInterface::ALGO_AES, CryptoInterface::CHAIN_CBC, + client->decrypt(CryptoInterface::ALGO_AES, CryptoInterface::CHAIN_CBC, deviceKey, 16, data, 16, data+16, len-16, decrypted_data, &outLen); @@ -1009,7 +1009,7 @@ namespace gourou unsigned char* clearRSAKey = new unsigned char[arrayEncryptedKey.size()]; - client->Decrypt(CryptoInterface::ALGO_AES, CryptoInterface::CHAIN_CBC, + client->decrypt(CryptoInterface::ALGO_AES, CryptoInterface::CHAIN_CBC, (const unsigned char*)key, (unsigned int)sizeof(key), (const unsigned char*)iv, (unsigned int)sizeof(iv), (const unsigned char*)arrayEncryptedKey.data(), arrayEncryptedKey.size(), @@ -1109,7 +1109,7 @@ namespace gourou gourou::ByteArray inflateData(true); unsigned int dataOutLength; - client->Decrypt(CryptoInterface::ALGO_AES, CryptoInterface::CHAIN_CBC, + client->decrypt(CryptoInterface::ALGO_AES, CryptoInterface::CHAIN_CBC, decryptedKey+sizeof(decryptedKey)-16, 16, /* Key */ _data, 16, /* IV */ &_data[16], zipData.length()-16, @@ -1313,7 +1313,7 @@ namespace gourou GOUROU_LOG(DEBUG, "Decrypt string " << dictIt->first << " " << dataLength); - client->Decrypt(CryptoInterface::ALGO_RC4, CryptoInterface::CHAIN_ECB, + client->decrypt(CryptoInterface::ALGO_RC4, CryptoInterface::CHAIN_ECB, tmpKey, 16, /* Key */ NULL, 0, /* IV */ encryptedData, dataLength, @@ -1346,7 +1346,7 @@ namespace gourou GOUROU_LOG(DEBUG, "Decrypt stream id " << object->objectId() << ", size " << stream->dataLength()); - client->Decrypt(CryptoInterface::ALGO_RC4, CryptoInterface::CHAIN_ECB, + client->decrypt(CryptoInterface::ALGO_RC4, CryptoInterface::CHAIN_ECB, tmpKey, 16, /* Key */ NULL, 0, /* IV */ encryptedData, dataLength, diff --git a/utils/drmprocessorclientimpl.cpp b/utils/drmprocessorclientimpl.cpp index 9f17171..d451834 100644 --- a/utils/drmprocessorclientimpl.cpp +++ b/utils/drmprocessorclientimpl.cpp @@ -82,32 +82,32 @@ void* DRMProcessorClientImpl::createDigest(const std::string& digestName) if (EVP_DigestInit(md_ctx, md) != 1) { EVP_MD_CTX_free(md_ctx); - return 0; + EXCEPTION(gourou::CLIENT_DIGEST_ERROR, ERR_error_string(ERR_get_error(), NULL)); } return md_ctx; } -int DRMProcessorClientImpl::digestUpdate(void* handler, unsigned char* data, unsigned int length) +void DRMProcessorClientImpl::digestUpdate(void* handler, unsigned char* data, unsigned int length) { - return (EVP_DigestUpdate((EVP_MD_CTX *)handler, data, length)) ? 0 : -1; + if (EVP_DigestUpdate((EVP_MD_CTX *)handler, data, length) != 1) + EXCEPTION(gourou::CLIENT_DIGEST_ERROR, ERR_error_string(ERR_get_error(), NULL)); } -int DRMProcessorClientImpl::digestFinalize(void* handler, unsigned char* digestOut) +void DRMProcessorClientImpl::digestFinalize(void* handler, unsigned char* digestOut) { int res = EVP_DigestFinal((EVP_MD_CTX *)handler, digestOut, NULL); EVP_MD_CTX_free((EVP_MD_CTX *)handler); - return (res == 1) ? 0 : -1; + + if (res <= 0) + EXCEPTION(gourou::CLIENT_DIGEST_ERROR, ERR_error_string(ERR_get_error(), NULL)); } -int DRMProcessorClientImpl::digest(const std::string& digestName, unsigned char* data, unsigned int length, unsigned char* digestOut) +void DRMProcessorClientImpl::digest(const std::string& digestName, unsigned char* data, unsigned int length, unsigned char* digestOut) { void* handler = createDigest(digestName); - if (!handler) - return -1; - if (digestUpdate(handler, data, length)) - return -1; - return digestFinalize(handler, digestOut); + digestUpdate(handler, data, length); + digestFinalize(handler, digestOut); } /* Random interface */ @@ -506,24 +506,27 @@ void DRMProcessorClientImpl::extractCertificate(const unsigned char* RSAKey, uns } /* Crypto interface */ -void DRMProcessorClientImpl::Encrypt(CRYPTO_ALGO algo, CHAINING_MODE chaining, +void DRMProcessorClientImpl::encrypt(CRYPTO_ALGO algo, CHAINING_MODE chaining, const unsigned char* key, unsigned int keyLength, const unsigned char* iv, unsigned int ivLength, const unsigned char* dataIn, unsigned int dataInLength, unsigned char* dataOut, unsigned int* dataOutLength) { - void* handler = EncryptInit(algo, chaining, key, keyLength, iv, ivLength); - EncryptUpdate(handler, dataIn, dataInLength, dataOut, dataOutLength); - EncryptFinalize(handler, dataOut+*dataOutLength, dataOutLength); + void* handler = encryptInit(algo, chaining, key, keyLength, iv, ivLength); + encryptUpdate(handler, dataIn, dataInLength, dataOut, dataOutLength); + encryptFinalize(handler, dataOut+*dataOutLength, dataOutLength); } -void* DRMProcessorClientImpl::EncryptInit(CRYPTO_ALGO algo, CHAINING_MODE chaining, +void* DRMProcessorClientImpl::encryptInit(CRYPTO_ALGO algo, CHAINING_MODE chaining, const unsigned char* key, unsigned int keyLength, const unsigned char* iv, unsigned int ivLength) { EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new(); - - if (algo == ALGO_AES) + int ret = 0; + + switch (algo) + { + case ALGO_AES: { switch(keyLength) { @@ -531,10 +534,10 @@ void* DRMProcessorClientImpl::EncryptInit(CRYPTO_ALGO algo, CHAINING_MODE chaini switch(chaining) { case CHAIN_ECB: - EVP_EncryptInit(ctx, EVP_aes_128_ecb(), key, iv); + ret = EVP_EncryptInit(ctx, EVP_aes_128_ecb(), key, iv); break; case CHAIN_CBC: - EVP_EncryptInit(ctx, EVP_aes_128_cbc(), key, iv); + ret = EVP_EncryptInit(ctx, EVP_aes_128_cbc(), key, iv); break; default: EXCEPTION(gourou::CLIENT_BAD_CHAINING, "Unknown chaining mode " << chaining); @@ -544,98 +547,134 @@ void* DRMProcessorClientImpl::EncryptInit(CRYPTO_ALGO algo, CHAINING_MODE chaini EVP_CIPHER_CTX_free(ctx); EXCEPTION(gourou::CLIENT_BAD_KEY_SIZE, "Invalid key size " << keyLength); } + break; } - else if (algo == ALGO_RC4) + case ALGO_RC4: { if (keyLength != 16) { EVP_CIPHER_CTX_free(ctx); EXCEPTION(gourou::CLIENT_BAD_KEY_SIZE, "Invalid key size " << keyLength); } - EVP_DecryptInit(ctx, EVP_rc4(), key, iv); + ret = EVP_DecryptInit(ctx, EVP_rc4(), key, iv); + break; } - return ctx; -} - -void* DRMProcessorClientImpl::DecryptInit(CRYPTO_ALGO algo, CHAINING_MODE chaining, - const unsigned char* key, unsigned int keyLength, - const unsigned char* iv, unsigned int ivLength) -{ - EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new(); - - if (algo == ALGO_AES) - { - switch(keyLength) - { - case 16: - switch(chaining) - { - case CHAIN_ECB: - EVP_DecryptInit(ctx, EVP_aes_128_ecb(), key, iv); - break; - case CHAIN_CBC: - EVP_DecryptInit(ctx, EVP_aes_128_cbc(), key, iv); - break; - default: - EXCEPTION(gourou::CLIENT_BAD_CHAINING, "Unknown chaining mode " << chaining); - } - break; - default: - EVP_CIPHER_CTX_free(ctx); - EXCEPTION(gourou::CLIENT_BAD_KEY_SIZE, "Invalid key size " << keyLength); - } } - else if (algo == ALGO_RC4) + + if (ret <= 0) { - if (keyLength != 16) - { - EVP_CIPHER_CTX_free(ctx); - EXCEPTION(gourou::CLIENT_BAD_KEY_SIZE, "Invalid key size " << keyLength); - } - EVP_DecryptInit(ctx, EVP_rc4(), key, iv); + EVP_CIPHER_CTX_free(ctx); + EXCEPTION(gourou::CLIENT_CRYPT_ERROR, ERR_error_string(ERR_get_error(), NULL)); } return ctx; } -void DRMProcessorClientImpl::EncryptUpdate(void* handler, const unsigned char* dataIn, unsigned int dataInLength, +void* DRMProcessorClientImpl::decryptInit(CRYPTO_ALGO algo, CHAINING_MODE chaining, + const unsigned char* key, unsigned int keyLength, + const unsigned char* iv, unsigned int ivLength) +{ + EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new(); + int ret = 0; + + switch(algo) + { + case ALGO_AES: + { + switch(keyLength) + { + case 16: + switch(chaining) + { + case CHAIN_ECB: + ret = EVP_DecryptInit(ctx, EVP_aes_128_ecb(), key, iv); + break; + case CHAIN_CBC: + ret = EVP_DecryptInit(ctx, EVP_aes_128_cbc(), key, iv); + break; + default: + EXCEPTION(gourou::CLIENT_BAD_CHAINING, "Unknown chaining mode " << chaining); + } + break; + default: + EVP_CIPHER_CTX_free(ctx); + EXCEPTION(gourou::CLIENT_BAD_KEY_SIZE, "Invalid key size " << keyLength); + } + break; + } + case ALGO_RC4: + { + if (keyLength != 16) + { + EVP_CIPHER_CTX_free(ctx); + EXCEPTION(gourou::CLIENT_BAD_KEY_SIZE, "Invalid key size " << keyLength); + } + ret = EVP_DecryptInit(ctx, EVP_rc4(), key, iv); + break; + } + } + + if (ret <= 0) + { + EVP_CIPHER_CTX_free(ctx); + EXCEPTION(gourou::CLIENT_CRYPT_ERROR, ERR_error_string(ERR_get_error(), NULL)); + } + + return ctx; +} + +void DRMProcessorClientImpl::encryptUpdate(void* handler, const unsigned char* dataIn, unsigned int dataInLength, unsigned char* dataOut, unsigned int* dataOutLength) { - EVP_EncryptUpdate((EVP_CIPHER_CTX*)handler, dataOut, (int*)dataOutLength, dataIn, dataInLength); + int ret = EVP_EncryptUpdate((EVP_CIPHER_CTX*)handler, dataOut, (int*)dataOutLength, dataIn, dataInLength); + + if (ret <= 0) + EXCEPTION(gourou::CLIENT_CRYPT_ERROR, ERR_error_string(ERR_get_error(), NULL)); } -void DRMProcessorClientImpl::EncryptFinalize(void* handler, +void DRMProcessorClientImpl::encryptFinalize(void* handler, unsigned char* dataOut, unsigned int* dataOutLength) { - int len; - EVP_EncryptFinal_ex((EVP_CIPHER_CTX*)handler, dataOut, &len); + int len, ret; + + ret = EVP_EncryptFinal_ex((EVP_CIPHER_CTX*)handler, dataOut, &len); *dataOutLength += len; EVP_CIPHER_CTX_free((EVP_CIPHER_CTX*)handler); + + if (ret <= 0) + EXCEPTION(gourou::CLIENT_CRYPT_ERROR, ERR_error_string(ERR_get_error(), NULL)); } -void DRMProcessorClientImpl::Decrypt(CRYPTO_ALGO algo, CHAINING_MODE chaining, +void DRMProcessorClientImpl::decrypt(CRYPTO_ALGO algo, CHAINING_MODE chaining, const unsigned char* key, unsigned int keyLength, const unsigned char* iv, unsigned int ivLength, const unsigned char* dataIn, unsigned int dataInLength, unsigned char* dataOut, unsigned int* dataOutLength) { - void* handler = DecryptInit(algo, chaining, key, keyLength, iv, ivLength); - DecryptUpdate(handler, dataIn, dataInLength, dataOut, dataOutLength); - DecryptFinalize(handler, dataOut+*dataOutLength, dataOutLength); + void* handler = decryptInit(algo, chaining, key, keyLength, iv, ivLength); + decryptUpdate(handler, dataIn, dataInLength, dataOut, dataOutLength); + decryptFinalize(handler, dataOut+*dataOutLength, dataOutLength); } -void DRMProcessorClientImpl::DecryptUpdate(void* handler, const unsigned char* dataIn, unsigned int dataInLength, +void DRMProcessorClientImpl::decryptUpdate(void* handler, const unsigned char* dataIn, unsigned int dataInLength, unsigned char* dataOut, unsigned int* dataOutLength) { - EVP_DecryptUpdate((EVP_CIPHER_CTX*)handler, dataOut, (int*)dataOutLength, dataIn, dataInLength); + int ret = EVP_DecryptUpdate((EVP_CIPHER_CTX*)handler, dataOut, (int*)dataOutLength, dataIn, dataInLength); + + if (ret <= 0) + EXCEPTION(gourou::CLIENT_CRYPT_ERROR, ERR_error_string(ERR_get_error(), NULL)); } -void DRMProcessorClientImpl::DecryptFinalize(void* handler, unsigned char* dataOut, unsigned int* dataOutLength) +void DRMProcessorClientImpl::decryptFinalize(void* handler, unsigned char* dataOut, unsigned int* dataOutLength) { - int len; - EVP_DecryptFinal_ex((EVP_CIPHER_CTX*)handler, dataOut, &len); + int len, ret; + + ret = EVP_DecryptFinal_ex((EVP_CIPHER_CTX*)handler, dataOut, &len); *dataOutLength += len; EVP_CIPHER_CTX_free((EVP_CIPHER_CTX*)handler); + + if (ret <= 0) + EXCEPTION(gourou::CLIENT_CRYPT_ERROR, ERR_error_string(ERR_get_error(), NULL)); } void* DRMProcessorClientImpl::zipOpen(const std::string& path) diff --git a/utils/drmprocessorclientimpl.h b/utils/drmprocessorclientimpl.h index b878e8c..c2198c6 100644 --- a/utils/drmprocessorclientimpl.h +++ b/utils/drmprocessorclientimpl.h @@ -45,9 +45,9 @@ 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); + virtual void digestUpdate(void* handler, unsigned char* data, unsigned int length); + virtual void digestFinalize(void* handler,unsigned char* digestOut); + virtual void digest(const std::string& digestName, unsigned char* data, unsigned int length, unsigned char* digestOut); /* Random interface */ virtual void randBytes(unsigned char* bytesOut, unsigned int length); @@ -80,34 +80,34 @@ public: unsigned char** certOut, unsigned int* certOutLength); /* Crypto interface */ - virtual void Encrypt(CRYPTO_ALGO algo, CHAINING_MODE chaining, + virtual void encrypt(CRYPTO_ALGO algo, CHAINING_MODE chaining, const unsigned char* key, unsigned int keyLength, const unsigned char* iv, unsigned int ivLength, const unsigned char* dataIn, unsigned int dataInLength, unsigned char* dataOut, unsigned int* dataOutLength); - virtual void* EncryptInit(CRYPTO_ALGO algo, CHAINING_MODE chaining, + virtual void* encryptInit(CRYPTO_ALGO algo, CHAINING_MODE chaining, const unsigned char* key, unsigned int keyLength, const unsigned char* iv=0, unsigned int ivLength=0); - virtual void EncryptUpdate(void* handler, const unsigned char* dataIn, unsigned int dataInLength, + virtual void encryptUpdate(void* handler, const unsigned char* dataIn, unsigned int dataInLength, unsigned char* dataOut, unsigned int* dataOutLength); - virtual void EncryptFinalize(void* handler, unsigned char* dataOut, unsigned int* dataOutLength); + virtual void encryptFinalize(void* handler, unsigned char* dataOut, unsigned int* dataOutLength); - virtual void Decrypt(CRYPTO_ALGO algo, CHAINING_MODE chaining, + virtual void decrypt(CRYPTO_ALGO algo, CHAINING_MODE chaining, const unsigned char* key, unsigned int keyLength, const unsigned char* iv, unsigned int ivLength, const unsigned char* dataIn, unsigned int dataInLength, unsigned char* dataOut, unsigned int* dataOutLength); - virtual void* DecryptInit(CRYPTO_ALGO algo, CHAINING_MODE chaining, + virtual void* decryptInit(CRYPTO_ALGO algo, CHAINING_MODE chaining, const unsigned char* key, unsigned int keyLength, const unsigned char* iv=0, unsigned int ivLength=0); - virtual void DecryptUpdate(void* handler, const unsigned char* dataIn, unsigned int dataInLength, + virtual void decryptUpdate(void* handler, const unsigned char* dataIn, unsigned int dataInLength, unsigned char* dataOut, unsigned int* dataOutLength); - virtual void DecryptFinalize(void* handler, unsigned char* dataOut, unsigned int* dataOutLength); + virtual void decryptFinalize(void* handler, unsigned char* dataOut, unsigned int* dataOutLength); /* ZIP Interface */ virtual void* zipOpen(const std::string& path); From 2dbd4cc343529568c3cf05d3f3c9f54501e56435 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9gory=20Soutad=C3=A9?= Date: Mon, 29 Aug 2022 12:35:09 +0200 Subject: [PATCH 29/94] Update version --- include/libgourou.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/libgourou.h b/include/libgourou.h index 60f7fe6..14f3437 100644 --- a/include/libgourou.h +++ b/include/libgourou.h @@ -40,7 +40,7 @@ #define ACS_SERVER "http://adeactivate.adobe.com/adept" #endif -#define LIBGOUROU_VERSION "0.7.3" +#define LIBGOUROU_VERSION "0.8" namespace gourou { From 6e3958f09e6eee9128c60e54ab81f2e834cd6ff8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9gory=20Soutad=C3=A9?= Date: Sun, 4 Sep 2022 09:25:06 +0200 Subject: [PATCH 30/94] Compute over encrypted key also for PDF files --- include/libgourou.h | 2 +- src/libgourou.cpp | 40 +++++++++++++++++++++++++++++++++------- 2 files changed, 34 insertions(+), 8 deletions(-) diff --git a/include/libgourou.h b/include/libgourou.h index 14f3437..4d7fbfd 100644 --- a/include/libgourou.h +++ b/include/libgourou.h @@ -231,7 +231,7 @@ namespace gourou void buildSignInRequest(pugi::xml_document& signInRequest, const std::string& adobeID, const std::string& adobePassword, const std::string& authenticationCertificate); void fetchLicenseServiceCertificate(const std::string& licenseURL, const std::string& operatorURL); - std::string encryptedKeyFirstPass(pugi::xml_document& rightsDoc, const std::string& encryptedKey, const std::string& keyType); + std::string encryptedKeyFirstPass(pugi::xml_document& rightsDoc, const std::string& encryptedKey, const std::string& keyType, ITEM_TYPE type); void decryptADEPTKey(const std::string& encryptedKey, unsigned char* decryptedKey); void removeEPubDRM(const std::string& filenameIn, const std::string& filenameOut, const unsigned char* encryptionKey, unsigned encryptionKeySize); void generatePDFObjectKey(int version, diff --git a/src/libgourou.cpp b/src/libgourou.cpp index e828910..6084729 100644 --- a/src/libgourou.cpp +++ b/src/libgourou.cpp @@ -963,20 +963,33 @@ namespace gourou /** * RSA Key can be over encrypted with AES128-CBC if keyType attribute is set - * Key = SHA256(keyType)[14:22] || SHA256(keyType)[7:13] + * For EPUB, Key = SHA256(keyType)[14:22] || SHA256(keyType)[7:13] + * For PDF, Key = SHA256(keyType)[6:19] || SHA256(keyType)[3:6] * IV = DeviceID ^ FulfillmentId ^ VoucherId * * @return Base64 encoded decrypted key */ - std::string DRMProcessor::encryptedKeyFirstPass(pugi::xml_document& rightsDoc, const std::string& encryptedKey, const std::string& keyType) + std::string DRMProcessor::encryptedKeyFirstPass(pugi::xml_document& rightsDoc, const std::string& encryptedKey, const std::string& keyType, ITEM_TYPE type) { unsigned char digest[32], key[16], iv[16]; unsigned int dataOutLength; std::string id; client->digest("SHA256", (unsigned char*)keyType.c_str(), keyType.size(), digest); - memcpy(key, &digest[14], 9); - memcpy(&key[9], &digest[7], 7); + + dumpBuffer(gourou::LG_LOG_DEBUG, "SHA of KeyType : ", digest, sizeof(digest)); + + switch(type) + { + case EPUB: + memcpy(key, &digest[14], 9); + memcpy(&key[9], &digest[7], 7); + break; + case PDF: + memcpy(key, &digest[6], 13); + memcpy(&key[13], &digest[3], 3); + break; + } id = extractTextElem(rightsDoc, "/adept:rights/licenseToken/device"); if (id == "") @@ -1054,7 +1067,7 @@ namespace gourou std::string keyType = extractTextAttribute(rightsDoc, "/adept:rights/licenseToken/encryptedKey", "keyType", false); if (keyType != "") - encryptedKey = encryptedKeyFirstPass(rightsDoc, encryptedKey, keyType); + encryptedKey = encryptedKeyFirstPass(rightsDoc, encryptedKey, keyType, EPUB); decryptADEPTKey(encryptedKey, decryptedKey); @@ -1245,7 +1258,20 @@ namespace gourou std::string encryptedKey = extractTextElem(rightsDoc, "/adept:rights/licenseToken/encryptedKey"); if (!encryptionKey) + { + std::string keyType = extractTextAttribute(rightsDoc, "/adept:rights/licenseToken/encryptedKey", "keyType", false); + + if (keyType != "") + encryptedKey = encryptedKeyFirstPass(rightsDoc, encryptedKey, keyType, PDF); + decryptADEPTKey(encryptedKey, decryptedKey); + + dumpBuffer(gourou::LG_LOG_DEBUG, "Decrypted : ", decryptedKey, RSA_KEY_SIZE); + + if (decryptedKey[0] != 0x00 || decryptedKey[1] != 0x02 || + decryptedKey[RSA_KEY_SIZE-16-1] != 0x00) + EXCEPTION(DRM_ERR_ENCRYPTION_KEY, "Unable to retrieve encryption key"); + } else { GOUROU_LOG(DEBUG, "Use provided encryption key"); @@ -1314,7 +1340,7 @@ namespace gourou GOUROU_LOG(DEBUG, "Decrypt string " << dictIt->first << " " << dataLength); client->decrypt(CryptoInterface::ALGO_RC4, CryptoInterface::CHAIN_ECB, - tmpKey, 16, /* Key */ + tmpKey, sizeof(tmpKey), /* Key */ NULL, 0, /* IV */ encryptedData, dataLength, clearData, &dataOutLength); @@ -1347,7 +1373,7 @@ namespace gourou GOUROU_LOG(DEBUG, "Decrypt stream id " << object->objectId() << ", size " << stream->dataLength()); client->decrypt(CryptoInterface::ALGO_RC4, CryptoInterface::CHAIN_ECB, - tmpKey, 16, /* Key */ + tmpKey, sizeof(tmpKey), /* Key */ NULL, 0, /* IV */ encryptedData, dataLength, clearData, &dataOutLength); From 7878f91cdda79b4860ed6564fbe5086740abb7c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9gory=20Soutad=C3=A9?= Date: Mon, 21 Nov 2022 17:56:29 +0100 Subject: [PATCH 31/94] Add support for MacOS and old compilers (not supporting C++11). Main patch is from Samuel Marks. --- README.md | 1 - include/Base64.h | 12 +++++++-- include/libgourou_common.h | 14 ++++++++-- src/device.cpp | 53 +++++++++++++++++++++++++++++++++++--- src/fulfillment_item.cpp | 5 ++++ 5 files changed, 77 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 0cffdd7..d09f731 100644 --- a/README.md +++ b/README.md @@ -42,7 +42,6 @@ _externals_ : _internals_ : * PugiXML - * Base64 * uPDFParser For utils : diff --git a/include/Base64.h b/include/Base64.h index cdfdc04..e5b4930 100644 --- a/include/Base64.h +++ b/include/Base64.h @@ -33,7 +33,11 @@ class Base64 { public: static std::string Encode(const std::string data) { - static constexpr char sEncodingTable[] = { + static +#if __STDC_VERSION__ >= 201112L + constexpr +#endif /* __STDC_VERSION__ >= 201112L */ + char sEncodingTable[] = { 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', @@ -73,7 +77,11 @@ class Base64 { } static std::string Decode(const std::string& input, std::string& out) { - static constexpr unsigned char kDecodingTable[] = { + static +#if __STDC_VERSION__ >= 201112L + constexpr +#endif /* __STDC_VERSION__ >= 201112L */ + unsigned char kDecodingTable[] = { 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 62, 64, 64, 64, 63, diff --git a/include/libgourou_common.h b/include/libgourou_common.h index 826a1a3..22bbd2d 100644 --- a/include/libgourou_common.h +++ b/include/libgourou_common.h @@ -131,7 +131,17 @@ namespace gourou DRM_INVALID_KEY_SIZE, DRM_ERR_ENCRYPTION_KEY_FP }; - + + #ifndef _NOEXCEPT + #if __STDC_VERSION__ >= 201112L + # define _NOEXCEPT noexcept + # define _NOEXCEPT_(x) noexcept(x) + #else + # define _NOEXCEPT throw() + # define _NOEXCEPT_(x) + #endif + #endif /* !_NOEXCEPT */ + /** * Generic exception class */ @@ -157,7 +167,7 @@ namespace gourou this->fullmessage = strdup(other.fullmessage); } - ~Exception() + ~Exception() _NOEXCEPT { free(fullmessage); } diff --git a/src/device.cpp b/src/device.cpp index 481a99b..1418d47 100644 --- a/src/device.cpp +++ b/src/device.cpp @@ -29,13 +29,23 @@ #include #include -// From https://stackoverflow.com/questions/1779715/how-to-get-mac-address-of-your-machine-using-a-c-program/35242525 +#include +#if defined(__linux__) || defined(linux) || defined(__linux) #include -#include +#include #include #include -#include +#elif (defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__) \ + || defined(__bsdi__) || defined(__DragonFly__) || defined(__APPLE__)) +#include +#include +#include +#define BSD_HEADERS 1 +#endif + +#if defined(__linux__) || defined(linux) || defined(__linux) +// From https://stackoverflow.com/questions/1779715/how-to-get-mac-address-of-your-machine-using-a-c-program/35242525 int get_mac_address(unsigned char* mac_address) { struct ifreq ifr; @@ -74,6 +84,43 @@ int get_mac_address(unsigned char* mac_address) return 1; } +#elif BSD_HEADERS +// https://stackoverflow.com/a/3978293 +int get_mac_address(unsigned char* mac_address, const char* if_name = "en0") +{ + ifaddrs* iflist; + int found = 0; + if (getifaddrs(&iflist) == 0) { + for (ifaddrs* cur = iflist; cur; cur = cur->ifa_next) { + if ((cur->ifa_addr->sa_family == AF_LINK) && + (strcmp(cur->ifa_name, if_name) == 0) && + cur->ifa_addr) { + sockaddr_dl* sdl = (sockaddr_dl*)cur->ifa_addr; + memcpy(mac_address, LLADDR(sdl), sdl->sdl_alen); + found = 1; + break; + } + } + + freeifaddrs(iflist); + } + return found; +} +#else +int get_mac_address(unsigned char* mac_address) +{ + GOUROU_LOG(INFO, "get_mac_address() not implemented for your platform, using a static address"); + + mac_address[0] = 0x8D; + mac_address[1] = 0x70; + mac_address[2] = 0x13; + mac_address[3] = 0x8D; + mac_address[4] = 0x43; + mac_address[5] = 0x27; + + return 1; +} +#endif /* defined(__linux__) || defined(linux) || defined(__linux) */ namespace gourou diff --git a/src/fulfillment_item.cpp b/src/fulfillment_item.cpp index 924c63f..dbd8d31 100644 --- a/src/fulfillment_item.cpp +++ b/src/fulfillment_item.cpp @@ -17,6 +17,7 @@ along with libgourou. If not, see . */ +#include #include #include #include "user.h" @@ -93,8 +94,12 @@ namespace gourou std::string FulfillmentItem::getMetadata(std::string name) { // https://stackoverflow.com/questions/313970/how-to-convert-an-instance-of-stdstring-to-lower-case + #if __STDC_VERSION__ >= 201112L std::transform(name.begin(), name.end(), name.begin(), [](unsigned char c){ return std::tolower(c); }); + #else + std::transform(name.begin(), name.end(), name.begin(), tolower); + #endif name = std::string("dc:") + name; pugi::xpath_node path = metadatas.select_node(name.c_str()); From afab1c001258d32a268f2dfdbda976e403f42fdf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9gory=20Soutad=C3=A9?= Date: Wed, 21 Dec 2022 20:15:11 +0100 Subject: [PATCH 32/94] Fix over encrypted RSA key decryption algorithm --- include/libgourou.h | 2 +- src/libgourou.cpp | 28 +++++++++++----------------- 2 files changed, 12 insertions(+), 18 deletions(-) diff --git a/include/libgourou.h b/include/libgourou.h index 4d7fbfd..14f3437 100644 --- a/include/libgourou.h +++ b/include/libgourou.h @@ -231,7 +231,7 @@ namespace gourou void buildSignInRequest(pugi::xml_document& signInRequest, const std::string& adobeID, const std::string& adobePassword, const std::string& authenticationCertificate); void fetchLicenseServiceCertificate(const std::string& licenseURL, const std::string& operatorURL); - std::string encryptedKeyFirstPass(pugi::xml_document& rightsDoc, const std::string& encryptedKey, const std::string& keyType, ITEM_TYPE type); + std::string encryptedKeyFirstPass(pugi::xml_document& rightsDoc, const std::string& encryptedKey, const std::string& keyType); void decryptADEPTKey(const std::string& encryptedKey, unsigned char* decryptedKey); void removeEPubDRM(const std::string& filenameIn, const std::string& filenameOut, const unsigned char* encryptionKey, unsigned encryptionKeySize); void generatePDFObjectKey(int version, diff --git a/src/libgourou.cpp b/src/libgourou.cpp index 6084729..c69d7b7 100644 --- a/src/libgourou.cpp +++ b/src/libgourou.cpp @@ -963,13 +963,13 @@ namespace gourou /** * RSA Key can be over encrypted with AES128-CBC if keyType attribute is set - * For EPUB, Key = SHA256(keyType)[14:22] || SHA256(keyType)[7:13] - * For PDF, Key = SHA256(keyType)[6:19] || SHA256(keyType)[3:6] + * remainder = keyType % 16 + * Key = SHA256(keyType)[remainder*2:remainder*2+(16-remainder)] || SHA256(keyType)[16-remainder:16] * IV = DeviceID ^ FulfillmentId ^ VoucherId * * @return Base64 encoded decrypted key */ - std::string DRMProcessor::encryptedKeyFirstPass(pugi::xml_document& rightsDoc, const std::string& encryptedKey, const std::string& keyType, ITEM_TYPE type) + std::string DRMProcessor::encryptedKeyFirstPass(pugi::xml_document& rightsDoc, const std::string& encryptedKey, const std::string& keyType) { unsigned char digest[32], key[16], iv[16]; unsigned int dataOutLength; @@ -979,18 +979,12 @@ namespace gourou dumpBuffer(gourou::LG_LOG_DEBUG, "SHA of KeyType : ", digest, sizeof(digest)); - switch(type) - { - case EPUB: - memcpy(key, &digest[14], 9); - memcpy(&key[9], &digest[7], 7); - break; - case PDF: - memcpy(key, &digest[6], 13); - memcpy(&key[13], &digest[3], 3); - break; - } + long nonce = std::stol(keyType); + int remainder = nonce % 16; + memcpy(key, &digest[remainder*2], 16-remainder); + memcpy(&key[16-remainder], &digest[remainder], remainder); + id = extractTextElem(rightsDoc, "/adept:rights/licenseToken/device"); if (id == "") EXCEPTION(DRM_ERR_ENCRYPTION_KEY_FP, "Device id not found in rights.xml"); @@ -1021,7 +1015,7 @@ namespace gourou dumpBuffer(gourou::LG_LOG_DEBUG, "First pass IV : ", iv, sizeof(iv)); unsigned char* clearRSAKey = new unsigned char[arrayEncryptedKey.size()]; - + client->decrypt(CryptoInterface::ALGO_AES, CryptoInterface::CHAIN_CBC, (const unsigned char*)key, (unsigned int)sizeof(key), (const unsigned char*)iv, (unsigned int)sizeof(iv), @@ -1067,7 +1061,7 @@ namespace gourou std::string keyType = extractTextAttribute(rightsDoc, "/adept:rights/licenseToken/encryptedKey", "keyType", false); if (keyType != "") - encryptedKey = encryptedKeyFirstPass(rightsDoc, encryptedKey, keyType, EPUB); + encryptedKey = encryptedKeyFirstPass(rightsDoc, encryptedKey, keyType); decryptADEPTKey(encryptedKey, decryptedKey); @@ -1262,7 +1256,7 @@ namespace gourou std::string keyType = extractTextAttribute(rightsDoc, "/adept:rights/licenseToken/encryptedKey", "keyType", false); if (keyType != "") - encryptedKey = encryptedKeyFirstPass(rightsDoc, encryptedKey, keyType, PDF); + encryptedKey = encryptedKeyFirstPass(rightsDoc, encryptedKey, keyType); decryptADEPTKey(encryptedKey, decryptedKey); From 24bae89095f5ad12dfa516d114ed2058e874a74c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9gory=20Soutad=C3=A9?= Date: Wed, 21 Dec 2022 20:56:26 +0100 Subject: [PATCH 33/94] Factorize decryptADEPTKey() for ePub and PDF --- include/libgourou.h | 2 +- src/libgourou.cpp | 127 ++++++++++++++++++-------------------------- 2 files changed, 54 insertions(+), 75 deletions(-) diff --git a/include/libgourou.h b/include/libgourou.h index 14f3437..e2ee44c 100644 --- a/include/libgourou.h +++ b/include/libgourou.h @@ -232,7 +232,7 @@ namespace gourou void fetchLicenseServiceCertificate(const std::string& licenseURL, const std::string& operatorURL); std::string encryptedKeyFirstPass(pugi::xml_document& rightsDoc, const std::string& encryptedKey, const std::string& keyType); - void decryptADEPTKey(const std::string& encryptedKey, unsigned char* decryptedKey); + void decryptADEPTKey(pugi::xml_document& rightsDoc, unsigned char* decryptedKey, const unsigned char* encryptionKey=0, unsigned encryptionKeySize=0); void removeEPubDRM(const std::string& filenameIn, const std::string& filenameOut, const unsigned char* encryptionKey, unsigned encryptionKeySize); void generatePDFObjectKey(int version, const unsigned char* masterKey, unsigned int masterKeyLength, diff --git a/src/libgourou.cpp b/src/libgourou.cpp index c69d7b7..e6d81c7 100644 --- a/src/libgourou.cpp +++ b/src/libgourou.cpp @@ -944,23 +944,6 @@ namespace gourou int DRMProcessor::getLogLevel() {return (int)gourou::logLevel;} void DRMProcessor::setLogLevel(int logLevel) {gourou::logLevel = (GOUROU_LOG_LEVEL)logLevel;} - void DRMProcessor::decryptADEPTKey(const std::string& encryptedKey, unsigned char* decryptedKey) - { - if (encryptedKey.size() != 172) - EXCEPTION(DRM_INVALID_KEY_SIZE, "Invalid encrypted key size (" << encryptedKey.size() << "). DRM version not supported"); - - ByteArray arrayEncryptedKey = ByteArray::fromBase64(encryptedKey); - - std::string privateKeyData = user->getPrivateLicenseKey(); - ByteArray privateRSAKey = ByteArray::fromBase64(privateKeyData); - - dumpBuffer(gourou::LG_LOG_DEBUG, "To decrypt : ", arrayEncryptedKey.data(), arrayEncryptedKey.length()); - - client->RSAPrivateDecrypt(privateRSAKey.data(), privateRSAKey.length(), - RSAInterface::RSA_KEY_PKCS8, "", - arrayEncryptedKey.data(), arrayEncryptedKey.length(), decryptedKey); - } - /** * RSA Key can be over encrypted with AES128-CBC if keyType attribute is set * remainder = keyType % 16 @@ -1009,11 +992,10 @@ namespace gourou for(unsigned int i=0; idecrypt(CryptoInterface::ALGO_AES, CryptoInterface::CHAIN_CBC, @@ -1041,7 +1023,51 @@ namespace gourou return res.toBase64(); } - + + void DRMProcessor::decryptADEPTKey(pugi::xml_document& rightsDoc, unsigned char* decryptedKey, const unsigned char* encryptionKey, unsigned encryptionKeySize) + { + unsigned char rsaKey[RSA_KEY_SIZE]; + + if (!encryptionKey) + { + std::string encryptedKey = extractTextElem(rightsDoc, "/adept:rights/licenseToken/encryptedKey"); + std::string keyType = extractTextAttribute(rightsDoc, "/adept:rights/licenseToken/encryptedKey", "keyType", false); + + if (keyType != "") + encryptedKey = encryptedKeyFirstPass(rightsDoc, encryptedKey, keyType); + + if (encryptedKey.size() != 172) + EXCEPTION(DRM_INVALID_KEY_SIZE, "Invalid encrypted key size (" << encryptedKey.size() << "). DRM version not supported"); + + ByteArray arrayEncryptedKey = ByteArray::fromBase64(encryptedKey); + + std::string privateKeyData = user->getPrivateLicenseKey(); + ByteArray privateRSAKey = ByteArray::fromBase64(privateKeyData); + + dumpBuffer(gourou::LG_LOG_DEBUG, "To decrypt : ", arrayEncryptedKey.data(), arrayEncryptedKey.length()); + + client->RSAPrivateDecrypt(privateRSAKey.data(), privateRSAKey.length(), + RSAInterface::RSA_KEY_PKCS8, "", + arrayEncryptedKey.data(), arrayEncryptedKey.length(), rsaKey); + + dumpBuffer(gourou::LG_LOG_DEBUG, "Decrypted : ", rsaKey, sizeof(rsaKey)); + + if (rsaKey[0] != 0x00 || rsaKey[1] != 0x02 || + rsaKey[RSA_KEY_SIZE-16-1] != 0x00) + EXCEPTION(DRM_ERR_ENCRYPTION_KEY, "Unable to retrieve encryption key"); + + memcpy(decryptedKey, &rsaKey[sizeof(rsaKey)-16], 16); + } + else + { + GOUROU_LOG(DEBUG, "Use provided encryption key"); + if (encryptionKeySize != 16) + EXCEPTION(DRM_ERR_ENCRYPTION_KEY, "Provided encryption key must be 16 bytes"); + + memcpy(decryptedKey, encryptionKey, encryptionKeySize); + } + } + void DRMProcessor::removeEPubDRM(const std::string& filenameIn, const std::string& filenameOut, const unsigned char* encryptionKey, unsigned encryptionKeySize) { @@ -1053,32 +1079,9 @@ namespace gourou pugi::xml_document rightsDoc; rightsDoc.load_string((const char*)zipData.data()); - std::string encryptedKey = extractTextElem(rightsDoc, "/adept:rights/licenseToken/encryptedKey"); - unsigned char decryptedKey[RSA_KEY_SIZE]; + unsigned char decryptedKey[16]; - if (!encryptionKey) - { - std::string keyType = extractTextAttribute(rightsDoc, "/adept:rights/licenseToken/encryptedKey", "keyType", false); - - if (keyType != "") - encryptedKey = encryptedKeyFirstPass(rightsDoc, encryptedKey, keyType); - - decryptADEPTKey(encryptedKey, decryptedKey); - - dumpBuffer(gourou::LG_LOG_DEBUG, "Decrypted : ", decryptedKey, RSA_KEY_SIZE); - - if (decryptedKey[0] != 0x00 || decryptedKey[1] != 0x02 || - decryptedKey[RSA_KEY_SIZE-16-1] != 0x00) - EXCEPTION(DRM_ERR_ENCRYPTION_KEY, "Unable to retrieve encryption key"); - } - else - { - GOUROU_LOG(DEBUG, "Use provided encryption key"); - if (encryptionKeySize != 16) - EXCEPTION(DRM_ERR_ENCRYPTION_KEY, "Provided encryption key must be 16 bytes"); - - memcpy(&decryptedKey[sizeof(decryptedKey)-16], encryptionKey, encryptionKeySize); - } + decryptADEPTKey(rightsDoc, decryptedKey, encryptionKey, encryptionKeySize); client->zipReadFile(zipHandler, "META-INF/encryption.xml", zipData); pugi::xml_document encryptionDoc; @@ -1117,7 +1120,7 @@ namespace gourou unsigned int dataOutLength; client->decrypt(CryptoInterface::ALGO_AES, CryptoInterface::CHAIN_CBC, - decryptedKey+sizeof(decryptedKey)-16, 16, /* Key */ + decryptedKey, sizeof(decryptedKey), /* Key */ _data, 16, /* IV */ &_data[16], zipData.length()-16, _clearData, &dataOutLength); @@ -1210,7 +1213,7 @@ namespace gourou std::vector objects = parser.objects(); std::vector::iterator it; std::vector::reverse_iterator rIt; - unsigned char decryptedKey[RSA_KEY_SIZE]; + unsigned char decryptedKey[16]; int ebxId; for(rIt = objects.rbegin(); rIt != objects.rend(); rIt++) @@ -1249,31 +1252,7 @@ namespace gourou pugi::xml_document rightsDoc; rightsDoc.load_string((const char*)rightsStr.data()); - std::string encryptedKey = extractTextElem(rightsDoc, "/adept:rights/licenseToken/encryptedKey"); - - if (!encryptionKey) - { - std::string keyType = extractTextAttribute(rightsDoc, "/adept:rights/licenseToken/encryptedKey", "keyType", false); - - if (keyType != "") - encryptedKey = encryptedKeyFirstPass(rightsDoc, encryptedKey, keyType); - - decryptADEPTKey(encryptedKey, decryptedKey); - - dumpBuffer(gourou::LG_LOG_DEBUG, "Decrypted : ", decryptedKey, RSA_KEY_SIZE); - - if (decryptedKey[0] != 0x00 || decryptedKey[1] != 0x02 || - decryptedKey[RSA_KEY_SIZE-16-1] != 0x00) - EXCEPTION(DRM_ERR_ENCRYPTION_KEY, "Unable to retrieve encryption key"); - } - else - { - GOUROU_LOG(DEBUG, "Use provided encryption key"); - if (encryptionKeySize != 16) - EXCEPTION(DRM_ERR_ENCRYPTION_KEY, "Provided encryption key must be 16 bytes"); - - memcpy(&decryptedKey[sizeof(decryptedKey)-16], encryptionKey, encryptionKeySize); - } + decryptADEPTKey(rightsDoc, decryptedKey, encryptionKey, encryptionKeySize); ebxId = ebx->objectId(); @@ -1305,10 +1284,10 @@ namespace gourou GOUROU_LOG(DEBUG, "Obj " << object->objectId()); - unsigned char tmpKey[16]; + unsigned char tmpKey[sizeof(decryptedKey)]; generatePDFObjectKey(ebxVersion->value(), - decryptedKey+sizeof(decryptedKey)-16, 16, + decryptedKey, sizeof(decryptedKey), object->objectId(), object->generationNumber(), tmpKey); From f65e8cd9ebcb78ba232eb6b3cf6b3bd1b72a8c9c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9gory=20Soutad=C3=A9?= Date: Wed, 21 Dec 2022 21:06:03 +0100 Subject: [PATCH 34/94] Check for target user before trying to decrypt a file --- include/libgourou_common.h | 3 ++- src/libgourou.cpp | 9 ++++++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/include/libgourou_common.h b/include/libgourou_common.h index 22bbd2d..2947b14 100644 --- a/include/libgourou_common.h +++ b/include/libgourou_common.h @@ -129,7 +129,8 @@ namespace gourou DRM_IN_OUT_EQUALS, DRM_MISSING_PARAMETER, DRM_INVALID_KEY_SIZE, - DRM_ERR_ENCRYPTION_KEY_FP + DRM_ERR_ENCRYPTION_KEY_FP, + DRM_INVALID_USER }; #ifndef _NOEXCEPT diff --git a/src/libgourou.cpp b/src/libgourou.cpp index e6d81c7..cf2e9c2 100644 --- a/src/libgourou.cpp +++ b/src/libgourou.cpp @@ -1028,6 +1028,13 @@ namespace gourou { unsigned char rsaKey[RSA_KEY_SIZE]; + std::string user = extractTextElem(rightsDoc, "/adept:rights/licenseToken/user"); + + if (this->user->getUUID() != user) + { + EXCEPTION(DRM_INVALID_USER, "This book has been downloaded for another user (" << user << ")"); + } + if (!encryptionKey) { std::string encryptedKey = extractTextElem(rightsDoc, "/adept:rights/licenseToken/encryptedKey"); @@ -1041,7 +1048,7 @@ namespace gourou ByteArray arrayEncryptedKey = ByteArray::fromBase64(encryptedKey); - std::string privateKeyData = user->getPrivateLicenseKey(); + std::string privateKeyData = this->user->getPrivateLicenseKey(); ByteArray privateRSAKey = ByteArray::fromBase64(privateKeyData); dumpBuffer(gourou::LG_LOG_DEBUG, "To decrypt : ", arrayEncryptedKey.data(), arrayEncryptedKey.length()); From e4bd73c03db20e8c6dacb53b51eae2e5244a82f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9gory=20Soutad=C3=A9?= Date: Wed, 21 Dec 2022 21:23:42 +0100 Subject: [PATCH 35/94] Add global option -D to utils, allowing to specify .adept directory instead of every single files. WARNING : -D has been changed by -d in adept_loan_mgt ! --- include/libgourou.h | 2 +- utils/acsmdownloader.cpp | 15 ++++++++++++-- utils/adept_loan_mgt.cpp | 42 ++++++++++++++++++++-------------------- utils/adept_remove.cpp | 15 ++++++++++++-- 4 files changed, 48 insertions(+), 26 deletions(-) diff --git a/include/libgourou.h b/include/libgourou.h index e2ee44c..1f82dd8 100644 --- a/include/libgourou.h +++ b/include/libgourou.h @@ -40,7 +40,7 @@ #define ACS_SERVER "http://adeactivate.adobe.com/adept" #endif -#define LIBGOUROU_VERSION "0.8" +#define LIBGOUROU_VERSION "0.8.1" namespace gourou { diff --git a/utils/acsmdownloader.cpp b/utils/acsmdownloader.cpp index 48ab0f2..1c66086 100644 --- a/utils/acsmdownloader.cpp +++ b/utils/acsmdownloader.cpp @@ -184,8 +184,9 @@ static void usage(const char* cmd) { std::cout << "Download EPUB file from ACSM request file" << std::endl; - std::cout << "Usage: " << basename((char*)cmd) << " [(-d|--device-file) device.xml] [(-a|--activation-file) activation.xml] [(-k|--device-key-file) devicesalt] [(-O|--output-dir) dir] [(-o|--output-file) output(.epub|.pdf|.der)] [(-r|--resume)] [(-v|--verbose)] [(-h|--help)] (-f|--acsm-file) file.acsm|(-e|--export-private-key)" << std::endl << std::endl; + std::cout << "Usage: " << basename((char*)cmd) << " [(-D|--adept-directory) dir] [(-d|--device-file) device.xml] [(-a|--activation-file) activation.xml] [(-k|--device-key-file) devicesalt] [(-O|--output-dir) dir] [(-o|--output-file) output(.epub|.pdf|.der)] [(-r|--resume)] [(-v|--verbose)] [(-h|--help)] (-f|--acsm-file) file.acsm|(-e|--export-private-key)" << std::endl << std::endl; + std::cout << " " << "-D|--adept-directory" << "\t" << ".adept directory that must contains device.xml, activation.xml and devicesalt" << std::endl; std::cout << " " << "-d|--device-file" << "\t" << "device.xml file from eReader" << std::endl; std::cout << " " << "-a|--activation-file" << "\t" << "activation.xml file from eReader" << std::endl; std::cout << " " << "-k|--device-key-file" << "\t" << "private device key file (eg devicesalt/devkey.bin) from eReader" << std::endl; @@ -209,6 +210,7 @@ static void usage(const char* cmd) int main(int argc, char** argv) { int c, ret = -1; + std::string _deviceFile, _activationFile, _devicekeyFile; const char** files[] = {&devicekeyFile, &deviceFile, &activationFile}; int verbose = gourou::DRMProcessor::getLogLevel(); @@ -216,6 +218,7 @@ int main(int argc, char** argv) while (1) { int option_index = 0; static struct option long_options[] = { + {"adept-directory", required_argument, 0, 'D' }, {"device-file", required_argument, 0, 'd' }, {"activation-file", required_argument, 0, 'a' }, {"device-key-file", required_argument, 0, 'k' }, @@ -230,12 +233,20 @@ int main(int argc, char** argv) {0, 0, 0, 0 } }; - c = getopt_long(argc, argv, "d:a:k:O:o:f:ervVh", + c = getopt_long(argc, argv, "D:d:a:k:O:o:f:ervVh", long_options, &option_index); if (c == -1) break; switch (c) { + case 'D': + _deviceFile = std::string(optarg) + "/device.xml"; + _activationFile = std::string(optarg) + "/activation.xml"; + _devicekeyFile = std::string(optarg) + "/devicesalt"; + deviceFile = _deviceFile.c_str(); + activationFile = _activationFile.c_str(); + devicekeyFile = _devicekeyFile.c_str(); + break; case 'd': deviceFile = optarg; break; diff --git a/utils/adept_loan_mgt.cpp b/utils/adept_loan_mgt.cpp index 791ab9e..2bb7693 100644 --- a/utils/adept_loan_mgt.cpp +++ b/utils/adept_loan_mgt.cpp @@ -45,7 +45,7 @@ #define MAX_SIZE_BOOK_NAME 30 -static char* activationDir = 0; +static char* adeptDir = 0; static const char* deviceFile = "device.xml"; static const char* activationFile = "activation.xml"; static const char* devicekeyFile = "devicesalt"; @@ -106,7 +106,7 @@ private: struct Loan* loan; char * res; - std::string loanDir = std::string(activationDir) + std::string("/") + LOANS_DIR; + std::string loanDir = std::string(adeptDir) + std::string("/") + LOANS_DIR; if (!fileExists(loanDir.c_str())) return; @@ -336,18 +336,18 @@ static void usage(const char* cmd) { std::cout << "Manage loaned books" << std::endl; - std::cout << "Usage: " << basename((char*)cmd) << " [(-d|--activation-dir) dir] (-l|--list)|(-D|--delete loanID)|(-R|--delete loanID) [(-v|--verbose)] [(-h|--help)]" << std::endl << std::endl; + std::cout << "Usage: " << basename((char*)cmd) << " [(-D|--adept-directory) dir] (-l|--list)|(-d|--delete loanID)|(-R|--return loanID) [(-v|--verbose)] [(-h|--help)]" << std::endl << std::endl; - std::cout << " " << "-d|--activation-dir" << "\t" << "Directory of device.xml/activation.xml and device key" << std::endl; + std::cout << " " << "-D|--adept-directory" << "\t" << ".adept directory that must contains device.xml, activation.xml and devicesalt" << std::endl; std::cout << " " << "-l|--list" << "\t\t" << "List all loaned books" << std::endl; std::cout << " " << "-r|--return" << "\t\t" << "Return a loaned book" << std::endl; - std::cout << " " << "-D|--delete" << "\t\t" << "Delete a loan entry without returning it" << std::endl; + std::cout << " " << "-d|--delete" << "\t\t" << "Delete a loan entry without returning it" << std::endl; std::cout << " " << "-v|--verbose" << "\t\t" << "Increase verbosity, can be set multiple times" << std::endl; std::cout << " " << "-V|--version" << "\t\t" << "Display libgourou version" << std::endl; std::cout << " " << "-h|--help" << "\t\t" << "This help" << std::endl; std::cout << std::endl; - std::cout << "Activation directory is optional. If not set, it's looked into :" << std::endl; + std::cout << "ADEPT directory is optional. If not set, it's looked into :" << std::endl; std::cout << " * Current directory" << std::endl; std::cout << " * .adept" << std::endl; std::cout << " * adobe-digital-editions directory" << std::endl; @@ -365,10 +365,10 @@ int main(int argc, char** argv) while (1) { int option_index = 0; static struct option long_options[] = { - {"activation-dir", required_argument, 0, 'd' }, + {"adept-directory", required_argument, 0, 'D' }, {"list", no_argument, 0, 'l' }, {"return", no_argument, 0, 'r' }, - {"delete", no_argument, 0, 'D' }, + {"delete", no_argument, 0, 'd' }, {"verbose", no_argument, 0, 'v' }, {"version", no_argument, 0, 'V' }, {"help", no_argument, 0, 'h' }, @@ -381,8 +381,8 @@ int main(int argc, char** argv) break; switch (c) { - case 'd': - activationDir = optarg; + case 'D': + adeptDir = optarg; break; case 'l': list = true; @@ -392,7 +392,7 @@ int main(int argc, char** argv) returnID = optarg; actions++; break; - case 'D': + case 'd': deleteID = optarg; actions++; break; @@ -432,9 +432,9 @@ int main(int argc, char** argv) { orig = *files[i]; - if (activationDir) + if (adeptDir) { - std::string path = std::string(activationDir) + std::string("/") + orig; + std::string path = std::string(adeptDir) + std::string("/") + orig; filename = strdup(path.c_str()); } else @@ -450,17 +450,17 @@ int main(int argc, char** argv) if (hasErrors) { - // In case of activation dir was provided by user - activationDir = 0; + // In case of adept dir was provided by user + adeptDir = 0; goto end; } - if (activationDir) - activationDir = strdup(activationDir); // For below free + if (adeptDir) + adeptDir = strdup(adeptDir); // For below free else { - activationDir = strdup(deviceFile); - activationDir = dirname(activationDir); + adeptDir = strdup(deviceFile); + adeptDir = dirname(adeptDir); } ret = loanMGT.run(); @@ -472,8 +472,8 @@ end: free((void*)*files[i]); } - if (activationDir) - free(activationDir); + if (adeptDir) + free(adeptDir); return ret; } diff --git a/utils/adept_remove.cpp b/utils/adept_remove.cpp index 1a16c6d..6baeb53 100644 --- a/utils/adept_remove.cpp +++ b/utils/adept_remove.cpp @@ -143,8 +143,9 @@ static void usage(const char* cmd) { std::cout << "Remove ADEPT DRM (from Adobe) of EPUB/PDF file" << std::endl; - std::cout << "Usage: " << basename((char*)cmd) << " [(-d|--device-file) device.xml] [(-a|--activation-file) activation.xml] [(-k|--device-key-file) devicesalt] [(-O|--output-dir) dir] [(-o|--output-file) output(.epub|.pdf|.der)] [(-v|--verbose)] [(-h|--help)] (-f|--input-file) file(.epub|pdf)" << std::endl << std::endl; + std::cout << "Usage: " << basename((char*)cmd) << " [(-D|--adept-directory) dir] [(-d|--device-file) device.xml] [(-a|--activation-file) activation.xml] [(-k|--device-key-file) devicesalt] [(-O|--output-dir) dir] [(-o|--output-file) output(.epub|.pdf|.der)] [(-v|--verbose)] [(-h|--help)] (-f|--input-file) file(.epub|pdf)" << std::endl << std::endl; + std::cout << " " << "-D|--adept-directory" << "\t" << ".adept directory that must contains device.xml, activation.xml and devicesalt" << std::endl; std::cout << " " << "-d|--device-file" << "\t" << "device.xml file from eReader" << std::endl; std::cout << " " << "-a|--activation-file" << "\t" << "activation.xml file from eReader" << std::endl; std::cout << " " << "-k|--device-key-file" << "\t" << "private device key file (eg devicesalt/devkey.bin) from eReader" << std::endl; @@ -169,10 +170,12 @@ int main(int argc, char** argv) const char** files[] = {&devicekeyFile, &deviceFile, &activationFile}; int verbose = gourou::DRMProcessor::getLogLevel(); + std::string _deviceFile, _activationFile, _devicekeyFile; while (1) { int option_index = 0; static struct option long_options[] = { + {"adept-directory", required_argument, 0, 'D' }, {"device-file", required_argument, 0, 'd' }, {"activation-file", required_argument, 0, 'a' }, {"device-key-file", required_argument, 0, 'k' }, @@ -186,12 +189,20 @@ int main(int argc, char** argv) {0, 0, 0, 0 } }; - c = getopt_long(argc, argv, "d:a:k:O:o:f:K:vVh", + c = getopt_long(argc, argv, "D:d:a:k:O:o:f:K:vVh", long_options, &option_index); if (c == -1) break; switch (c) { + case 'D': + _deviceFile = std::string(optarg) + "/device.xml"; + _activationFile = std::string(optarg) + "/activation.xml"; + _devicekeyFile = std::string(optarg) + "/devicesalt"; + deviceFile = _deviceFile.c_str(); + activationFile = _activationFile.c_str(); + devicekeyFile = _devicekeyFile.c_str(); + break; case 'd': deviceFile = optarg; break; From c41dd46ca7aa024f057895eda52a9f6b8ea280a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9gory=20Soutad=C3=A9?= Date: Fri, 23 Dec 2022 17:51:51 +0100 Subject: [PATCH 36/94] Check for potential write error (or not buffer fully consumed) --- src/libgourou.cpp | 9 +++++++-- utils/utils_common.cpp | 16 ++++++++++++++-- 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/src/libgourou.cpp b/src/libgourou.cpp index cf2e9c2..7341fe9 100644 --- a/src/libgourou.cpp +++ b/src/libgourou.cpp @@ -931,14 +931,19 @@ namespace gourou void DRMProcessor::exportPrivateLicenseKey(std::string path) { int fd = open(path.c_str(), O_CREAT|O_TRUNC|O_WRONLY, S_IRWXU); + int ret; + if (fd <= 0) EXCEPTION(GOUROU_FILE_ERROR, "Unable to open " << path); ByteArray privateLicenseKey = ByteArray::fromBase64(user->getPrivateLicenseKey()); /* In adobekey.py, we get base64 decoded data [26:] */ - write(fd, privateLicenseKey.data()+26, privateLicenseKey.length()-26); - + ret = write(fd, privateLicenseKey.data()+26, privateLicenseKey.length()-26); close(fd); + if (ret != privateLicenseKey.length()-26) + { + EXCEPTION(gourou::GOUROU_FILE_ERROR, "Error writing " << path); + } } int DRMProcessor::getLogLevel() {return (int)gourou::logLevel;} diff --git a/utils/utils_common.cpp b/utils/utils_common.cpp index c66af71..41c0ac7 100644 --- a/utils/utils_common.cpp +++ b/utils/utils_common.cpp @@ -99,7 +99,7 @@ void mkpath(const char *dir) void fileCopy(const char* in, const char* out) { char buffer[4096]; - int ret, fdIn, fdOut; + int ret, ret2, fdIn, fdOut; fdIn = open(in, O_RDONLY); @@ -119,7 +119,19 @@ void fileCopy(const char* in, const char* out) ret = ::read(fdIn, buffer, sizeof(buffer)); if (ret <= 0) break; - ::write(fdOut, buffer, ret); + do + { + ret2 = ::write(fdOut, buffer, ret); + if (ret2 >= 0) + { + ret -= ret2; + buffer += ret2; + } + else + { + EXCEPTION(gourou::CLIENT_FILE_ERROR, "Error writing " << out); + } + } while (ret); } close (fdIn); From ffd2004cbb42f9e0cf244071ce9731ef2fe48a9f Mon Sep 17 00:00:00 2001 From: Berwyn Date: Wed, 4 Jan 2023 22:16:56 +1300 Subject: [PATCH 37/94] Correct 'any book loaned' message --- utils/adept_loan_mgt.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utils/adept_loan_mgt.cpp b/utils/adept_loan_mgt.cpp index 2bb7693..190cf5f 100644 --- a/utils/adept_loan_mgt.cpp +++ b/utils/adept_loan_mgt.cpp @@ -209,7 +209,7 @@ private: { if (!loanedBooks.size()) { - std::cout << "Any book loaned" << std::endl; + std::cout << "No books loaned" << std::endl; return; } From 34216d1b6e38a2119891a446cd08889380bd8a59 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9gory=20Soutad=C3=A9?= Date: Thu, 5 Jan 2023 21:26:05 +0100 Subject: [PATCH 38/94] Check for target user before trying to decrypt a file --- src/libgourou.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/libgourou.cpp b/src/libgourou.cpp index 7341fe9..cdf3848 100644 --- a/src/libgourou.cpp +++ b/src/libgourou.cpp @@ -1035,13 +1035,13 @@ namespace gourou std::string user = extractTextElem(rightsDoc, "/adept:rights/licenseToken/user"); - if (this->user->getUUID() != user) - { - EXCEPTION(DRM_INVALID_USER, "This book has been downloaded for another user (" << user << ")"); - } - if (!encryptionKey) { + if (this->user->getUUID() != user) + { + EXCEPTION(DRM_INVALID_USER, "This book has been downloaded for another user (" << user << ")"); + } + std::string encryptedKey = extractTextElem(rightsDoc, "/adept:rights/licenseToken/encryptedKey"); std::string keyType = extractTextAttribute(rightsDoc, "/adept:rights/licenseToken/encryptedKey", "keyType", false); From 937c27fc2373cd5649d69f72993ffeb342f5ef32 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9gory=20Soutad=C3=A9?= Date: Thu, 5 Jan 2023 21:27:50 +0100 Subject: [PATCH 39/94] Fix some warnings --- src/libgourou.cpp | 2 +- utils/utils_common.cpp | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/libgourou.cpp b/src/libgourou.cpp index cdf3848..9210460 100644 --- a/src/libgourou.cpp +++ b/src/libgourou.cpp @@ -940,7 +940,7 @@ namespace gourou /* In adobekey.py, we get base64 decoded data [26:] */ ret = write(fd, privateLicenseKey.data()+26, privateLicenseKey.length()-26); close(fd); - if (ret != privateLicenseKey.length()-26) + if (ret != (int)(privateLicenseKey.length()-26)) { EXCEPTION(gourou::GOUROU_FILE_ERROR, "Error writing " << path); } diff --git a/utils/utils_common.cpp b/utils/utils_common.cpp index 41c0ac7..08ca3a2 100644 --- a/utils/utils_common.cpp +++ b/utils/utils_common.cpp @@ -98,7 +98,7 @@ void mkpath(const char *dir) void fileCopy(const char* in, const char* out) { - char buffer[4096]; + char buffer[4096], *_buffer; int ret, ret2, fdIn, fdOut; fdIn = open(in, O_RDONLY); @@ -121,11 +121,12 @@ void fileCopy(const char* in, const char* out) break; do { - ret2 = ::write(fdOut, buffer, ret); + _buffer = buffer; + ret2 = ::write(fdOut, _buffer, ret); if (ret2 >= 0) { ret -= ret2; - buffer += ret2; + _buffer += ret2; } else { From ab5afa50038e987da7d92794564df45382b3d1a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9gory=20Soutad=C3=A9?= Date: Thu, 5 Jan 2023 21:29:55 +0100 Subject: [PATCH 40/94] Add new default ADEPT directories : /home//.config/adept and $ADEPT_DIR environment variable --- README.md | 19 ++++++++++++------- include/libgourou.h | 11 ++++++----- src/libgourou.cpp | 25 +++++++++++++++++++++++-- utils/acsmdownloader.cpp | 2 ++ utils/adept_activate.cpp | 2 +- utils/adept_loan_mgt.cpp | 2 ++ utils/adept_remove.cpp | 2 ++ utils/utils_common.cpp | 16 +++++++++++++++- 8 files changed, 63 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index d09f731..7b14aa5 100644 --- a/README.md +++ b/README.md @@ -82,38 +82,43 @@ BUILD_SHARED build libgourou.so if 1, nothing if 0, can be combined with BUILD_S Utils ----- -You can import configuration from your eReader or create a new one with _utils/adept\_activate_ : +First, add libgourou.so to your LD_LIBRARY_PATH export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$PWD + +You can optionaly specify your .adept directory + + export ADEPT_DIR=/home/XXX + +Then, use utils as following : + +You can import configuration from your eReader or create a new one with _utils/adept\_activate_ : + ./utils/adept_activate -u -Then a _./.adept_ directory is created with all configuration file +Then a _/home//.config/adept_ directory is created with all configuration file To download an ePub/PDF : - export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$PWD ./utils/acsmdownloader -f To export your private key (for DeDRM software) : - export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$PWD ./utils/acsmdownloader --export-private-key [-o adobekey_1.der] To remove ADEPT DRM : - export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$PWD ./utils/adept_remove -f To list loaned books : - export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$PWD ./utils/adept_loan_mgt [-l] To return a loaned book : - export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$PWD ./utils/adept_loan_mgt -r + You can get utils full options description with -h or --help switch diff --git a/include/libgourou.h b/include/libgourou.h index 1f82dd8..706504a 100644 --- a/include/libgourou.h +++ b/include/libgourou.h @@ -32,10 +32,6 @@ #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 @@ -107,6 +103,11 @@ namespace gourou * @param operatorURL URL of operator that loans this book */ void returnLoan(const std::string& loanID, const std::string& operatorURL); + + /** + * @brief Return default ADEPT directory (ie /home//.config/adept) + */ + static std::string getDefaultAdeptDir(void); /** * @brief Create a new ADEPT environment (device.xml, devicesalt and activation.xml). @@ -118,7 +119,7 @@ namespace gourou * @param ACSServer Override main ACS server (default adeactivate.adobe.com) */ static DRMProcessor* createDRMProcessor(DRMProcessorClient* client, - bool randomSerial=false, const std::string& dirName=std::string(DEFAULT_ADEPT_DIR), + bool randomSerial=false, std::string dirName=std::string(""), const std::string& hobbes=std::string(HOBBES_DEFAULT_VERSION), const std::string& ACSServer=ACS_SERVER); diff --git a/src/libgourou.cpp b/src/libgourou.cpp index 9210460..ec199d4 100644 --- a/src/libgourou.cpp +++ b/src/libgourou.cpp @@ -28,6 +28,8 @@ #include #include +#define LOCAL_ADEPT_DIR "./.adept" + #define ASN_NONE 0x00 #define ASN_NS_TAG 0x01 #define ASN_CHILD 0x02 @@ -68,11 +70,14 @@ namespace gourou if (user) delete user; } - DRMProcessor* DRMProcessor::createDRMProcessor(DRMProcessorClient* client, bool randomSerial, const std::string& dirName, + DRMProcessor* DRMProcessor::createDRMProcessor(DRMProcessorClient* client, bool randomSerial, std::string dirName, const std::string& hobbes, const std::string& ACSServer) { DRMProcessor* processor = new DRMProcessor(client); + if (dirName == "") + dirName = getDefaultAdeptDir(); + Device* device = Device::createDevice(processor, dirName, hobbes, randomSerial); processor->device = device; @@ -844,7 +849,23 @@ namespace gourou addNonce(root); signNode(root); } - + + std::string DRMProcessor::getDefaultAdeptDir(void) + { +#ifndef DEFAULT_ADEPT_DIR + const char* user = getenv("USER"); + + if (user && user[0]) + { + return std::string("/home/") + user + std::string("/.config/adept/"); + } + else + return LOCAL_ADEPT_DIR; +#else + return DEFAULT_ADEPT_DIR "/"; +#endif + } + void DRMProcessor::returnLoan(const std::string& loanID, const std::string& operatorURL) { pugi::xml_document returnReq; diff --git a/utils/acsmdownloader.cpp b/utils/acsmdownloader.cpp index 1c66086..867205c 100644 --- a/utils/acsmdownloader.cpp +++ b/utils/acsmdownloader.cpp @@ -201,6 +201,8 @@ static void usage(const char* cmd) 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 << " * $ADEPT_DIR environment variable" << std::endl; + std::cout << " * /home//.config/adept" << std::endl; std::cout << " * Current directory" << std::endl; std::cout << " * .adept" << std::endl; std::cout << " * adobe-digital-editions directory" << std::endl; diff --git a/utils/adept_activate.cpp b/utils/adept_activate.cpp index bee58d3..80c8068 100644 --- a/utils/adept_activate.cpp +++ b/utils/adept_activate.cpp @@ -230,7 +230,7 @@ int main(int argc, char** argv) if (!_outputDir || _outputDir[0] == 0) { - outputDir = strdup(abspath(DEFAULT_ADEPT_DIR)); + outputDir = strdup(abspath(gourou::DRMProcessor::getDefaultAdeptDir().c_str())); } else { diff --git a/utils/adept_loan_mgt.cpp b/utils/adept_loan_mgt.cpp index 190cf5f..30e9444 100644 --- a/utils/adept_loan_mgt.cpp +++ b/utils/adept_loan_mgt.cpp @@ -348,6 +348,8 @@ static void usage(const char* cmd) std::cout << std::endl; std::cout << "ADEPT directory is optional. If not set, it's looked into :" << std::endl; + std::cout << " * $ADEPT_DIR environment variable" << std::endl; + std::cout << " * /home//.config/adept" << std::endl; std::cout << " * Current directory" << std::endl; std::cout << " * .adept" << std::endl; std::cout << " * adobe-digital-editions directory" << std::endl; diff --git a/utils/adept_remove.cpp b/utils/adept_remove.cpp index 6baeb53..77cda8e 100644 --- a/utils/adept_remove.cpp +++ b/utils/adept_remove.cpp @@ -158,6 +158,8 @@ static void usage(const char* cmd) 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 << " * $ADEPT_DIR environment variable" << std::endl; + std::cout << " * /home//.config/adept" << std::endl; std::cout << " * Current directory" << std::endl; std::cout << " * .adept" << std::endl; std::cout << " * adobe-digital-editions directory" << std::endl; diff --git a/utils/utils_common.cpp b/utils/utils_common.cpp index 08ca3a2..a686b40 100644 --- a/utils/utils_common.cpp +++ b/utils/utils_common.cpp @@ -61,6 +61,20 @@ bool fileExists(const char* filename) const char* findFile(const char* filename, bool inDefaultDirs) { + std::string path; + + const char* adeptDir = getenv("ADEPT_DIR"); + if (adeptDir && adeptDir[0]) + { + path = adeptDir + std::string("/") + filename; + if (fileExists(path.c_str())) + return strdup(path.c_str()); + } + + path = gourou::DRMProcessor::getDefaultAdeptDir() + filename; + if (fileExists(path.c_str())) + return strdup(path.c_str()); + if (fileExists(filename)) return strdup(filename); @@ -68,7 +82,7 @@ const char* findFile(const char* filename, bool inDefaultDirs) for (int i=0; i<(int)ARRAY_SIZE(defaultDirs); i++) { - std::string path = std::string(defaultDirs[i]) + filename; + path = std::string(defaultDirs[i]) + filename; if (fileExists(path.c_str())) return strdup(path.c_str()); } From fc839e671aba819d217bd957575237dbf6a45b2c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9gory=20Soutad=C3=A9?= Date: Fri, 6 Jan 2023 21:17:57 +0100 Subject: [PATCH 41/94] Manage ACSM files that contains server internal error --- include/libgourou_common.h | 3 ++- src/libgourou.cpp | 9 ++++++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/include/libgourou_common.h b/include/libgourou_common.h index 2947b14..12a717e 100644 --- a/include/libgourou_common.h +++ b/include/libgourou_common.h @@ -64,7 +64,8 @@ namespace gourou FF_INVALID_ACSM_FILE, FF_NO_HMAC_IN_ACSM_FILE, FF_NOT_ACTIVATED, - FF_NO_OPERATOR_URL + FF_NO_OPERATOR_URL, + FF_SERVER_INTERNAL_ERROR }; enum DOWNLOAD_ERROR { diff --git a/src/libgourou.cpp b/src/libgourou.cpp index ec199d4..b4d56e1 100644 --- a/src/libgourou.cpp +++ b/src/libgourou.cpp @@ -505,6 +505,13 @@ namespace gourou if (!acsmDoc.load_file(ACSMFile.c_str(), pugi::parse_ws_pcdata_single|pugi::parse_escapes, pugi::encoding_utf8)) EXCEPTION(FF_INVALID_ACSM_FILE, "Invalid ACSM file " << ACSMFile); + // Could be an server internal error + pugi::xml_node rootNode = acsmDoc.first_child(); + if (std::string(rootNode.name()) == "error") + { + EXCEPTION(FF_SERVER_INTERNAL_ERROR, rootNode.attribute("data").value()); + } + GOUROU_LOG(INFO, "Fulfill " << ACSMFile); // Build req file @@ -512,7 +519,7 @@ namespace gourou buildFulfillRequest(acsmDoc, fulfillReq); pugi::xpath_node root = fulfillReq.select_node("//adept:fulfill"); - pugi::xml_node rootNode = root.node(); + rootNode = root.node(); // Remove HMAC pugi::xpath_node xpathRes = fulfillReq.select_node("//hmac"); From 891ed05926cbd7c384909a1c3717ffe68b657820 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9gory=20Soutad=C3=A9?= Date: Sun, 8 Jan 2023 20:58:41 +0100 Subject: [PATCH 42/94] We can now specify directly file path for acsmdownloader and adept_remove (-f stille keeped for compatibility) --- utils/acsmdownloader.cpp | 3 +++ utils/adept_remove.cpp | 3 +++ 2 files changed, 6 insertions(+) diff --git a/utils/acsmdownloader.cpp b/utils/acsmdownloader.cpp index 867205c..89f7793 100644 --- a/utils/acsmdownloader.cpp +++ b/utils/acsmdownloader.cpp @@ -290,6 +290,9 @@ int main(int argc, char** argv) gourou::DRMProcessor::setLogLevel(verbose); + if (optind == argc-1) + acsmFile = argv[optind]; + if ((!acsmFile && !exportPrivateKey) || (outputDir && !outputDir[0]) || (outputFile && !outputFile[0])) { diff --git a/utils/adept_remove.cpp b/utils/adept_remove.cpp index 77cda8e..c2b226d 100644 --- a/utils/adept_remove.cpp +++ b/utils/adept_remove.cpp @@ -243,6 +243,9 @@ int main(int argc, char** argv) gourou::DRMProcessor::setLogLevel(verbose); + if (optind == argc-1) + inputFile = argv[optind]; + if (!inputFile || (outputDir && !outputDir[0]) || (outputFile && !outputFile[0])) { From 3a0ab4b43890d97a254bb22b8f65b136394eadc1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9gory=20Soutad=C3=A9?= Date: Sun, 8 Jan 2023 21:02:59 +0100 Subject: [PATCH 43/94] Update --help for utils and README.md --- README.md | 9 ++++----- utils/acsmdownloader.cpp | 25 ++++++++++++++----------- utils/adept_activate.cpp | 7 ++++--- utils/adept_loan_mgt.cpp | 10 +++++++--- utils/adept_remove.cpp | 19 ++++++++++++------- 5 files changed, 41 insertions(+), 29 deletions(-) diff --git a/README.md b/README.md index 7b14aa5..f11aed5 100644 --- a/README.md +++ b/README.md @@ -37,11 +37,10 @@ For libgourou : _externals_ : - * None + * libpugixml _internals_ : - * PugiXML * uPDFParser For utils : @@ -62,7 +61,7 @@ Compilation Use _make_ command - make [CROSS=XXX] [DEBUG=(0*|1)] [STATIC_UTILS=(0*|1)] [BUILD_UTILS=(0|1*)] [BUILD_STATIC=(0*|1)] [BUILD_SHARED=(0|1*)] [all*|clean|ultraclean|build_utils] + make [CROSS=XXX] [DEBUG=(0*|1)] [STATIC_UTILS=(0*|1)] [BUILD_UTILS=(0|1*)] [BUILD_STATIC=(0*|1)] [BUILD_SHARED=(0|1*)] [all*|clean|ultraclean|build_utils|install|uninstall] CROSS can define a cross compiler prefix (ie arm-linux-gnueabihf-) @@ -100,7 +99,7 @@ Then a _/home//.config/adept_ directory is created with all configuration To download an ePub/PDF : - ./utils/acsmdownloader -f + ./utils/acsmdownloader To export your private key (for DeDRM software) : @@ -108,7 +107,7 @@ To export your private key (for DeDRM software) : To remove ADEPT DRM : - ./utils/adept_remove -f + ./utils/adept_remove To list loaned books : diff --git a/utils/acsmdownloader.cpp b/utils/acsmdownloader.cpp index 89f7793..b84efc5 100644 --- a/utils/acsmdownloader.cpp +++ b/utils/acsmdownloader.cpp @@ -181,26 +181,29 @@ private: static void usage(const char* cmd) -{ - std::cout << "Download EPUB file from ACSM request file" << std::endl; - - std::cout << "Usage: " << basename((char*)cmd) << " [(-D|--adept-directory) dir] [(-d|--device-file) device.xml] [(-a|--activation-file) activation.xml] [(-k|--device-key-file) devicesalt] [(-O|--output-dir) dir] [(-o|--output-file) output(.epub|.pdf|.der)] [(-r|--resume)] [(-v|--verbose)] [(-h|--help)] (-f|--acsm-file) file.acsm|(-e|--export-private-key)" << std::endl << std::endl; - - std::cout << " " << "-D|--adept-directory" << "\t" << ".adept directory that must contains device.xml, activation.xml and devicesalt" << std::endl; - std::cout << " " << "-d|--device-file" << "\t" << "device.xml file from eReader" << std::endl; - std::cout << " " << "-a|--activation-file" << "\t" << "activation.xml file from eReader" << std::endl; - std::cout << " " << "-k|--device-key-file" << "\t" << "private device key file (eg devicesalt/devkey.bin) from eReader" << std::endl; +{ + std::cout << basename((char*)cmd) << " download EPUB file from ACSM request file" << std::endl << std::endl; + std::cout << "Usage: " << basename((char*)cmd) << " [OPTIONS] file.acsm" << std::endl << std::endl; + std::cout << "Global Options:" << std::endl; std::cout << " " << "-O|--output-dir" << "\t" << "Optional output directory were to put result (default ./)" << std::endl; std::cout << " " << "-o|--output-file" << "\t" << "Optional output filename (default )" << std::endl; - std::cout << " " << "-f|--acsm-file" << "\t" << "ACSM request file for epub download" << std::endl; + std::cout << " " << "-f|--acsm-file" << "\t" << "Backward compatibility: ACSM request file for epub download" << std::endl; std::cout << " " << "-e|--export-private-key"<< "\t" << "Export private key in DER format" << std::endl; std::cout << " " << "-r|--resume" << "\t\t" << "Try to resume download (in case of previous failure)" << std::endl; std::cout << " " << "-v|--verbose" << "\t\t" << "Increase verbosity, can be set multiple times" << std::endl; std::cout << " " << "-V|--version" << "\t\t" << "Display libgourou version" << std::endl; std::cout << " " << "-h|--help" << "\t\t" << "This help" << std::endl; + std::cout << "ADEPT Options:" << std::endl; + std::cout << " " << "-D|--adept-directory" << "\t" << ".adept directory that must contains device.xml, activation.xml and devicesalt" << std::endl; + std::cout << " " << "-d|--device-file" << "\t" << "device.xml file from eReader" << std::endl; + std::cout << " " << "-a|--activation-file" << "\t" << "activation.xml file from eReader" << std::endl; + std::cout << " " << "-k|--device-key-file" << "\t" << "private device key file (eg devicesalt/devkey.bin) from eReader" << std::endl; + std::cout << std::endl; - std::cout << "Device file, activation file and device key file are optionals. If not set, they are looked into :" << std::endl; + + std::cout << "Environment:" << std::endl; + std::cout << "Device file, activation file and device key file are optionals. If not set, they are looked into :" << std::endl << std::endl; std::cout << " * $ADEPT_DIR environment variable" << std::endl; std::cout << " * /home//.config/adept" << std::endl; std::cout << " * Current directory" << std::endl; diff --git a/utils/adept_activate.cpp b/utils/adept_activate.cpp index 80c8068..466add3 100644 --- a/utils/adept_activate.cpp +++ b/utils/adept_activate.cpp @@ -124,10 +124,11 @@ public: static void usage(const char* cmd) { - std::cout << "Create new device files used by ADEPT DRM" << std::endl; - - std::cout << "Usage: " << basename((char*)cmd) << " (-a|--anonymous) | ( (-u|--username) username [(-p|--password) password] ) [(-O|--output-dir) dir] [(-r|--random-serial)] [(-v|--verbose)] [(-h|--help)]" << std::endl << std::endl; + std::cout << basename((char*)cmd) << " create new device files used by ADEPT DRM" << std::endl << std::endl; + std::cout << "Usage: " << basename((char*)cmd) << " OPTIONS" << std::endl << std::endl; + + std::cout << "Global Options:" << std::endl; std::cout << " " << "-a|--anonymous" << "\t" << "Anonymous account, no need for username/password (Use it only with a DRM removal software)" << std::endl; std::cout << " " << "-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; diff --git a/utils/adept_loan_mgt.cpp b/utils/adept_loan_mgt.cpp index 30e9444..807e505 100644 --- a/utils/adept_loan_mgt.cpp +++ b/utils/adept_loan_mgt.cpp @@ -334,11 +334,11 @@ private: static void usage(const char* cmd) { - std::cout << "Manage loaned books" << std::endl; + std::cout << basename((char*)cmd) << " manage loaned books" << std::endl << std::endl; - std::cout << "Usage: " << basename((char*)cmd) << " [(-D|--adept-directory) dir] (-l|--list)|(-d|--delete loanID)|(-R|--return loanID) [(-v|--verbose)] [(-h|--help)]" << std::endl << std::endl; + std::cout << "Usage: " << basename((char*)cmd) << " [OPTIONS]" << std::endl << std::endl; - std::cout << " " << "-D|--adept-directory" << "\t" << ".adept directory that must contains device.xml, activation.xml and devicesalt" << std::endl; + std::cout << "Global Options:" << std::endl; std::cout << " " << "-l|--list" << "\t\t" << "List all loaned books" << std::endl; std::cout << " " << "-r|--return" << "\t\t" << "Return a loaned book" << std::endl; std::cout << " " << "-d|--delete" << "\t\t" << "Delete a loan entry without returning it" << std::endl; @@ -346,7 +346,11 @@ static void usage(const char* cmd) std::cout << " " << "-V|--version" << "\t\t" << "Display libgourou version" << std::endl; std::cout << " " << "-h|--help" << "\t\t" << "This help" << std::endl; + std::cout << "ADEPT Options:" << std::endl; + std::cout << " " << "-D|--adept-directory" << "\t" << ".adept directory that must contains device.xml, activation.xml and devicesalt" << std::endl; std::cout << std::endl; + + std::cout << "Environment:" << std::endl; std::cout << "ADEPT directory is optional. If not set, it's looked into :" << std::endl; std::cout << " * $ADEPT_DIR environment variable" << std::endl; std::cout << " * /home//.config/adept" << std::endl; diff --git a/utils/adept_remove.cpp b/utils/adept_remove.cpp index c2b226d..646e57b 100644 --- a/utils/adept_remove.cpp +++ b/utils/adept_remove.cpp @@ -141,22 +141,27 @@ public: static void usage(const char* cmd) { - std::cout << "Remove ADEPT DRM (from Adobe) of EPUB/PDF file" << std::endl; + std::cout << basename((char*)cmd) << " remove ADEPT DRM (from Adobe) of EPUB/PDF file" << std::endl << std::endl; - std::cout << "Usage: " << basename((char*)cmd) << " [(-D|--adept-directory) dir] [(-d|--device-file) device.xml] [(-a|--activation-file) activation.xml] [(-k|--device-key-file) devicesalt] [(-O|--output-dir) dir] [(-o|--output-file) output(.epub|.pdf|.der)] [(-v|--verbose)] [(-h|--help)] (-f|--input-file) file(.epub|pdf)" << std::endl << std::endl; + std::cout << "Usage: " << basename((char*)cmd) << " [OPTIONS] file(.epub|pdf)" << std::endl << std::endl; - std::cout << " " << "-D|--adept-directory" << "\t" << ".adept directory that must contains device.xml, activation.xml and devicesalt" << std::endl; - std::cout << " " << "-d|--device-file" << "\t" << "device.xml file from eReader" << std::endl; - std::cout << " " << "-a|--activation-file" << "\t" << "activation.xml file from eReader" << std::endl; - std::cout << " " << "-k|--device-key-file" << "\t" << "private device key file (eg devicesalt/devkey.bin) from eReader" << std::endl; + std::cout << "Global Options:" << std::endl; std::cout << " " << "-O|--output-dir" << "\t" << "Optional output directory were to put result (default ./)" << std::endl; std::cout << " " << "-o|--output-file" << "\t" << "Optional output filename (default inplace DRM removal>)" << std::endl; - std::cout << " " << "-f|--input-file" << "\t" << "EPUB/PDF file to process" << std::endl; + std::cout << " " << "-f|--input-file" << "\t" << "Backward compatibility: EPUB/PDF file to process" << std::endl; std::cout << " " << "-v|--verbose" << "\t\t" << "Increase verbosity, can be set multiple times" << std::endl; std::cout << " " << "-V|--version" << "\t\t" << "Display libgourou version" << std::endl; std::cout << " " << "-h|--help" << "\t\t" << "This help" << std::endl; + std::cout << "ADEPT Options:" << std::endl; + std::cout << " " << "-D|--adept-directory" << "\t" << ".adept directory that must contains device.xml, activation.xml and devicesalt" << std::endl; + std::cout << " " << "-d|--device-file" << "\t" << "device.xml file from eReader" << std::endl; + std::cout << " " << "-a|--activation-file" << "\t" << "activation.xml file from eReader" << std::endl; + std::cout << " " << "-k|--device-key-file" << "\t" << "private device key file (eg devicesalt/devkey.bin) from eReader" << std::endl; + std::cout << std::endl; + + std::cout << "Environment:" << std::endl; std::cout << "Device file, activation file and device key file are optionals. If not set, they are looked into :" << std::endl; std::cout << " * $ADEPT_DIR environment variable" << std::endl; std::cout << " * /home//.config/adept" << std::endl; From 8aec5be2449116653cfdf4aa1391f63cb6f4ca57 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9gory=20Soutad=C3=A9?= Date: Sun, 8 Jan 2023 21:05:04 +0100 Subject: [PATCH 44/94] Update Makefile to be more GNU/Linux style --- Makefile | 35 ++++++++++++++++++++++++++++++----- utils/Makefile | 12 +++++++++++- 2 files changed, 41 insertions(+), 6 deletions(-) diff --git a/Makefile b/Makefile index d05d86d..a962ba7 100644 --- a/Makefile +++ b/Makefile @@ -1,21 +1,30 @@ AR ?= $(CROSS)ar CXX ?= $(CROSS)g++ +ifeq ($(PREFIX),) + PREFIX := /usr/local +endif + UPDFPARSERLIB = ./lib/updfparser/libupdfparser.a -CXXFLAGS=-Wall -fPIC -I./include -I./lib/pugixml/src/ -I./lib/updfparser/include +CXXFLAGS += -Wall -fPIC -I./include -I./lib/pugixml/src/ -I./lib/updfparser/include LDFLAGS = $(UPDFPARSERLIB) +VERSION := $(shell cat include/libgourou.h |grep LIBGOUROU_VERSION|cut -d '"' -f2) + BUILD_STATIC ?= 0 BUILD_SHARED ?= 1 BUILD_UTILS ?= 1 TARGETS = +TARGET_BINARIES = ifneq ($(BUILD_STATIC), 0) TARGETS += libgourou.a + TARGET_BINARIES += libgourou.a endif ifneq ($(BUILD_SHARED), 0) TARGETS += libgourou.so + TARGET_BINARIES += libgourou.so libgourou.so.$(VERSION) endif ifneq ($(BUILD_UTILS), 0) TARGETS += build_utils @@ -42,7 +51,10 @@ OBJEXT := o SOURCES = src/libgourou.cpp src/user.cpp src/device.cpp src/fulfillment_item.cpp src/loan_token.cpp src/bytearray.cpp src/pugixml.cpp OBJECTS := $(patsubst $(SRCDIR)/%,$(BUILDDIR)/%,$(SOURCES:.$(SRCEXT)=.$(OBJEXT))) -all: lib obj $(TARGETS) +all: version lib obj $(TARGETS) + +version: + @echo "Building libgourou $(VERSION)" lib: mkdir lib @@ -63,13 +75,26 @@ libgourou.a: $(OBJECTS) $(UPDFPARSERLIB) $(AR) crs $@ obj/*.o $(UPDFPARSERLIB) libgourou.so: $(OBJECTS) $(UPDFPARSERLIB) - $(CXX) obj/*.o $(LDFLAGS) -o $@ -shared + $(CXX) obj/*.o -Wl,-soname,$@.$(VERSION) $(LDFLAGS) -o $@.$(VERSION) -shared + rm -f $@ + ln -s $@.$(VERSION) $@ build_utils: - make -C utils ROOT=$(PWD) CXX=$(CXX) AR=$(AR) DEBUG=$(DEBUG) STATIC_UTILS=$(STATIC_UTILS) + make -C utils ROOT=$(PWD) CXX=$(CXX) AR=$(AR) DEBUG=$(DEBUG) STATIC_UTILS=$(STATIC_UTILS) DEST_DIR=$(DEST_DIR) PREFIX=$(PREFIX) + +install: + install -d $(DESTDIR)$(PREFIX)/lib/ +# Use cp to preserver symlinks + cp --no-dereference $(TARGET_BINARIES) $(DESTDIR)$(PREFIX)/lib/ + make -C utils ROOT=$(PWD) CXX=$(CXX) AR=$(AR) DEBUG=$(DEBUG) STATIC_UTILS=$(STATIC_UTILS) DEST_DIR=$(DEST_DIR) PREFIX=$(PREFIX) install + +uninstall: + cd $(DESTDIR)$(PREFIX)/lib/ + rm -f $(TARGET_BINARIES) libgourou.so.$(VERSION) + cd - clean: - rm -rf libgourou.a libgourou.so obj + rm -rf libgourou.a libgourou.so libgourou.so.$(VERSION)* obj make -C utils clean ultraclean: clean diff --git a/utils/Makefile b/utils/Makefile index f279fdc..aa84e38 100644 --- a/utils/Makefile +++ b/utils/Makefile @@ -1,5 +1,6 @@ -TARGETS=acsmdownloader adept_activate adept_remove adept_loan_mgt launcher +TARGET_BINARIES=acsmdownloader adept_activate adept_remove adept_loan_mgt +TARGETS=$(TARGET_BINARIES) launcher CXXFLAGS=-Wall -fPIC -I$(ROOT)/include -I$(ROOT)/lib/pugixml/src/ @@ -32,6 +33,15 @@ ${COMMON_LIB}: ${COMMON_DEPS} ${STATIC_DEP} %: %.cpp ${COMMON_LIB} $(CXX) $(CXXFLAGS) $^ $(LDFLAGS) -o $@ +install: + install -d $(DESTDIR)$(PREFIX)/bin/ + install -m 644 $(TARGET_BINARIES) $(DESTDIR)$(PREFIX)/bin/ + +uninstall: + cd $(DESTDIR)$(PREFIX)/bin/ + rm -f $(TARGET_BINARIES) + cd - + clean: rm -f $(TARGETS) $(COMMON_LIB) From be78d242369e713c75be3bf70a9f10f2b8bcee46 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9gory=20Soutad=C3=A9?= Date: Sun, 8 Jan 2023 21:05:22 +0100 Subject: [PATCH 45/94] Add man pages for utils --- utils/man/acsmdownloader.1 | 61 ++++++++++++++++++++++++++++++++++ utils/man/adept_activate.1 | 67 ++++++++++++++++++++++++++++++++++++++ utils/man/adept_load_mgt.1 | 46 ++++++++++++++++++++++++++ utils/man/adept_remove.1 | 55 +++++++++++++++++++++++++++++++ 4 files changed, 229 insertions(+) create mode 100644 utils/man/acsmdownloader.1 create mode 100644 utils/man/adept_activate.1 create mode 100644 utils/man/adept_load_mgt.1 create mode 100644 utils/man/adept_remove.1 diff --git a/utils/man/acsmdownloader.1 b/utils/man/acsmdownloader.1 new file mode 100644 index 0000000..32fc08a --- /dev/null +++ b/utils/man/acsmdownloader.1 @@ -0,0 +1,61 @@ +.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.49.3. +.TH ACSMDOWNLOADER "1" "January 2023" "acsmdownloader download EPUB file from ACSM request file" "User Commands" +.SH NAME +acsmdownloader \- download EPUB file from ACSM request file +.SH SYNOPSIS +.B acsmdownloader +[\fI\,OPTIONS\/\fR] \fI\,file.acsm\/\fR +.SH DESCRIPTION +Download EPUB file from ACSM request file +.SS "Global Options:" +.TP +\fB\-O\fR|\-\-output\-dir +Optional output directory were to put result (default ./) +.TP +\fB\-o\fR|\-\-output\-file +Optional output filename (default ) +.TP +\fB\-f\fR|\-\-acsm\-file +Backward compatibility: ACSM request file for epub download +.TP +\fB\-e\fR|\-\-export\-private\-key +Export private key in DER format +.TP +\fB\-r\fR|\-\-resume +Try to resume download (in case of previous failure) +.TP +\fB\-v\fR|\-\-verbose +Increase verbosity, can be set multiple times +.TP +\fB\-V\fR|\-\-version +Display libgourou version +.TP +\fB\-h\fR|\-\-help +This help +.SS "ADEPT Options:" +.TP +\fB\-D\fR|\-\-adept\-directory +\&.adept directory that must contains device.xml, activation.xml and devicesalt +.TP +\fB\-d\fR|\-\-device\-file +device.xml file from eReader +.TP +\fB\-a\fR|\-\-activation\-file +activation.xml file from eReader +.TP +\fB\-k\fR|\-\-device\-key\-file +private device key file (eg devicesalt/devkey.bin) from eReader +.SH ENVIRONMENT +Device file, activation file and device key file are optionals. If not set, they are looked into : +.IP +* $ADEPT_DIR environment variable +.IP +* /home//.config/adept +.IP +* Current directory +.IP +* .adept +.IP +* adobe\-digital\-editions directory +.IP +* .adobe\-digital\-editions directory diff --git a/utils/man/adept_activate.1 b/utils/man/adept_activate.1 new file mode 100644 index 0000000..1516f38 --- /dev/null +++ b/utils/man/adept_activate.1 @@ -0,0 +1,67 @@ +.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.49.3. +.TH ADEPT_ACTIVATE "1" "January 2023" "adept_activate create new device files used by ADEPT DRM" "User Commands" +.SH NAME +adept_activate create new device files used by ADEPT DRM +.SH SYNOPSIS +.B adept_activate +\fI\,OPTIONS\/\fR +.SH DESCRIPTION +Create new device files used by ADEPT DRM +.SS "Global Options:" +.TP +\fB\-a\fR|\-\-anonymous +Anonymous account, no need for username/password (Use it only with a DRM removal software) +.TP +\fB\-u\fR|\-\-username +AdobeID username (ie adobe.com email account) +.TP +\fB\-p\fR|\-\-password +AdobeID password (asked if not set via command line) +.TP +\fB\-O\fR|\-\-output\-dir +Optional output directory were to put result (default ./.adept). This directory must not already exists +.TP +\fB\-H\fR|\-\-hobbes\-version +Force RMSDK version to a specific value (default: version of current librmsdk) +.TP +\fB\-r\fR|\-\-random\-serial +Generate a random device serial (if not set, it will be dependent of your current configuration) +.TP +\fB\-v\fR|\-\-verbose +Increase verbosity, can be set multiple times +.TP +\fB\-V\fR|\-\-version +Display libgourou version +.TP +\fB\-h\fR|\-\-help +This help +.PP +Usage: adept_activate OPTIONS +.SS "Global Options:" +.TP +\fB\-a\fR|\-\-anonymous +Anonymous account, no need for username/password (Use it only with a DRM removal software) +.TP +\fB\-u\fR|\-\-username +AdobeID username (ie adobe.com email account) +.TP +\fB\-p\fR|\-\-password +AdobeID password (asked if not set via command line) +.TP +\fB\-O\fR|\-\-output\-dir +Optional output directory were to put result (default ./.adept). This directory must not already exists +.TP +\fB\-H\fR|\-\-hobbes\-version +Force RMSDK version to a specific value (default: version of current librmsdk) +.TP +\fB\-r\fR|\-\-random\-serial +Generate a random device serial (if not set, it will be dependent of your current configuration) +.TP +\fB\-v\fR|\-\-verbose +Increase verbosity, can be set multiple times +.TP +\fB\-V\fR|\-\-version +Display libgourou version +.TP +\fB\-h\fR|\-\-help +This help diff --git a/utils/man/adept_load_mgt.1 b/utils/man/adept_load_mgt.1 new file mode 100644 index 0000000..619b600 --- /dev/null +++ b/utils/man/adept_load_mgt.1 @@ -0,0 +1,46 @@ +.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.49.3. +.TH ADEPT_LOAN_MGT "1" "January 2023" "adept_loan_mgt manage loaned books" "User Commands" +.SH NAME +adept_loan_mgt manage loaned books +.SH SYNOPSIS +.B adept_loan_mgt +[\fI\,OPTIONS\/\fR] +.SH DESCRIPTION +Manage ADEPT loaned books +.SS "Global Options:" +.TP +\fB\-l\fR|\-\-list +List all loaned books +.TP +\fB\-r\fR|\-\-return +Return a loaned book +.TP +\fB\-d\fR|\-\-delete +Delete a loan entry without returning it +.TP +\fB\-v\fR|\-\-verbose +Increase verbosity, can be set multiple times +.TP +\fB\-V\fR|\-\-version +Display libgourou version +.TP +\fB\-h\fR|\-\-help +This help +.SS "ADEPT Options:" +.TP +\fB\-D\fR|\-\-adept\-directory +\&.adept directory that must contains device.xml, activation.xml and devicesalt +.SH ENVIRONMENT +.SS "ADEPT directory is optional. If not set, it's looked into :" +.IP +* $ADEPT_DIR environment variable +.IP +* /home//.config/adept +.IP +* Current directory +.IP +* .adept +.IP +* adobe\-digital\-editions directory +.IP +* .adobe\-digital\-editions directory diff --git a/utils/man/adept_remove.1 b/utils/man/adept_remove.1 new file mode 100644 index 0000000..f1d446f --- /dev/null +++ b/utils/man/adept_remove.1 @@ -0,0 +1,55 @@ +.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.49.3. +.TH ADEPT_REMOVE "1" "January 2023" "adept_remove remove ADEPT DRM (from Adobe) of EPUB/PDF file" "User Commands" +.SH NAME +adept_remove remove ADEPT DRM (from Adobe) of EPUB/PDF file +.SH SYNOPSIS +.B adept_remove +[\fI\,OPTIONS\/\fR] \fI\,file(.epub|pdf)\/\fR +.SH DESCRIPTION +Remove ADEPT DRM (from Adobe) of EPUB/PDF file +.SS "Global Options:" +.TP +\fB\-O\fR|\-\-output\-dir +Optional output directory were to put result (default ./) +.TP +\fB\-o\fR|\-\-output\-file +Optional output filename (default inplace DRM removal>) +.TP +\fB\-f\fR|\-\-input\-file +Backward compatibility: EPUB/PDF file to process +.TP +\fB\-v\fR|\-\-verbose +Increase verbosity, can be set multiple times +.TP +\fB\-V\fR|\-\-version +Display libgourou version +.TP +\fB\-h\fR|\-\-help +This help +.SS "ADEPT Options:" +.TP +\fB\-D\fR|\-\-adept\-directory +\&.adept directory that must contains device.xml, activation.xml and devicesalt +.TP +\fB\-d\fR|\-\-device\-file +device.xml file from eReader +.TP +\fB\-a\fR|\-\-activation\-file +activation.xml file from eReader +.TP +\fB\-k\fR|\-\-device\-key\-file +private device key file (eg devicesalt/devkey.bin) from eReader +.SH ENVIRONMENT +.SS "Device file, activation file and device key file are optionals. If not set, they are looked into :" +.IP +* $ADEPT_DIR environment variable +.IP +* /home//.config/adept +.IP +* Current directory +.IP +* .adept +.IP +* adobe\-digital\-editions directory +.IP +* .adobe\-digital\-editions directory From 84b01a5de3a940d3d6dff2692234a229d6119625 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9gory=20Soutad=C3=A9?= Date: Sun, 8 Jan 2023 21:12:38 +0100 Subject: [PATCH 46/94] Use system version of pugixml, not a checkouted one --- Makefile | 6 +++--- README.md | 1 + scripts/setup.sh | 8 -------- scripts/update_lib.sh | 7 +------ src/pugixml.cpp | 1 - utils/Makefile | 2 +- 6 files changed, 6 insertions(+), 19 deletions(-) delete mode 120000 src/pugixml.cpp diff --git a/Makefile b/Makefile index a962ba7..98b7aa4 100644 --- a/Makefile +++ b/Makefile @@ -7,8 +7,8 @@ endif UPDFPARSERLIB = ./lib/updfparser/libupdfparser.a -CXXFLAGS += -Wall -fPIC -I./include -I./lib/pugixml/src/ -I./lib/updfparser/include -LDFLAGS = $(UPDFPARSERLIB) +CXXFLAGS += -Wall -fPIC -I./include -I./usr/include/pugixml -I./lib/updfparser/include +LDFLAGS = $(UPDFPARSERLIB) -lpugixml VERSION := $(shell cat include/libgourou.h |grep LIBGOUROU_VERSION|cut -d '"' -f2) @@ -48,7 +48,7 @@ TARGETDIR := bin SRCEXT := cpp OBJEXT := o -SOURCES = src/libgourou.cpp src/user.cpp src/device.cpp src/fulfillment_item.cpp src/loan_token.cpp src/bytearray.cpp src/pugixml.cpp +SOURCES = src/libgourou.cpp src/user.cpp src/device.cpp src/fulfillment_item.cpp src/loan_token.cpp src/bytearray.cpp OBJECTS := $(patsubst $(SRCDIR)/%,$(BUILDDIR)/%,$(SOURCES:.$(SRCEXT)=.$(OBJEXT))) all: version lib obj $(TARGETS) diff --git a/README.md b/README.md index f11aed5..2f333e7 100644 --- a/README.md +++ b/README.md @@ -48,6 +48,7 @@ For utils : * libcurl * OpenSSL * libzip + * libpugixml Internal libraries are automatically fetched and statically compiled during the first run. diff --git a/scripts/setup.sh b/scripts/setup.sh index a3965f4..5c25ff9 100755 --- a/scripts/setup.sh +++ b/scripts/setup.sh @@ -1,13 +1,5 @@ #!/bin/bash -# Pugixml -if [ ! -d lib/pugixml ] ; then - git clone https://github.com/zeux/pugixml.git lib/pugixml - pushd lib/pugixml - git checkout latest - popd -fi - # uPDFParser if [ ! -d lib/updfparser ] ; then git clone git://soutade.fr/updfparser.git lib/updfparser diff --git a/scripts/update_lib.sh b/scripts/update_lib.sh index 1605450..6c1ede0 100755 --- a/scripts/update_lib.sh +++ b/scripts/update_lib.sh @@ -1,17 +1,12 @@ #!/bin/bash -if [ ! -d lib/pugixml -o ! -d lib/updfparser ] ; then +if [ ! -d lib/updfparser ] ; then echo "Some libraries are missing" echo "You must run this script at the top of libgourou working direcotry." echo "./lib/setup.sh must be called first (make all)" exit 1 fi -# Pugixml -pushd lib/pugixml -git pull origin latest -popd - # uPDFParser pushd lib/updfparser git pull origin master diff --git a/src/pugixml.cpp b/src/pugixml.cpp deleted file mode 120000 index 5049a58..0000000 --- a/src/pugixml.cpp +++ /dev/null @@ -1 +0,0 @@ -../lib/pugixml/src/pugixml.cpp \ No newline at end of file diff --git a/utils/Makefile b/utils/Makefile index aa84e38..84ab81a 100644 --- a/utils/Makefile +++ b/utils/Makefile @@ -5,7 +5,7 @@ TARGETS=$(TARGET_BINARIES) launcher CXXFLAGS=-Wall -fPIC -I$(ROOT)/include -I$(ROOT)/lib/pugixml/src/ STATIC_DEP= -LDFLAGS += -L$(ROOT) -lcrypto -lzip -lz -lcurl +LDFLAGS += -L$(ROOT) -lcrypto -lzip -lz -lcurl -lpugixml ifneq ($(STATIC_UTILS),) STATIC_DEP = $(ROOT)/libgourou.a From a66dcb959c09549a32827fbde2351670967f9c13 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9gory=20Soutad=C3=A9?= Date: Tue, 10 Jan 2023 21:03:45 +0100 Subject: [PATCH 47/94] Work on Makefile --- Makefile | 39 ++++++++++++------- utils/Makefile | 16 ++++++-- .../{adept_load_mgt.1 => adept_loan_mgt.1} | 0 3 files changed, 36 insertions(+), 19 deletions(-) rename utils/man/{adept_load_mgt.1 => adept_loan_mgt.1} (100%) diff --git a/Makefile b/Makefile index 98b7aa4..61a54a9 100644 --- a/Makefile +++ b/Makefile @@ -1,3 +1,6 @@ +LIBDIR ?= /usr/lib +INCDIR ?= /usr/include + AR ?= $(CROSS)ar CXX ?= $(CROSS)g++ @@ -17,14 +20,14 @@ BUILD_SHARED ?= 1 BUILD_UTILS ?= 1 TARGETS = -TARGET_BINARIES = +TARGET_LIBRARIES = ifneq ($(BUILD_STATIC), 0) TARGETS += libgourou.a - TARGET_BINARIES += libgourou.a + TARGET_LIBRARIES += libgourou.a endif ifneq ($(BUILD_SHARED), 0) TARGETS += libgourou.so - TARGET_BINARIES += libgourou.so libgourou.so.$(VERSION) + TARGET_LIBRARIES += libgourou.so libgourou.so.$(VERSION) endif ifneq ($(BUILD_UTILS), 0) TARGETS += build_utils @@ -42,9 +45,7 @@ CXXFLAGS += -DSTATIC_NONCE=1 endif SRCDIR := src -INCDIR := inc BUILDDIR := obj -TARGETDIR := bin SRCEXT := cpp OBJEXT := o @@ -74,25 +75,33 @@ libgourou: libgourou.a libgourou.so libgourou.a: $(OBJECTS) $(UPDFPARSERLIB) $(AR) crs $@ obj/*.o $(UPDFPARSERLIB) -libgourou.so: $(OBJECTS) $(UPDFPARSERLIB) - $(CXX) obj/*.o -Wl,-soname,$@.$(VERSION) $(LDFLAGS) -o $@.$(VERSION) -shared - rm -f $@ - ln -s $@.$(VERSION) $@ +libgourou.so.$(VERSION): $(OBJECTS) $(UPDFPARSERLIB) + $(CXX) obj/*.o -Wl,-soname,$@ $(LDFLAGS) -o $@ -shared -build_utils: +libgourou.so: libgourou.so.$(VERSION) + ln -f -s $^ $@ + +build_utils: $(TARGET_LIBRARIES) make -C utils ROOT=$(PWD) CXX=$(CXX) AR=$(AR) DEBUG=$(DEBUG) STATIC_UTILS=$(STATIC_UTILS) DEST_DIR=$(DEST_DIR) PREFIX=$(PREFIX) -install: - install -d $(DESTDIR)$(PREFIX)/lib/ +install: $(TARGET_LIBRARIES) + install -d $(DESTDIR)$(PREFIX)$(LIBDIR) # Use cp to preserver symlinks - cp --no-dereference $(TARGET_BINARIES) $(DESTDIR)$(PREFIX)/lib/ + cp --no-dereference $(TARGET_LIBRARIES) $(DESTDIR)$(PREFIX)$(LIBDIR) make -C utils ROOT=$(PWD) CXX=$(CXX) AR=$(AR) DEBUG=$(DEBUG) STATIC_UTILS=$(STATIC_UTILS) DEST_DIR=$(DEST_DIR) PREFIX=$(PREFIX) install uninstall: - cd $(DESTDIR)$(PREFIX)/lib/ - rm -f $(TARGET_BINARIES) libgourou.so.$(VERSION) + cd $(DESTDIR)$(PREFIX)/$(LIBDIR) + rm -f $(TARGET_LIBRARIES) libgourou.so.$(VERSION) cd - +install_headers: + install -d $(DESTDIR)$(PREFIX)/$(INCDIR)/libgourou + cp --no-dereference include/*.h $(DESTDIR)$(PREFIX)/$(INCDIR)/libgourou + +uninstall_headers: + rm -rf $(DESTDIR)$(PREFIX)/$(INCDIR)/libgourou + clean: rm -rf libgourou.a libgourou.so libgourou.so.$(VERSION)* obj make -C utils clean diff --git a/utils/Makefile b/utils/Makefile index 84ab81a..e7d0ae5 100644 --- a/utils/Makefile +++ b/utils/Makefile @@ -1,7 +1,11 @@ +BINDIR ?= /usr/bin +MANDIR ?= /usr/share/man TARGET_BINARIES=acsmdownloader adept_activate adept_remove adept_loan_mgt TARGETS=$(TARGET_BINARIES) launcher +MAN_PAGES=acsmdownloader adept_activate adept_remove adept_loan_mgt + CXXFLAGS=-Wall -fPIC -I$(ROOT)/include -I$(ROOT)/lib/pugixml/src/ STATIC_DEP= @@ -33,14 +37,18 @@ ${COMMON_LIB}: ${COMMON_DEPS} ${STATIC_DEP} %: %.cpp ${COMMON_LIB} $(CXX) $(CXXFLAGS) $^ $(LDFLAGS) -o $@ -install: - install -d $(DESTDIR)$(PREFIX)/bin/ - install -m 644 $(TARGET_BINARIES) $(DESTDIR)$(PREFIX)/bin/ +install: $(TARGET_BINARIES) + install -d $(DESTDIR)$(PREFIX)/$(BINDIR) + install -m 755 $(TARGET_BINARIES) $(DESTDIR)$(PREFIX)/$(BINDIR) + install -d $(DESTDIR)$(PREFIX)/$(MANDIR)/man1 + install -m 644 man/*.1 $(DESTDIR)$(PREFIX)/$(MANDIR)/man1 uninstall: - cd $(DESTDIR)$(PREFIX)/bin/ + cd $(DESTDIR)$(PREFIX)/$(BINDIR) rm -f $(TARGET_BINARIES) cd - + cd $(DESTDIR)$(PREFIX)/$(MANDIR)/man1 + rm -f $(addsuffix .1,$(TARGET_BINARIES) clean: rm -f $(TARGETS) $(COMMON_LIB) diff --git a/utils/man/adept_load_mgt.1 b/utils/man/adept_loan_mgt.1 similarity index 100% rename from utils/man/adept_load_mgt.1 rename to utils/man/adept_loan_mgt.1 From 1213b342505af7495817a1661cbd0b0726157778 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9gory=20Soutad=C3=A9?= Date: Tue, 10 Jan 2023 21:12:30 +0100 Subject: [PATCH 48/94] Fix ADEPT path creation within adept_activate --- utils/adept_activate.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utils/adept_activate.cpp b/utils/adept_activate.cpp index 466add3..7b215b3 100644 --- a/utils/adept_activate.cpp +++ b/utils/adept_activate.cpp @@ -231,7 +231,7 @@ int main(int argc, char** argv) if (!_outputDir || _outputDir[0] == 0) { - outputDir = strdup(abspath(gourou::DRMProcessor::getDefaultAdeptDir().c_str())); + outputDir = strdup(gourou::DRMProcessor::getDefaultAdeptDir().c_str()); } else { From 50bc16079f48cbffdacf5c07c99e12a1ff5aebdb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9gory=20Soutad=C3=A9?= Date: Sat, 14 Jan 2023 12:56:06 +0100 Subject: [PATCH 49/94] Fix static build --- Makefile | 20 ++++++++++++-------- utils/Makefile | 10 +++++----- 2 files changed, 17 insertions(+), 13 deletions(-) diff --git a/Makefile b/Makefile index 61a54a9..ceaf392 100644 --- a/Makefile +++ b/Makefile @@ -11,7 +11,7 @@ endif UPDFPARSERLIB = ./lib/updfparser/libupdfparser.a CXXFLAGS += -Wall -fPIC -I./include -I./usr/include/pugixml -I./lib/updfparser/include -LDFLAGS = $(UPDFPARSERLIB) -lpugixml +LDFLAGS = -lpugixml VERSION := $(shell cat include/libgourou.h |grep LIBGOUROU_VERSION|cut -d '"' -f2) @@ -21,9 +21,13 @@ BUILD_UTILS ?= 1 TARGETS = TARGET_LIBRARIES = +ifneq ($(STATIC_UTILS),) + BUILD_STATIC=1 +endif ifneq ($(BUILD_STATIC), 0) TARGETS += libgourou.a TARGET_LIBRARIES += libgourou.a + STATIC_UTILS=1 endif ifneq ($(BUILD_SHARED), 0) TARGETS += libgourou.so @@ -70,25 +74,25 @@ obj: $(BUILDDIR)/%.$(OBJEXT): $(SRCDIR)/%.$(SRCEXT) $(CXX) $(CXXFLAGS) -c $^ -o $@ -libgourou: libgourou.a libgourou.so +libgourou: $(TARGET_LIBRARIES) libgourou.a: $(OBJECTS) $(UPDFPARSERLIB) - $(AR) crs $@ obj/*.o $(UPDFPARSERLIB) + $(AR) rcs --thin $@ $^ libgourou.so.$(VERSION): $(OBJECTS) $(UPDFPARSERLIB) - $(CXX) obj/*.o -Wl,-soname,$@ $(LDFLAGS) -o $@ -shared + $(CXX) $^ -Wl,-soname,$@ $(LDFLAGS) -o $@ -shared libgourou.so: libgourou.so.$(VERSION) ln -f -s $^ $@ build_utils: $(TARGET_LIBRARIES) - make -C utils ROOT=$(PWD) CXX=$(CXX) AR=$(AR) DEBUG=$(DEBUG) STATIC_UTILS=$(STATIC_UTILS) DEST_DIR=$(DEST_DIR) PREFIX=$(PREFIX) + $(MAKE) -C utils ROOT=$(PWD) CXX=$(CXX) AR=$(AR) DEBUG=$(DEBUG) STATIC_UTILS=$(STATIC_UTILS) DEST_DIR=$(DEST_DIR) PREFIX=$(PREFIX) install: $(TARGET_LIBRARIES) install -d $(DESTDIR)$(PREFIX)$(LIBDIR) # Use cp to preserver symlinks cp --no-dereference $(TARGET_LIBRARIES) $(DESTDIR)$(PREFIX)$(LIBDIR) - make -C utils ROOT=$(PWD) CXX=$(CXX) AR=$(AR) DEBUG=$(DEBUG) STATIC_UTILS=$(STATIC_UTILS) DEST_DIR=$(DEST_DIR) PREFIX=$(PREFIX) install + $(MAKE) -C utils ROOT=$(PWD) CXX=$(CXX) AR=$(AR) DEBUG=$(DEBUG) STATIC_UTILS=$(STATIC_UTILS) DEST_DIR=$(DEST_DIR) PREFIX=$(PREFIX) install uninstall: cd $(DESTDIR)$(PREFIX)/$(LIBDIR) @@ -104,8 +108,8 @@ uninstall_headers: clean: rm -rf libgourou.a libgourou.so libgourou.so.$(VERSION)* obj - make -C utils clean + $(MAKE) -C utils clean ultraclean: clean rm -rf lib - make -C utils ultraclean + $(MAKE) -C utils ultraclean diff --git a/utils/Makefile b/utils/Makefile index e7d0ae5..687ce1d 100644 --- a/utils/Makefile +++ b/utils/Makefile @@ -30,12 +30,12 @@ COMMON_LIB = utils.a all: $(TARGETS) -${COMMON_LIB}: ${COMMON_DEPS} ${STATIC_DEP} - $(CXX) $(CXXFLAGS) ${COMMON_DEPS} $(LDFLAGS) -c - $(AR) crs $@ ${COMMON_OBJECTS} $(STATIC_DEP) +${COMMON_LIB}: $(COMMON_DEPS) + $(CXX) $(CXXFLAGS) $(COMMON_DEPS) $(LDFLAGS) -c + $(AR) crs $@ $(COMMON_OBJECTS) -%: %.cpp ${COMMON_LIB} - $(CXX) $(CXXFLAGS) $^ $(LDFLAGS) -o $@ +%: %.cpp $(COMMON_LIB) $(STATIC_DEP) + $(CXX) $(CXXFLAGS) $^ $(STATIC_DEP) $(LDFLAGS) -o $@ install: $(TARGET_BINARIES) install -d $(DESTDIR)$(PREFIX)/$(BINDIR) From cad2189fc2862194a763aeaac9adec5e2ae38965 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9gory=20Soutad=C3=A9?= Date: Sat, 14 Jan 2023 18:21:06 +0100 Subject: [PATCH 50/94] Typo fix --- utils/adept_loan_mgt.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utils/adept_loan_mgt.cpp b/utils/adept_loan_mgt.cpp index 807e505..34f328f 100644 --- a/utils/adept_loan_mgt.cpp +++ b/utils/adept_loan_mgt.cpp @@ -252,7 +252,7 @@ private: std::cout.width (fillExpiration); std::cout << ""; - std::cout << "Exipration"; + std::cout << "Expiration"; std::cout.width (fillExpiration); std::cout << "" << std::endl; From 46afe771c788a32eb7a96234a4cea0810293942d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9gory=20Soutad=C3=A9?= Date: Sun, 15 Jan 2023 09:51:00 +0100 Subject: [PATCH 51/94] Remove old pugixml include in utils Makefile --- utils/Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utils/Makefile b/utils/Makefile index 687ce1d..ee77ad5 100644 --- a/utils/Makefile +++ b/utils/Makefile @@ -6,7 +6,7 @@ TARGETS=$(TARGET_BINARIES) launcher MAN_PAGES=acsmdownloader adept_activate adept_remove adept_loan_mgt -CXXFLAGS=-Wall -fPIC -I$(ROOT)/include -I$(ROOT)/lib/pugixml/src/ +CXXFLAGS=-Wall -fPIC -I$(ROOT)/include STATIC_DEP= LDFLAGS += -L$(ROOT) -lcrypto -lzip -lz -lcurl -lpugixml From c259cbd5a38f571c937418939fff07f63c049154 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9gory=20Soutad=C3=A9?= Date: Tue, 28 Mar 2023 20:32:05 +0200 Subject: [PATCH 52/94] Add missing libgen.h in utils for basename() call --- utils/adept_activate.cpp | 1 + utils/adept_remove.cpp | 1 + 2 files changed, 2 insertions(+) diff --git a/utils/adept_activate.cpp b/utils/adept_activate.cpp index 7b215b3..808fbe1 100644 --- a/utils/adept_activate.cpp +++ b/utils/adept_activate.cpp @@ -31,6 +31,7 @@ #include #include #include +#include #include #include diff --git a/utils/adept_remove.cpp b/utils/adept_remove.cpp index 646e57b..c8d3ecb 100644 --- a/utils/adept_remove.cpp +++ b/utils/adept_remove.cpp @@ -27,6 +27,7 @@ */ #include +#include #include From a0f6324999000f3ceca00b73da6147d69bf4cc91 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9gory=20Soutad=C3=A9?= Date: Sat, 29 Apr 2023 13:09:53 +0200 Subject: [PATCH 53/94] Try to fix GCC 13 compilation errors --- include/Base64.h | 1 + include/libgourou.h | 1 + 2 files changed, 2 insertions(+) diff --git a/include/Base64.h b/include/Base64.h index e5b4930..548b9f1 100644 --- a/include/Base64.h +++ b/include/Base64.h @@ -26,6 +26,7 @@ */ #include +#include namespace macaron { diff --git a/include/libgourou.h b/include/libgourou.h index 706504a..7093f8d 100644 --- a/include/libgourou.h +++ b/include/libgourou.h @@ -27,6 +27,7 @@ #include "drmprocessorclient.h" #include +#include #ifndef HOBBES_DEFAULT_VERSION #define HOBBES_DEFAULT_VERSION "10.0.4" From e06d20a3921d9872b50dfda6c0af60ffa77cf97f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9gory=20Soutad=C3=A9?= Date: Sat, 5 Aug 2023 14:43:48 +0200 Subject: [PATCH 54/94] DRM removal: Forgot to decrypt HexaString objects --- src/libgourou.cpp | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/src/libgourou.cpp b/src/libgourou.cpp index b4d56e1..31704d6 100644 --- a/src/libgourou.cpp +++ b/src/libgourou.cpp @@ -1363,6 +1363,30 @@ namespace gourou delete[] clearData; } + else if (dictData->type() == uPDFParser::DataType::HEXASTRING) + { + string = ((uPDFParser::HexaString*) dictData)->value(); + ByteArray hexStr = ByteArray::fromHex(string); + + unsigned char* encryptedData = hexStr.data(); + unsigned int dataLength = hexStr.size(); + unsigned char* clearData = new unsigned char[dataLength]; + unsigned int dataOutLength; + + GOUROU_LOG(DEBUG, "Decrypt hexa string " << dictIt->first << " " << dataLength); + + client->decrypt(CryptoInterface::ALGO_RC4, CryptoInterface::CHAIN_ECB, + tmpKey, sizeof(tmpKey), /* Key */ + NULL, 0, /* IV */ + encryptedData, dataLength, + clearData, &dataOutLength); + + ByteArray clearHexStr = ByteArray(clearData, dataOutLength); + decodedStrings[dictIt->first] = new uPDFParser::HexaString( + clearHexStr.toHex()); + + delete[] clearData; + } } for (dictIt = decodedStrings.begin(); dictIt != decodedStrings.end(); dictIt++) From 9a75213b4957d0c906c5b9cfdcb35b3b2aa5ad61 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9gory=20Soutad=C3=A9?= Date: Tue, 8 Aug 2023 20:11:33 +0200 Subject: [PATCH 55/94] Fix misuse of DESTDIR and PREFIX in Makefile --- Makefile | 13 +++++-------- utils/Makefile | 4 ++-- 2 files changed, 7 insertions(+), 10 deletions(-) diff --git a/Makefile b/Makefile index ceaf392..31e9b6b 100644 --- a/Makefile +++ b/Makefile @@ -1,13 +1,10 @@ -LIBDIR ?= /usr/lib -INCDIR ?= /usr/include +PREFIX ?= /usr/local +LIBDIR ?= /lib +INCDIR ?= /include AR ?= $(CROSS)ar CXX ?= $(CROSS)g++ -ifeq ($(PREFIX),) - PREFIX := /usr/local -endif - UPDFPARSERLIB = ./lib/updfparser/libupdfparser.a CXXFLAGS += -Wall -fPIC -I./include -I./usr/include/pugixml -I./lib/updfparser/include @@ -86,13 +83,13 @@ libgourou.so: libgourou.so.$(VERSION) ln -f -s $^ $@ build_utils: $(TARGET_LIBRARIES) - $(MAKE) -C utils ROOT=$(PWD) CXX=$(CXX) AR=$(AR) DEBUG=$(DEBUG) STATIC_UTILS=$(STATIC_UTILS) DEST_DIR=$(DEST_DIR) PREFIX=$(PREFIX) + $(MAKE) -C utils ROOT=$(PWD) CXX=$(CXX) AR=$(AR) DEBUG=$(DEBUG) STATIC_UTILS=$(STATIC_UTILS) DESTDIR=$(DESTDIR) PREFIX=$(PREFIX) install: $(TARGET_LIBRARIES) install -d $(DESTDIR)$(PREFIX)$(LIBDIR) # Use cp to preserver symlinks cp --no-dereference $(TARGET_LIBRARIES) $(DESTDIR)$(PREFIX)$(LIBDIR) - $(MAKE) -C utils ROOT=$(PWD) CXX=$(CXX) AR=$(AR) DEBUG=$(DEBUG) STATIC_UTILS=$(STATIC_UTILS) DEST_DIR=$(DEST_DIR) PREFIX=$(PREFIX) install + $(MAKE) -C utils ROOT=$(PWD) CXX=$(CXX) AR=$(AR) DEBUG=$(DEBUG) STATIC_UTILS=$(STATIC_UTILS) DESTDIR=$(DESTDIR) PREFIX=$(PREFIX) install uninstall: cd $(DESTDIR)$(PREFIX)/$(LIBDIR) diff --git a/utils/Makefile b/utils/Makefile index ee77ad5..5fa5fe2 100644 --- a/utils/Makefile +++ b/utils/Makefile @@ -1,5 +1,5 @@ -BINDIR ?= /usr/bin -MANDIR ?= /usr/share/man +BINDIR ?= /bin +MANDIR ?= /share/man TARGET_BINARIES=acsmdownloader adept_activate adept_remove adept_loan_mgt TARGETS=$(TARGET_BINARIES) launcher From bb5349d7109de4c908eb4a274a7ceee39b0eeba8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9gory=20Soutad=C3=A9?= Date: Tue, 8 Aug 2023 20:14:23 +0200 Subject: [PATCH 56/94] Update version --- include/libgourou.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/libgourou.h b/include/libgourou.h index 7093f8d..e3baa9b 100644 --- a/include/libgourou.h +++ b/include/libgourou.h @@ -37,7 +37,7 @@ #define ACS_SERVER "http://adeactivate.adobe.com/adept" #endif -#define LIBGOUROU_VERSION "0.8.1" +#define LIBGOUROU_VERSION "0.8.2" namespace gourou { From c19279397f591ea5849d449d46baf048a509b424 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9gory=20Soutad=C3=A9?= Date: Wed, 9 Aug 2023 20:58:06 +0200 Subject: [PATCH 57/94] Update README.md --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index 2f333e7..c986e3b 100644 --- a/README.md +++ b/README.md @@ -76,6 +76,8 @@ BUILD_STATIC build libgourou.a if 1, nothing if 0, can be combined with BUILD_SH BUILD_SHARED build libgourou.so if 1, nothing if 0, can be combined with BUILD_STATIC +other variables are DESTDIR and PREFIX to handle destination install directory + * Default value @@ -147,3 +149,4 @@ Special thanks * _Jens_ for all test samples and utils testing * _Milian_ for debug & code + * _Berwyn H_ for all test samples, feedbacks, patches and kind donation From 9388d82138b3d9eb3c49c8a90c56034a15d300eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9gory=20Soutad=C3=A9?= Date: Mon, 4 Sep 2023 18:28:47 +0200 Subject: [PATCH 58/94] Loan ID must be Fullfilment ID, not value(s) --- src/loan_token.cpp | 15 ++------------- 1 file changed, 2 insertions(+), 13 deletions(-) diff --git a/src/loan_token.cpp b/src/loan_token.cpp index 8977ef2..14b50eb 100644 --- a/src/loan_token.cpp +++ b/src/loan_token.cpp @@ -29,24 +29,13 @@ namespace gourou if (!node) EXCEPTION(FFI_INVALID_LOAN_TOKEN, "No loanToken element in document"); - node = doc.select_node("/envelope/loanToken/loan").node(); + node = doc.select_node("/envelope/fulfillmentResult/fulfillment").node(); if (node) properties["id"] = node.first_child().value(); else { - node = doc.select_node("/envelope/fulfillmentResult/resourceItemInfo/licenseToken/permissions/display/loan").node(); - - if (node) - properties["id"] = node.first_child().value(); - else - { - node = doc.select_node("/envelope/fulfillmentResult/resourceItemInfo/licenseToken/permissions/play/loan").node(); - if (node) - properties["id"] = node.first_child().value(); - else - EXCEPTION(FFI_INVALID_LOAN_TOKEN, "No loanToken/loan element in document"); - } + EXCEPTION(FFI_INVALID_LOAN_TOKEN, "No fulfillment element in document"); } node = doc.select_node("/envelope/loanToken/operatorURL").node(); From e0bb1bd4f89acc8e229e404f72fa33c0d6be1b8d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9gory=20Soutad=C3=A9?= Date: Tue, 5 Sep 2023 20:48:22 +0200 Subject: [PATCH 59/94] Add notify server feature --- include/libgourou.h | 11 ++++-- include/libgourou_common.h | 60 ++++++++++++++++++++--------- src/libgourou.cpp | 78 ++++++++++++++++++++++++++++++++++++-- 3 files changed, 124 insertions(+), 25 deletions(-) diff --git a/include/libgourou.h b/include/libgourou.h index e3baa9b..37cc68e 100644 --- a/include/libgourou.h +++ b/include/libgourou.h @@ -67,10 +67,11 @@ namespace gourou * @brief Fulfill ACSM file to server in order to retrieve ePub fulfillment item * * @param ACSMFile Path of ACSMFile + * @param notify Notify server if requested by response * * @return a FulfillmentItem if all is OK */ - FulfillmentItem* fulfill(const std::string& ACSMFile); + FulfillmentItem* fulfill(const std::string& ACSMFile, bool notify=true); /** * @brief Once fulfilled, ePub file needs to be downloaded. @@ -102,8 +103,9 @@ namespace gourou * * @param loanID Loan ID received during fulfill * @param operatorURL URL of operator that loans this book + * @param notify Notify server if requested by response */ - void returnLoan(const std::string& loanID, const std::string& operatorURL); + void returnLoan(const std::string& loanID, const std::string& operatorURL, bool notify=true); /** * @brief Return default ADEPT directory (ie /home//.config/adept) @@ -156,7 +158,7 @@ namespace gourou * @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 */ @@ -233,6 +235,9 @@ namespace gourou void buildSignInRequest(pugi::xml_document& signInRequest, const std::string& adobeID, const std::string& adobePassword, const std::string& authenticationCertificate); void fetchLicenseServiceCertificate(const std::string& licenseURL, const std::string& operatorURL); + void buildNotifyReq(pugi::xml_document& returnReq, pugi::xml_node& body); + void notifyServer(pugi::xml_node& notifyRoot); + void notifyServer(pugi::xml_document& fulfillReply); std::string encryptedKeyFirstPass(pugi::xml_document& rightsDoc, const std::string& encryptedKey, const std::string& keyType); void decryptADEPTKey(pugi::xml_document& rightsDoc, unsigned char* decryptedKey, const unsigned char* encryptionKey=0, unsigned encryptionKeySize=0); void removeEPubDRM(const std::string& filenameIn, const std::string& filenameOut, const unsigned char* encryptionKey, unsigned encryptionKeySize); diff --git a/include/libgourou_common.h b/include/libgourou_common.h index 12a717e..a6f2a91 100644 --- a/include/libgourou_common.h +++ b/include/libgourou_common.h @@ -235,12 +235,7 @@ namespace gourou return ltrim(rtrim(s, t), t); } - /** - * @brief Extract text node from tag in document - * It can throw an exception if tag does not exists - * or just return an empty value - */ - static inline std::string extractTextElem(const pugi::xml_node& root, const char* tagName, bool throwOnNull=true) + static inline pugi::xml_node getNode(const pugi::xml_node& root, const char* tagName, bool throwOnNull=true) { pugi::xpath_node xpath_node = root.select_node(tagName); @@ -249,10 +244,23 @@ namespace gourou if (throwOnNull) EXCEPTION(GOUROU_TAG_NOT_FOUND, "Tag " << tagName << " not found"); - return ""; + return pugi::xml_node(); } - pugi::xml_node node = xpath_node.node().first_child(); + return xpath_node.node(); + + } + + /** + * @brief Extract text node from tag in document + * It can throw an exception if tag does not exists + * or just return an empty value + */ + static inline std::string extractTextElem(const pugi::xml_node& root, const char* tagName, bool throwOnNull=true) + { + pugi::xml_node node = getNode(root, tagName, throwOnNull); + + node = node.first_child(); if (!node) { @@ -266,6 +274,30 @@ namespace gourou return trim(res); } + /** + * @brief Set text node of a tag in document + * It can throw an exception if tag does not exists + */ + static inline void setTextElem(const pugi::xml_node& root, const char* tagName, + const std::string& value, bool throwOnNull=true) + { + pugi::xml_node node = getNode(root, tagName, throwOnNull); + + if (!node) + { + if (throwOnNull) + EXCEPTION(GOUROU_TAG_NOT_FOUND, "Text element for tag " << tagName << " not found"); + return; + } + + node = node.first_child(); + + if (!node) + node.append_child(pugi::node_pcdata).set_value(value.c_str()); + else + node.set_value(value.c_str()); + } + /** * @brief Extract text attribute from tag in document * It can throw an exception if attribute does not exists @@ -273,17 +305,9 @@ namespace gourou */ static inline std::string extractTextAttribute(const pugi::xml_node& root, const char* tagName, const char* attributeName, bool throwOnNull=true) { - pugi::xpath_node xpath_node = root.select_node(tagName); + pugi::xml_node node = getNode(root, tagName, throwOnNull); - if (!xpath_node) - { - if (throwOnNull) - EXCEPTION(GOUROU_TAG_NOT_FOUND, "Tag " << tagName << " not found"); - - return ""; - } - - pugi::xml_attribute attr = xpath_node.node().attribute(attributeName); + pugi::xml_attribute attr = node.attribute(attributeName); if (!attr) { diff --git a/src/libgourou.cpp b/src/libgourou.cpp index 31704d6..2bbc201 100644 --- a/src/libgourou.cpp +++ b/src/libgourou.cpp @@ -495,7 +495,7 @@ namespace gourou user->updateActivationFile(activationDoc); } - FulfillmentItem* DRMProcessor::fulfill(const std::string& ACSMFile) + FulfillmentItem* DRMProcessor::fulfill(const std::string& ACSMFile, bool notify) { if (!user->getPKCS12().length()) EXCEPTION(FF_NOT_ACTIVATED, "Device not activated"); @@ -580,7 +580,12 @@ namespace gourou fetchLicenseServiceCertificate(licenseURL, operatorURL); - return new FulfillmentItem(fulfillReply, user); + FulfillmentItem* item = new FulfillmentItem(fulfillReply, user); + + if (notify) + notifyServer(fulfillReply); + + return item; } DRMProcessor::ITEM_TYPE DRMProcessor::download(FulfillmentItem* item, std::string path, bool resume) @@ -873,7 +878,8 @@ namespace gourou #endif } - void DRMProcessor::returnLoan(const std::string& loanID, const std::string& operatorURL) + void DRMProcessor::returnLoan(const std::string& loanID, const std::string& operatorURL, + bool notify) { pugi::xml_document returnReq; @@ -881,9 +887,73 @@ namespace gourou buildReturnReq(returnReq, loanID, operatorURL); - sendRequest(returnReq, operatorURL + "/LoanReturn"); + ByteArray replyData = sendRequest(returnReq, operatorURL + "/LoanReturn"); + + pugi::xml_document fulfillReply; + + fulfillReply.load_string((const char*)replyData.data()); + + if (notify) + notifyServer(fulfillReply); } + void DRMProcessor::buildNotifyReq(pugi::xml_document& returnReq, pugi::xml_node& body) + { + pugi::xml_node decl = returnReq.append_child(pugi::node_declaration); + decl.append_attribute("version") = "1.0"; + + pugi::xml_node root = returnReq.append_child("adept:notification"); + root.append_attribute("xmlns:adept") = ADOBE_ADEPT_NS; + + appendTextElem(root, "adept:user", user->getUUID()); + appendTextElem(root, "adept:device", user->getDeviceUUID()); + body = root.append_copy(body); + body.append_attribute("xmlns") = ADOBE_ADEPT_NS; + + addNonce(root); + signNode(root); + } + + void DRMProcessor::notifyServer(pugi::xml_node& notifyRoot) + { + std::string notifyUrl = extractTextElem(notifyRoot, "//notifyURL", false); + pugi::xml_node notifyBody = getNode(notifyRoot, "//body", false); + + if (notifyUrl == "") + { + GOUROU_LOG(INFO, "No notify URL"); + return; + } + + if (!notifyBody) + { + GOUROU_LOG(INFO, "No notify body"); + return; + } + + pugi::xml_document notifyReq; + buildNotifyReq(notifyReq, notifyBody); + + sendRequest(notifyReq, notifyUrl); + } + + void DRMProcessor::notifyServer(pugi::xml_document& fulfillReply) + { + pugi::xpath_node_set notifySet = fulfillReply.select_nodes("//notify"); + + if (notifySet.empty()) + { + GOUROU_LOG(DEBUG, "No notify request"); + return; + } + + for (pugi::xpath_node_set::const_iterator it = notifySet.begin(); it != notifySet.end(); ++it) + { + pugi::xml_node notifyRoot = it->node(); + notifyServer(notifyRoot); + } + } + ByteArray DRMProcessor::encryptWithDeviceKey(const unsigned char* data, unsigned int len) { const unsigned char* deviceKey = device->getDeviceKey(); From 40dcb7a0416e0a7ef863c98702c2bc82d701632f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9gory=20Soutad=C3=A9?= Date: Wed, 6 Sep 2023 21:09:42 +0200 Subject: [PATCH 60/94] Add --no-notify option to utils --- utils/acsmdownloader.cpp | 10 ++++++++-- utils/adept_loan_mgt.cpp | 8 +++++++- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/utils/acsmdownloader.cpp b/utils/acsmdownloader.cpp index b84efc5..4d23748 100644 --- a/utils/acsmdownloader.cpp +++ b/utils/acsmdownloader.cpp @@ -46,6 +46,7 @@ static bool exportPrivateKey = false; static const char* outputFile = 0; static const char* outputDir = 0; static bool resume = false; +static bool notify = true; class ACSMDownloader @@ -82,7 +83,7 @@ public: } else { - gourou::FulfillmentItem* item = processor.fulfill(acsmFile); + gourou::FulfillmentItem* item = processor.fulfill(acsmFile, notify); std::string filename; if (!outputFile) @@ -190,6 +191,7 @@ static void usage(const char* cmd) std::cout << " " << "-f|--acsm-file" << "\t" << "Backward compatibility: ACSM request file for epub download" << std::endl; std::cout << " " << "-e|--export-private-key"<< "\t" << "Export private key in DER format" << std::endl; std::cout << " " << "-r|--resume" << "\t\t" << "Try to resume download (in case of previous failure)" << std::endl; + std::cout << " " << "-N|--no-notify" << "\t\t" << "Don't notify server, even if requested" << std::endl; std::cout << " " << "-v|--verbose" << "\t\t" << "Increase verbosity, can be set multiple times" << std::endl; std::cout << " " << "-V|--version" << "\t\t" << "Display libgourou version" << std::endl; std::cout << " " << "-h|--help" << "\t\t" << "This help" << std::endl; @@ -232,13 +234,14 @@ int main(int argc, char** argv) {"acsm-file", required_argument, 0, 'f' }, {"export-private-key",no_argument, 0, 'e' }, {"resume", no_argument, 0, 'r' }, + {"no-notify", no_argument, 0, 'N' }, {"verbose", no_argument, 0, 'v' }, {"version", no_argument, 0, 'V' }, {"help", no_argument, 0, 'h' }, {0, 0, 0, 0 } }; - c = getopt_long(argc, argv, "D:d:a:k:O:o:f:ervVh", + c = getopt_long(argc, argv, "D:d:a:k:O:o:f:erNvVh", long_options, &option_index); if (c == -1) break; @@ -276,6 +279,9 @@ int main(int argc, char** argv) case 'r': resume = true; break; + case 'N': + notify = false; + break; case 'v': verbose++; break; diff --git a/utils/adept_loan_mgt.cpp b/utils/adept_loan_mgt.cpp index 34f328f..2bc944e 100644 --- a/utils/adept_loan_mgt.cpp +++ b/utils/adept_loan_mgt.cpp @@ -52,6 +52,7 @@ static const char* devicekeyFile = "devicesalt"; static bool list = false; static const char* returnID = 0; static const char* deleteID = 0; +static bool notify = true; struct Loan { @@ -296,7 +297,7 @@ private: return; } - processor.returnLoan(loan->id, loan->operatorURL); + processor.returnLoan(loan->id, loan->operatorURL, notify); deleteID = returnID; if (deleteLoan(false)) @@ -342,6 +343,7 @@ static void usage(const char* cmd) std::cout << " " << "-l|--list" << "\t\t" << "List all loaned books" << std::endl; std::cout << " " << "-r|--return" << "\t\t" << "Return a loaned book" << std::endl; std::cout << " " << "-d|--delete" << "\t\t" << "Delete a loan entry without returning it" << std::endl; + std::cout << " " << "-N|--no-notify" << "\t\t" << "Don't notify server, even if requested" << std::endl; std::cout << " " << "-v|--verbose" << "\t\t" << "Increase verbosity, can be set multiple times" << std::endl; std::cout << " " << "-V|--version" << "\t\t" << "Display libgourou version" << std::endl; std::cout << " " << "-h|--help" << "\t\t" << "This help" << std::endl; @@ -375,6 +377,7 @@ int main(int argc, char** argv) {"list", no_argument, 0, 'l' }, {"return", no_argument, 0, 'r' }, {"delete", no_argument, 0, 'd' }, + {"no-notify", no_argument, 0, 'N' }, {"verbose", no_argument, 0, 'v' }, {"version", no_argument, 0, 'V' }, {"help", no_argument, 0, 'h' }, @@ -402,6 +405,9 @@ int main(int argc, char** argv) deleteID = optarg; actions++; break; + case 'N': + notify = false; + break; case 'v': verbose++; break; From 29d298b37306bbcc107f223a827e945c6c336da5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9gory=20Soutad=C3=A9?= Date: Wed, 6 Sep 2023 21:09:53 +0200 Subject: [PATCH 61/94] Update libgourou version --- include/libgourou.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/libgourou.h b/include/libgourou.h index 37cc68e..fe1a112 100644 --- a/include/libgourou.h +++ b/include/libgourou.h @@ -37,7 +37,7 @@ #define ACS_SERVER "http://adeactivate.adobe.com/adept" #endif -#define LIBGOUROU_VERSION "0.8.2" +#define LIBGOUROU_VERSION "0.8.3" namespace gourou { From 92a67312bd8f4d4461637c5cf7744ce24b7250c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9gory=20Soutad=C3=A9?= Date: Wed, 6 Sep 2023 21:21:43 +0200 Subject: [PATCH 62/94] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index c986e3b..3f29f38 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ 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). +A reference implementation using cURL, OpenSSL and libzip is provided (in _utils_ directory). Main fucntions to use from gourou::DRMProcessor are : From fd38e84da66e9f57bdac27f24db2f238d5d6cbba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9gory=20Soutad=C3=A9?= Date: Sat, 6 Jan 2024 09:22:11 +0100 Subject: [PATCH 63/94] Fix for Android for adept_loan_mgt.cpp (strptime format) --- utils/adept_loan_mgt.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/utils/adept_loan_mgt.cpp b/utils/adept_loan_mgt.cpp index 2bc944e..22ed6db 100644 --- a/utils/adept_loan_mgt.cpp +++ b/utils/adept_loan_mgt.cpp @@ -183,8 +183,13 @@ private: loan->bookName = node.first_child().value(); struct tm tm; +#ifdef __ANDROID__ + res = strptime(loan->validity.c_str(), "%Y-%m-%dT%H:%M:%S%z", &tm); +#else res = strptime(loan->validity.c_str(), "%Y-%m-%dT%H:%M:%S%Z", &tm); - if (*res == 0) +#endif + + if (res != NULL && *res == 0) { if (mktime(&tm) <= time(NULL)) loan->validity = " (Expired)"; From 69865e005bc38020b072d893a1547ce5ed92c0dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9gory=20Soutad=C3=A9?= Date: Sat, 6 Jan 2024 09:23:03 +0100 Subject: [PATCH 64/94] Register operatorURL only when certificate from operator is fetched --- src/libgourou.cpp | 44 +++++++++++++++++++------------------------- 1 file changed, 19 insertions(+), 25 deletions(-) diff --git a/src/libgourou.cpp b/src/libgourou.cpp index 2bbc201..9d5e3f2 100644 --- a/src/libgourou.cpp +++ b/src/libgourou.cpp @@ -398,31 +398,7 @@ namespace gourou } } - doOperatorAuth(operatorURL); - - // Add new operatorURL to list - pugi::xml_document activationDoc; - user->readActivation(activationDoc); - - pugi::xml_node root; - pugi::xpath_node xpathRes = activationDoc.select_node("//adept:operatorURLList"); - - // Create adept:operatorURLList if it doesn't exists - if (!xpathRes) - { - xpathRes = activationDoc.select_node("/activationInfo"); - root = xpathRes.node(); - root = root.append_child("adept:operatorURLList"); - root.append_attribute("xmlns:adept") = ADOBE_ADEPT_NS; - - appendTextElem(root, "adept:user", user->getUUID()); - } - else - root = xpathRes.node(); - - appendTextElem(root, "adept:operatorURL", operatorURL); - - user->updateActivationFile(activationDoc); + doOperatorAuth(operatorURL); } void DRMProcessor::buildFulfillRequest(pugi::xml_document& acsmDoc, pugi::xml_document& fulfillReq) @@ -492,6 +468,24 @@ namespace gourou appendTextElem(root, "adept:licenseURL", licenseURL); appendTextElem(root, "adept:certificate", certificate); + // Add new operatorURL to list + xpathRes = activationDoc.select_node("//adept:operatorURLList"); + + // Create adept:operatorURLList if it doesn't exists + if (!xpathRes) + { + xpathRes = activationDoc.select_node("/activationInfo"); + root = xpathRes.node(); + root = root.append_child("adept:operatorURLList"); + root.append_attribute("xmlns:adept") = ADOBE_ADEPT_NS; + + appendTextElem(root, "adept:user", user->getUUID()); + } + else + root = xpathRes.node(); + + appendTextElem(root, "adept:operatorURL", operatorURL); + user->updateActivationFile(activationDoc); } From e05639c09d7d5a0c26ee20857fef1d53bd0ca1cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9gory=20Soutad=C3=A9?= Date: Sat, 6 Jan 2024 09:23:50 +0100 Subject: [PATCH 65/94] Support HTTP error codes != 200 (exception) and cookies in drmprocessorclientimpl --- include/libgourou_common.h | 1 + utils/drmprocessorclientimpl.cpp | 20 +++++++++++++++++++- utils/drmprocessorclientimpl.h | 2 ++ 3 files changed, 22 insertions(+), 1 deletion(-) diff --git a/include/libgourou_common.h b/include/libgourou_common.h index a6f2a91..72f3915 100644 --- a/include/libgourou_common.h +++ b/include/libgourou_common.h @@ -120,6 +120,7 @@ namespace gourou CLIENT_OSSL_ERROR, CLIENT_CRYPT_ERROR, CLIENT_DIGEST_ERROR, + CLIENT_HTTP_ERROR }; enum DRM_REMOVAL_ERROR { diff --git a/utils/drmprocessorclientimpl.cpp b/utils/drmprocessorclientimpl.cpp index d451834..1019eaf 100644 --- a/utils/drmprocessorclientimpl.cpp +++ b/utils/drmprocessorclientimpl.cpp @@ -30,6 +30,7 @@ #include #include #include +#include #define OPENSSL_NO_DEPRECATED 1 @@ -60,6 +61,14 @@ DRMProcessorClientImpl::DRMProcessorClientImpl(): if (!deflt) EXCEPTION(gourou::CLIENT_OSSL_ERROR, "Error, OpenSSL default provider not available"); #endif + +#ifdef WIN32 + strcpy(cookiejar, "C:\\temp\\libgourou_cookie_jar_XXXXXX"); +#else + strcpy(cookiejar, "/tmp/libgourou_cookie_jar_XXXXXX"); +#endif + + mkstemp(cookiejar); } DRMProcessorClientImpl::~DRMProcessorClientImpl() @@ -71,6 +80,8 @@ DRMProcessorClientImpl::~DRMProcessorClientImpl() if (deflt) OSSL_PROVIDER_unload(deflt); #endif + + unlink(cookiejar); } /* Digest interface */ @@ -227,6 +238,7 @@ std::string DRMProcessorClientImpl::sendHTTPRequest(const std::string& URL, cons } curl_easy_setopt(curl, CURLOPT_HTTPHEADER, list); + curl_easy_setopt(curl, CURLOPT_COOKIEJAR, cookiejar); if (POSTData.size()) { @@ -290,7 +302,13 @@ std::string DRMProcessorClientImpl::sendHTTPRequest(const std::string& URL, cons if (res != CURLE_OK) EXCEPTION(gourou::CLIENT_NETWORK_ERROR, "Error " << curl_easy_strerror(res)); - + + long http_code = 400; + curl_easy_getinfo (curl, CURLINFO_RESPONSE_CODE, &http_code); + + if (http_code >= 400) + EXCEPTION(gourou::CLIENT_HTTP_ERROR, "HTTP Error code " << http_code); + if ((downloadedBytes >= DISPLAY_THRESHOLD || replyData.size() >= DISPLAY_THRESHOLD) && gourou::logLevel >= gourou::LG_LOG_WARN) std::cout << std::endl; diff --git a/utils/drmprocessorclientimpl.h b/utils/drmprocessorclientimpl.h index c2198c6..cec1a22 100644 --- a/utils/drmprocessorclientimpl.h +++ b/utils/drmprocessorclientimpl.h @@ -136,6 +136,8 @@ private: #else void *legacy, *deflt; #endif + + char cookiejar[64]; }; #endif From ef8c2644cad6457eb4d795b0963f0faba34dbc96 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9gory=20Soutad=C3=A9?= Date: Tue, 16 Jan 2024 11:09:31 +0100 Subject: [PATCH 66/94] Update version --- include/libgourou.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/libgourou.h b/include/libgourou.h index fe1a112..b86c449 100644 --- a/include/libgourou.h +++ b/include/libgourou.h @@ -37,7 +37,7 @@ #define ACS_SERVER "http://adeactivate.adobe.com/adept" #endif -#define LIBGOUROU_VERSION "0.8.3" +#define LIBGOUROU_VERSION "0.8.4" namespace gourou { From 68bf982b6f5b1d0cc1a35f313bd244f162ffdc97 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9gory=20Soutad=C3=A9?= Date: Wed, 24 Jan 2024 19:13:22 +0100 Subject: [PATCH 67/94] Fix use after free in DRMProcessorClientImpl::sendHTTPRequest() --- include/libgourou.h | 2 +- utils/drmprocessorclientimpl.cpp | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/include/libgourou.h b/include/libgourou.h index b86c449..cb24a87 100644 --- a/include/libgourou.h +++ b/include/libgourou.h @@ -37,7 +37,7 @@ #define ACS_SERVER "http://adeactivate.adobe.com/adept" #endif -#define LIBGOUROU_VERSION "0.8.4" +#define LIBGOUROU_VERSION "0.8.5" namespace gourou { diff --git a/utils/drmprocessorclientimpl.cpp b/utils/drmprocessorclientimpl.cpp index 1019eaf..58a9f97 100644 --- a/utils/drmprocessorclientimpl.cpp +++ b/utils/drmprocessorclientimpl.cpp @@ -298,14 +298,15 @@ std::string DRMProcessorClientImpl::sendHTTPRequest(const std::string& URL, cons } curl_slist_free_all(list); + + long http_code = 400; + curl_easy_getinfo (curl, CURLINFO_RESPONSE_CODE, &http_code); + curl_easy_cleanup(curl); if (res != CURLE_OK) EXCEPTION(gourou::CLIENT_NETWORK_ERROR, "Error " << curl_easy_strerror(res)); - long http_code = 400; - curl_easy_getinfo (curl, CURLINFO_RESPONSE_CODE, &http_code); - if (http_code >= 400) EXCEPTION(gourou::CLIENT_HTTP_ERROR, "HTTP Error code " << http_code); From 86a79cc38103c114b15e8da69679d52fe5dc7fd2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9gory=20Soutad=C3=A9?= Date: Tue, 19 Mar 2024 14:54:59 +0100 Subject: [PATCH 68/94] Remove whole EBX objects for PDF when removing DRM --- src/libgourou.cpp | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/libgourou.cpp b/src/libgourou.cpp index 9d5e3f2..291bdd0 100644 --- a/src/libgourou.cpp +++ b/src/libgourou.cpp @@ -1317,6 +1317,7 @@ namespace gourou std::vector objects = parser.objects(); std::vector::iterator it; std::vector::reverse_iterator rIt; + std::vector ebxObjects; unsigned char decryptedKey[16]; int ebxId; @@ -1327,7 +1328,7 @@ namespace gourou { EBXHandlerFound = true; uPDFParser::Object* ebx = *rIt; - + ebxVersion = (uPDFParser::Integer*)(*ebx)["V"]; if (ebxVersion->value() != 4) { @@ -1338,7 +1339,7 @@ namespace gourou { EXCEPTION(DRM_ERR_ENCRYPTION_KEY, "No ADEPT_LICENSE found"); } - + uPDFParser::String* licenseObject = (uPDFParser::String*)(*ebx)["ADEPT_LICENSE"]; std::string value = licenseObject->value(); @@ -1375,7 +1376,7 @@ namespace gourou if (object->objectId() == ebxId) { - // object->deleteKey("Filter"); + ebxObjects.push_back(object); continue; } @@ -1485,6 +1486,9 @@ namespace gourou } } + for(it = ebxObjects.begin(); it != ebxObjects.end(); it++) + parser.removeObject(*it); + uPDFParser::Object& trailer = parser.getTrailer(); trailer.deleteKey("Encrypt"); From 0d77cf55e1b0de1e8238d12c1e4015863135485d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9gory=20Soutad=C3=A9?= Date: Thu, 28 Mar 2024 21:58:07 +0100 Subject: [PATCH 69/94] Update version --- include/libgourou.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/libgourou.h b/include/libgourou.h index cb24a87..31e62bf 100644 --- a/include/libgourou.h +++ b/include/libgourou.h @@ -37,7 +37,7 @@ #define ACS_SERVER "http://adeactivate.adobe.com/adept" #endif -#define LIBGOUROU_VERSION "0.8.5" +#define LIBGOUROU_VERSION "0.8.6" namespace gourou { From f60abf04d828558a36249bd37b45c78f04158e8f Mon Sep 17 00:00:00 2001 From: nicokosi Date: Thu, 11 Apr 2024 06:05:23 +0200 Subject: [PATCH 70/94] Fix typos in README.md --- README.md | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index 3f29f38..5a84bfc 100644 --- a/README.md +++ b/README.md @@ -1,16 +1,16 @@ Introduction ------------ -libgourou is a free implementation of Adobe's ADEPT protocol used to add DRM on ePub/PDF files. It overcome the lacks of Adobe support for Linux platforms. +libgourou is a free implementation of Adobe's ADEPT protocol used to add DRM on ePub/PDF files. It overcomes the lack of Adobe support for Linux platforms. Architecture ------------ -Like RMSDK, libgourou has a client/server scheme. All platform specific functions (crypto, network...) has to be implemented in a client class (that derives from DRMProcessorClient) while server implements ADEPT protocol. +Like RMSDK, libgourou has a client/server scheme. All platform specific functions (crypto, network...) have to be implemented in a client class (that derives from DRMProcessorClient) while server implements ADEPT protocol. A reference implementation using cURL, OpenSSL and libzip is provided (in _utils_ directory). -Main fucntions to use from gourou::DRMProcessor are : +Main functions to use from gourou::DRMProcessor are: * Get an ePub from an ACSM file : _fulfill()_ and _download()_ * Create a new device : _createDRMProcessor()_ @@ -18,32 +18,32 @@ Main fucntions to use from gourou::DRMProcessor are : * Remove DRM : _removeDRM()_ * Return loaned book : _returnLoan()_ -You can import configuration from (at least) : +You can import configuration from (at least): * Kobo device : .adept/device.xml, .adept/devicesalt and .adept/activation.xml * Bookeen device : .adobe-digital-editions/device.xml, root/devkey.bin and .adobe-digital-editions/activation.xml -Or create a new one. Be careful : there is a limited number of devices that can be created bye one account. +Or create a new one. Be careful: there is a limited number of devices that can be created by one account. -ePub are encrypted using a shared key : one account / multiple devices, so you can create and register a device into your computer and read downloaded (and encrypted) ePub file with your eReader configured using the same AdobeID account. +ePub are encrypted using a shared key: one account / multiple devices, so you can create and register a device into your computer and read downloaded (and encrypted) ePub file with your eReader configured using the same AdobeID account. -For those who wants to remove DRM without adept_remove, you can export your private key and import it within [Calibre](https://calibre-ebook.com/) an its DeDRM plugin. +For those who want to remove DRM without adept_remove, you can export your private key and import it within [Calibre](https://calibre-ebook.com/) an its DeDRM plugin. Dependencies ------------ -For libgourou : +For libgourou: _externals_ : * libpugixml -_internals_ : +_internals_: * uPDFParser -For utils : +For utils: * libcurl * OpenSSL @@ -52,7 +52,7 @@ For utils : Internal libraries are automatically fetched and statically compiled during the first run. -When you update libgourou's repository, **don't forget to update internal libraries** with : +When you update libgourou's repository, **don't forget to update internal libraries** with: make update_lib @@ -92,31 +92,31 @@ You can optionaly specify your .adept directory export ADEPT_DIR=/home/XXX -Then, use utils as following : +Then, use utils as following: -You can import configuration from your eReader or create a new one with _utils/adept\_activate_ : +You can import configuration from your eReader or create a new one with _utils/adept\_activate_: ./utils/adept_activate -u Then a _/home//.config/adept_ directory is created with all configuration file -To download an ePub/PDF : +To download an ePub/PDF: ./utils/acsmdownloader -To export your private key (for DeDRM software) : +To export your private key (for DeDRM software): ./utils/acsmdownloader --export-private-key [-o adobekey_1.der] -To remove ADEPT DRM : +To remove ADEPT DRM: ./utils/adept_remove -To list loaned books : +To list loaned books: ./utils/adept_loan_mgt [-l] -To return a loaned book : +To return a loaned book: ./utils/adept_loan_mgt -r From 81faf1f9bef4d27d8659f2f16b9c65df227ee3d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9gory=20Soutad=C3=A9?= Date: Sun, 14 Apr 2024 21:19:56 +0200 Subject: [PATCH 71/94] adept_loan_mgt: manage cases were name is empty --- utils/adept_loan_mgt.cpp | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/utils/adept_loan_mgt.cpp b/utils/adept_loan_mgt.cpp index 22ed6db..ab52d3f 100644 --- a/utils/adept_loan_mgt.cpp +++ b/utils/adept_loan_mgt.cpp @@ -229,7 +229,12 @@ private: maxSizeBookName = loan->bookName.size(); } - if (maxSizeBookName > MAX_SIZE_BOOK_NAME) + /* Manage empty names */ + if (maxSizeBookName == 0) + maxSizeBookName = sizeof("No name ")-1; + else if (maxSizeBookName < 4) + maxSizeBookName = 4; + else if (maxSizeBookName > MAX_SIZE_BOOK_NAME) maxSizeBookName = MAX_SIZE_BOOK_NAME; else if ((maxSizeBookName % 2)) maxSizeBookName++; @@ -276,7 +281,9 @@ private: std::cout << kv.first; std::cout << " "; - if (loan->bookName.size() > MAX_SIZE_BOOK_NAME) + if (loan->bookName.size() == 0) + bookName = std::string("No name "); + else if (loan->bookName.size() > MAX_SIZE_BOOK_NAME) bookName = std::string(loan->bookName.c_str(), MAX_SIZE_BOOK_NAME); else bookName = loan->bookName; From 68bf48df27e40b6331369c572e8ee7a3f5892391 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9gory=20Soutad=C3=A9?= Date: Tue, 27 Aug 2024 21:48:39 +0200 Subject: [PATCH 72/94] Update README.md --- README.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/README.md b/README.md index 5a84bfc..abe14a0 100644 --- a/README.md +++ b/README.md @@ -150,3 +150,16 @@ Special thanks * _Jens_ for all test samples and utils testing * _Milian_ for debug & code * _Berwyn H_ for all test samples, feedbacks, patches and kind donation + + +Donation +-------- + +https://www.paypal.com/donate/?hosted_button_id=JD3U6XMZCPHKN + + +Donators +-------- + + * _Berwyn H_ + * _bwitt_ From ce2cf4192a0818a04c786b55e78eef5600a30a29 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9gory=20Soutad=C3=A9?= Date: Sat, 31 Aug 2024 08:27:53 +0200 Subject: [PATCH 73/94] Update README.md --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index abe14a0..59ce35f 100644 --- a/README.md +++ b/README.md @@ -163,3 +163,4 @@ Donators * _Berwyn H_ * _bwitt_ + * _Ismail_ From 204500117d6cf17de3f564879d4809ce45521145 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9gory=20Soutad=C3=A9?= Date: Thu, 19 Sep 2024 08:41:15 +0200 Subject: [PATCH 74/94] Typo fix --- scripts/update_lib.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/update_lib.sh b/scripts/update_lib.sh index 6c1ede0..1290b0c 100755 --- a/scripts/update_lib.sh +++ b/scripts/update_lib.sh @@ -3,7 +3,7 @@ if [ ! -d lib/updfparser ] ; then echo "Some libraries are missing" echo "You must run this script at the top of libgourou working direcotry." - echo "./lib/setup.sh must be called first (make all)" + echo "./scripts/setup.sh must be called first (make all)" exit 1 fi From d9a920b062bd2274447b88fc6bccd5e5d12daf09 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ismail=20D=C3=B6nmez?= Date: Thu, 19 Sep 2024 21:35:05 +0200 Subject: [PATCH 75/94] Use $HOME variable if it exists MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Ismail Dönmez --- src/libgourou.cpp | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/src/libgourou.cpp b/src/libgourou.cpp index 291bdd0..aded1e8 100644 --- a/src/libgourou.cpp +++ b/src/libgourou.cpp @@ -859,14 +859,21 @@ namespace gourou std::string DRMProcessor::getDefaultAdeptDir(void) { #ifndef DEFAULT_ADEPT_DIR - const char* user = getenv("USER"); + const char* home = getenv("HOME"); + + if (home) + return home + std::string("/.config/adept/"); + else + { + const char* user = getenv("USER"); - if (user && user[0]) - { - return std::string("/home/") + user + std::string("/.config/adept/"); - } - else - return LOCAL_ADEPT_DIR; + if (user && user[0]) + { + return std::string("/home/") + user + std::string("/.config/adept/"); + } + else + return LOCAL_ADEPT_DIR; + } #else return DEFAULT_ADEPT_DIR "/"; #endif From 956bad306832fb2596908a7b0e230255b8fd5848 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9gory=20Soutad=C3=A9?= Date: Tue, 31 Dec 2024 13:21:14 +0100 Subject: [PATCH 76/94] Update README.md --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 59ce35f..b2e2113 100644 --- a/README.md +++ b/README.md @@ -164,3 +164,4 @@ Donators * _Berwyn H_ * _bwitt_ * _Ismail_ + * _Radon_ From 98b531a232a451d3bc9d7d318e5710c9c3854941 Mon Sep 17 00:00:00 2001 From: Philipp Pagel Date: Tue, 11 Feb 2025 18:12:36 +0100 Subject: [PATCH 77/94] Fix setup.sh fix path for updfparser --- scripts/setup.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/setup.sh b/scripts/setup.sh index 5c25ff9..a077138 100755 --- a/scripts/setup.sh +++ b/scripts/setup.sh @@ -2,7 +2,7 @@ # uPDFParser if [ ! -d lib/updfparser ] ; then - git clone git://soutade.fr/updfparser.git lib/updfparser + git clone git://soutade.fr/soutade/updfparser.git lib/updfparser pushd lib/updfparser make BUILD_STATIC=1 BUILD_SHARED=0 popd From 469d378f9a097dca5f4a44e2c54192cef39b3a96 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9gory=20Soutad=C3=A9?= Date: Sun, 16 Feb 2025 21:32:51 +0100 Subject: [PATCH 78/94] Update version to 0.8.7 --- include/libgourou.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/libgourou.h b/include/libgourou.h index 31e62bf..db366be 100644 --- a/include/libgourou.h +++ b/include/libgourou.h @@ -37,7 +37,7 @@ #define ACS_SERVER "http://adeactivate.adobe.com/adept" #endif -#define LIBGOUROU_VERSION "0.8.6" +#define LIBGOUROU_VERSION "0.8.7" namespace gourou { From d3c90f03bba187292c747080592840123f94f285 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9gory=20Soutad=C3=A9?= Date: Mon, 21 Apr 2025 08:59:51 +0200 Subject: [PATCH 79/94] Update README.md --- README.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index b2e2113..5f0e866 100644 --- a/README.md +++ b/README.md @@ -46,12 +46,14 @@ _internals_: For utils: * libcurl - * OpenSSL + * openssl * libzip * libpugixml -Internal libraries are automatically fetched and statically compiled during the first run. +External & utils dependencies has to be installed by your package manager (_apt_ for example). +Use _-dev_ flavours to get needed headers. +Internal libraries are automatically fetched and statically compiled during the first compilation. When you update libgourou's repository, **don't forget to update internal libraries** with: make update_lib From e0e2bc743016413b85f0aa1ba32a0d0346e1a828 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9gory=20Soutad=C3=A9?= Date: Mon, 25 Aug 2025 20:36:20 +0200 Subject: [PATCH 80/94] Update uPDFParser git address --- scripts/setup.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/setup.sh b/scripts/setup.sh index a077138..271361b 100755 --- a/scripts/setup.sh +++ b/scripts/setup.sh @@ -2,7 +2,7 @@ # uPDFParser if [ ! -d lib/updfparser ] ; then - git clone git://soutade.fr/soutade/updfparser.git lib/updfparser + git clone https://forge.soutade.fr/soutade/uPDFParser.git lib/updfparser pushd lib/updfparser make BUILD_STATIC=1 BUILD_SHARED=0 popd From 28aefba6d658fe6cf30b4fb21a04b31b224e0bf2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9gory=20Soutad=C3=A9?= Date: Mon, 17 Nov 2025 22:29:35 +0100 Subject: [PATCH 81/94] Update README.md --- README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/README.md b/README.md index 5f0e866..41c1dbe 100644 --- a/README.md +++ b/README.md @@ -126,6 +126,12 @@ To return a loaned book: You can get utils full options description with -h or --help switch +Binary packages +--------------- + +Compiled version (and AppImage) of libgourou and utils are available in [Release page](https://forge.soutade.fr/soutade/libgourou/releases) + + Docker ------ From db3e2179a936fb40dde958d258ce551f4f8ecbb6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9gory=20Soutad=C3=A9?= Date: Mon, 12 Jan 2026 21:47:34 +0100 Subject: [PATCH 82/94] Add PKCS1 Type 2 padding for Signing request --- utils/drmprocessorclientimpl.cpp | 71 +++++++++++++++++++++++++++----- utils/drmprocessorclientimpl.h | 2 + 2 files changed, 62 insertions(+), 11 deletions(-) diff --git a/utils/drmprocessorclientimpl.cpp b/utils/drmprocessorclientimpl.cpp index 58a9f97..a4b5038 100644 --- a/utils/drmprocessorclientimpl.cpp +++ b/utils/drmprocessorclientimpl.cpp @@ -49,17 +49,27 @@ #include #include "drmprocessorclientimpl.h" +static int error_cb(const char *str, size_t len, void *u) +{ + std::cout << str << std::endl; + return 0; +} + DRMProcessorClientImpl::DRMProcessorClientImpl(): legacy(0), deflt(0) { #if OPENSSL_VERSION_MAJOR >= 3 legacy = OSSL_PROVIDER_load(NULL, "legacy"); if (!legacy) + { + ERR_print_errors_cb(error_cb, NULL); EXCEPTION(gourou::CLIENT_OSSL_ERROR, "Error, OpenSSL legacy provider not available"); + } deflt = OSSL_PROVIDER_load(NULL, "default"); if (!deflt) EXCEPTION(gourou::CLIENT_OSSL_ERROR, "Error, OpenSSL default provider not available"); + OSSL_PROVIDER_load(NULL, "base"); #endif #ifdef WIN32 @@ -333,11 +343,39 @@ void DRMProcessorClientImpl::padWithPKCS1(unsigned char* out, unsigned int outLe 0x00 0x01 0xff * n 0x00 dataIn */ - memset(out, 0xFF, outLength); + memset(out, 0xFF, outLength - inLength - 1); out[0] = 0x0; out[1] = 0x1; out[outLength - inLength - 1] = 0x00; + + memcpy(&out[outLength - inLength], in, inLength); +} + +void DRMProcessorClientImpl::padWithPKCS1Type2(unsigned char* out, unsigned int outLength, + const unsigned char* in, unsigned int inLength) +{ + if (outLength < (inLength + 3)) + EXCEPTION(gourou::CLIENT_RSA_ERROR, "Not enough space for PKCS1 padding"); + + /* + PKCS1v5 type 2 Padding is : + 0x00 0x02 0xXX * n 0x00 dataIn + XX is random non zero data + */ + + RAND_bytes(&out[2], outLength - inLength - 1); + + for(unsigned int i=2; i= 3 OSSL_PROVIDER *legacy, *deflt; From 98c511d0ca1c686626af0ef9aaa8038f1cf2430b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9gory=20Soutad=C3=A9?= Date: Mon, 12 Jan 2026 21:48:41 +0100 Subject: [PATCH 83/94] Fix mkstemp unused result warning --- utils/drmprocessorclientimpl.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/utils/drmprocessorclientimpl.cpp b/utils/drmprocessorclientimpl.cpp index a4b5038..5f1950a 100644 --- a/utils/drmprocessorclientimpl.cpp +++ b/utils/drmprocessorclientimpl.cpp @@ -78,7 +78,13 @@ DRMProcessorClientImpl::DRMProcessorClientImpl(): strcpy(cookiejar, "/tmp/libgourou_cookie_jar_XXXXXX"); #endif - mkstemp(cookiejar); + int fd = mkstemp(cookiejar); + if (fd >= 0) + close(fd); + else + { + EXCEPTION(gourou::CLIENT_FILE_ERROR, "mkstemp error"); + } } DRMProcessorClientImpl::~DRMProcessorClientImpl() From 724961566c469f411a604ba52258891cf5d870a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9gory=20Soutad=C3=A9?= Date: Sun, 18 Jan 2026 14:35:18 +0100 Subject: [PATCH 84/94] Update version --- include/libgourou.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/libgourou.h b/include/libgourou.h index db366be..cdd3cf4 100644 --- a/include/libgourou.h +++ b/include/libgourou.h @@ -37,7 +37,7 @@ #define ACS_SERVER "http://adeactivate.adobe.com/adept" #endif -#define LIBGOUROU_VERSION "0.8.7" +#define LIBGOUROU_VERSION "0.8.8" namespace gourou { From 8061681705c5e4db2ce42eec0f612dcd5a72a003 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9gory=20Soutad=C3=A9?= Date: Sun, 18 Jan 2026 15:22:02 +0100 Subject: [PATCH 85/94] Add a warning when ACSM file is expired --- src/libgourou.cpp | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/src/libgourou.cpp b/src/libgourou.cpp index aded1e8..4a1da8a 100644 --- a/src/libgourou.cpp +++ b/src/libgourou.cpp @@ -21,6 +21,7 @@ #include #include #include +#include #include @@ -70,6 +71,15 @@ namespace gourou if (user) delete user; } + // function to parse a date or time string. + // https://www.geeksforgeeks.org/cpp/date-and-time-parsing-in-cpp/ + static time_t parseDateTime(const char* datetimeString, const char* format) + { + struct tm tmStruct; + strptime(datetimeString, format, &tmStruct); + return mktime(&tmStruct); + } + DRMProcessor* DRMProcessor::createDRMProcessor(DRMProcessorClient* client, bool randomSerial, std::string dirName, const std::string& hobbes, const std::string& ACSServer) { @@ -488,7 +498,7 @@ namespace gourou user->updateActivationFile(activationDoc); } - + FulfillmentItem* DRMProcessor::fulfill(const std::string& ACSMFile, bool notify) { if (!user->getPKCS12().length()) @@ -508,6 +518,16 @@ namespace gourou GOUROU_LOG(INFO, "Fulfill " << ACSMFile); + std::string expiration = extractTextElem(rootNode, "expiration", false); + + if (expiration != "") + { + time_t expirationTime = parseDateTime(expiration.c_str(), "%Y-%m-%dT%H:%M:%S"); + + if (time(NULL) > expirationTime) + GOUROU_LOG(WARN, "WARNING: ACSM file expired (" << expiration << "). It may not work"); + } + // Build req file pugi::xml_document fulfillReq; @@ -569,7 +589,7 @@ namespace gourou pugi::xml_document fulfillReply; fulfillReply.load_string((const char*)replyData.data()); - + std::string licenseURL = extractTextElem(fulfillReply, "//licenseToken/licenseURL"); fetchLicenseServiceCertificate(licenseURL, operatorURL); From 8f0341d0bdadb7b8281c4dba89ac8175fa9858a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9gory=20Soutad=C3=A9?= Date: Sun, 18 Jan 2026 15:44:20 +0100 Subject: [PATCH 86/94] Some code formating --- include/Base64.h | 174 +-- include/bytearray.h | 238 ++-- include/device.h | 80 +- include/drmprocessorclient.h | 644 ++++----- include/fulfillment_item.h | 76 +- include/libgourou.h | 348 ++--- include/libgourou_common.h | 500 +++---- include/libgourou_log.h | 10 +- include/loan_token.h | 24 +- include/user.h | 122 +- src/bytearray.cpp | 290 ++-- src/device.cpp | 310 ++-- src/fulfillment_item.cpp | 142 +- src/libgourou.cpp | 2260 +++++++++++++++--------------- src/loan_token.cpp | 66 +- src/user.cpp | 282 ++-- utils/Makefile | 5 +- utils/acsmdownloader.cpp | 420 +++--- utils/adept_activate.cpp | 272 ++-- utils/adept_loan_mgt.cpp | 608 ++++---- utils/adept_remove.cpp | 352 ++--- utils/drmprocessorclientimpl.cpp | 558 ++++---- utils/drmprocessorclientimpl.h | 94 +- utils/launcher.cpp | 8 +- utils/utils_common.cpp | 80 +- utils/utils_common.h | 14 +- 26 files changed, 3989 insertions(+), 3988 deletions(-) diff --git a/include/Base64.h b/include/Base64.h index 548b9f1..68f0341 100644 --- a/include/Base64.h +++ b/include/Base64.h @@ -30,104 +30,104 @@ namespace macaron { -class Base64 { - public: + class Base64 { + public: - static std::string Encode(const std::string data) { - static + static std::string Encode(const std::string data) { + static #if __STDC_VERSION__ >= 201112L - constexpr + constexpr #endif /* __STDC_VERSION__ >= 201112L */ - char sEncodingTable[] = { - 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', - 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', - 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', - 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', - 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', - 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', - 'w', 'x', 'y', 'z', '0', '1', '2', '3', - '4', '5', '6', '7', '8', '9', '+', '/' - }; + char sEncodingTable[] = { + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', + 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', + 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', + 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', + 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', + 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', + 'w', 'x', 'y', 'z', '0', '1', '2', '3', + '4', '5', '6', '7', '8', '9', '+', '/' + }; - size_t in_len = data.size(); - size_t out_len = 4 * ((in_len + 2) / 3); - std::string ret(out_len, '\0'); - size_t i; - char *p = const_cast(ret.c_str()); + size_t in_len = data.size(); + size_t out_len = 4 * ((in_len + 2) / 3); + std::string ret(out_len, '\0'); + size_t i; + char *p = const_cast(ret.c_str()); - for (i = 0; i < in_len - 2; i += 3) { - *p++ = sEncodingTable[(data[i] >> 2) & 0x3F]; - *p++ = sEncodingTable[((data[i] & 0x3) << 4) | ((int) (data[i + 1] & 0xF0) >> 4)]; - *p++ = sEncodingTable[((data[i + 1] & 0xF) << 2) | ((int) (data[i + 2] & 0xC0) >> 6)]; - *p++ = sEncodingTable[data[i + 2] & 0x3F]; - } - if (i < in_len) { - *p++ = sEncodingTable[(data[i] >> 2) & 0x3F]; - if (i == (in_len - 1)) { - *p++ = sEncodingTable[((data[i] & 0x3) << 4)]; - *p++ = '='; - } - else { - *p++ = sEncodingTable[((data[i] & 0x3) << 4) | ((int) (data[i + 1] & 0xF0) >> 4)]; - *p++ = sEncodingTable[((data[i + 1] & 0xF) << 2)]; - } - *p++ = '='; - } + for (i = 0; i < in_len - 2; i += 3) { + *p++ = sEncodingTable[(data[i] >> 2) & 0x3F]; + *p++ = sEncodingTable[((data[i] & 0x3) << 4) | ((int) (data[i + 1] & 0xF0) >> 4)]; + *p++ = sEncodingTable[((data[i + 1] & 0xF) << 2) | ((int) (data[i + 2] & 0xC0) >> 6)]; + *p++ = sEncodingTable[data[i + 2] & 0x3F]; + } + if (i < in_len) { + *p++ = sEncodingTable[(data[i] >> 2) & 0x3F]; + if (i == (in_len - 1)) { + *p++ = sEncodingTable[((data[i] & 0x3) << 4)]; + *p++ = '='; + } + else { + *p++ = sEncodingTable[((data[i] & 0x3) << 4) | ((int) (data[i + 1] & 0xF0) >> 4)]; + *p++ = sEncodingTable[((data[i + 1] & 0xF) << 2)]; + } + *p++ = '='; + } - return ret; - } + return ret; + } - static std::string Decode(const std::string& input, std::string& out) { - static + static std::string Decode(const std::string& input, std::string& out) { + static #if __STDC_VERSION__ >= 201112L - constexpr + constexpr #endif /* __STDC_VERSION__ >= 201112L */ - unsigned char kDecodingTable[] = { - 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, - 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, - 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 62, 64, 64, 64, 63, - 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 64, 64, 64, 64, 64, 64, - 64, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, - 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 64, 64, 64, 64, 64, - 64, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, - 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 64, 64, 64, 64, 64, - 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, - 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, - 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, - 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, - 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, - 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, - 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, - 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64 + unsigned char kDecodingTable[] = { + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 62, 64, 64, 64, 63, + 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 64, 64, 64, 64, 64, 64, + 64, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, + 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 64, 64, 64, 64, 64, + 64, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, + 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64 + }; + + size_t in_len = input.size(); + if (in_len % 4 != 0) return "Input data size is not a multiple of 4"; + + size_t out_len = in_len / 4 * 3; + if (input[in_len - 1] == '=') out_len--; + if (input[in_len - 2] == '=') out_len--; + + out.resize(out_len); + + for (size_t i = 0, j = 0; i < in_len;) { + uint32_t a = input[i] == '=' ? 0 & i++ : kDecodingTable[static_cast(input[i++])]; + uint32_t b = input[i] == '=' ? 0 & i++ : kDecodingTable[static_cast(input[i++])]; + uint32_t c = input[i] == '=' ? 0 & i++ : kDecodingTable[static_cast(input[i++])]; + uint32_t d = input[i] == '=' ? 0 & i++ : kDecodingTable[static_cast(input[i++])]; + + uint32_t triple = (a << 3 * 6) + (b << 2 * 6) + (c << 1 * 6) + (d << 0 * 6); + + if (j < out_len) out[j++] = (triple >> 2 * 8) & 0xFF; + if (j < out_len) out[j++] = (triple >> 1 * 8) & 0xFF; + if (j < out_len) out[j++] = (triple >> 0 * 8) & 0xFF; + } + + return ""; + } + }; - size_t in_len = input.size(); - if (in_len % 4 != 0) return "Input data size is not a multiple of 4"; - - size_t out_len = in_len / 4 * 3; - if (input[in_len - 1] == '=') out_len--; - if (input[in_len - 2] == '=') out_len--; - - out.resize(out_len); - - for (size_t i = 0, j = 0; i < in_len;) { - uint32_t a = input[i] == '=' ? 0 & i++ : kDecodingTable[static_cast(input[i++])]; - uint32_t b = input[i] == '=' ? 0 & i++ : kDecodingTable[static_cast(input[i++])]; - uint32_t c = input[i] == '=' ? 0 & i++ : kDecodingTable[static_cast(input[i++])]; - uint32_t d = input[i] == '=' ? 0 & i++ : kDecodingTable[static_cast(input[i++])]; - - uint32_t triple = (a << 3 * 6) + (b << 2 * 6) + (c << 1 * 6) + (d << 0 * 6); - - if (j < out_len) out[j++] = (triple >> 2 * 8) & 0xFF; - if (j < out_len) out[j++] = (triple >> 1 * 8) & 0xFF; - if (j < out_len) out[j++] = (triple >> 0 * 8) & 0xFF; - } - - return ""; - } - -}; - } #endif /* _MACARON_BASE64_H_ */ diff --git a/include/bytearray.h b/include/bytearray.h index 8fb7680..25c6aa1 100644 --- a/include/bytearray.h +++ b/include/bytearray.h @@ -38,149 +38,149 @@ namespace gourou { public: - /** - * @brief Create an empty byte array - * - * @param useMalloc If true, use malloc() instead of new[] for allocation - */ - ByteArray(bool useMalloc=false); + /** + * @brief Create an empty byte array + * + * @param useMalloc If true, use malloc() instead of new[] for allocation + */ + ByteArray(bool useMalloc=false); - /** - * @brief Create an empty byte array of length bytes - * - * @param length Length of data - * @param useMalloc If true, use malloc() instead of new[] for allocation - */ - ByteArray(unsigned int length, bool useMalloc=false); + /** + * @brief Create an empty byte array of length bytes + * + * @param length Length of data + * @param useMalloc If true, use malloc() instead of new[] for allocation + */ + ByteArray(unsigned int length, bool useMalloc=false); - /** - * @brief Initialize ByteArray with a copy of data - * - * @param data Data to be copied - * @param length Length of data - */ - ByteArray(const unsigned char* data, unsigned int length); + /** + * @brief Initialize ByteArray with a copy of data + * + * @param data Data to be copied + * @param length Length of data + */ + ByteArray(const unsigned char* data, unsigned int length); - /** - * @brief Initialize ByteArray with a copy of data - * - * @param data Data to be copied - * @param length Optional length of data. If length == -1, it use strlen(data) as length - */ - ByteArray(const char* data, int length=-1); + /** + * @brief Initialize ByteArray with a copy of data + * + * @param data Data to be copied + * @param length Optional length of data. If length == -1, it use strlen(data) as length + */ + ByteArray(const char* data, int length=-1); - /** - * @brief Initialize ByteArray with a copy of str - * - * @param str Use internal data of str - */ - ByteArray(const std::string& str); + /** + * @brief Initialize ByteArray with a copy of str + * + * @param str Use internal data of str + */ + ByteArray(const std::string& str); - ByteArray(const ByteArray& other); - ~ByteArray(); + ByteArray(const ByteArray& other); + ~ByteArray(); - /** - * @brief Encode "other" data into base64 and put it into a ByteArray - */ - static ByteArray fromBase64(const ByteArray& other); + /** + * @brief Encode "other" data into base64 and put it into a ByteArray + */ + static ByteArray fromBase64(const ByteArray& other); - /** - * @brief Encode data into base64 and put it into a ByteArray - * - * @param data Data to be encoded - * @param length Optional length of data. If length == -1, it use strlen(data) as length - */ - static ByteArray fromBase64(const char* data, int length=-1); + /** + * @brief Encode data into base64 and put it into a ByteArray + * + * @param data Data to be encoded + * @param length Optional length of data. If length == -1, it use strlen(data) as length + */ + static ByteArray fromBase64(const char* data, int length=-1); - /** - * @brief Encode str into base64 and put it into a ByteArray - * - * @param str Use internal data of str - */ - static ByteArray fromBase64(const std::string& str); + /** + * @brief Encode str into base64 and put it into a ByteArray + * + * @param str Use internal data of str + */ + static ByteArray fromBase64(const std::string& str); - /** - * @brief Return a string with base64 encoded internal data - */ - std::string toBase64(); + /** + * @brief Return a string with base64 encoded internal data + */ + std::string toBase64(); - /** - * @brief Convert hex string into bytes - * - * @param str Hex string - */ - static ByteArray fromHex(const std::string& str); + /** + * @brief Convert hex string into bytes + * + * @param str Hex string + */ + static ByteArray fromHex(const std::string& str); - /** - * @brief Return a string with human readable hex encoded internal data - */ - std::string toHex(); + /** + * @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 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 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 char* str); - /** - * @brief Append str to internal data - */ - void append(const std::string& str); + /** + * @brief Append str to internal data + */ + void append(const std::string& str); - /** - * @brief Get internal data. Must not be freed - */ - unsigned char* data() {return _data;} + /** + * @brief Get internal data. Must not be freed + */ + unsigned char* data() {return _data;} - /** - * @brief Get internal data and increment internal reference counter. - * Must bot be freed - */ - unsigned char* takeShadowData() {addRef() ; return _data;} + /** + * @brief Get internal data and increment internal reference counter. + * Must bot be freed + */ + unsigned char* takeShadowData() {addRef() ; return _data;} - /** - * @brief Release shadow data. It can now be freed by ByteArray - */ - void releaseShadowData() {delRef();} + /** + * @brief Release shadow data. It can now be freed by ByteArray + */ + void releaseShadowData() {delRef();} - /** - * @brief Get internal data length - */ - unsigned int length() const {return _length;} + /** + * @brief Get internal data length + */ + unsigned int length() const {return _length;} - /** - * @brief Get internal data length - */ - unsigned int size() const {return length();} + /** + * @brief Get internal data length + */ + unsigned int size() const {return length();} - /** - * @brief Increase or decrease internal buffer - * @param length New length of internal buffer - * @param keepData If true copy old data on new buffer, if false, - * create a new buffer with random data - */ - void resize(unsigned int length, bool keepData=true); + /** + * @brief Increase or decrease internal buffer + * @param length New length of internal buffer + * @param keepData If true copy old data on new buffer, if false, + * create a new buffer with random data + */ + void resize(unsigned int length, bool keepData=true); + + ByteArray& operator=(const ByteArray& other); - ByteArray& operator=(const ByteArray& other); - private: - void initData(const unsigned char* data, unsigned int length); - void addRef(); - void delRef(); + void initData(const unsigned char* data, unsigned int length); + void addRef(); + void delRef(); - bool _useMalloc; - unsigned char* _data; - unsigned int _length; - static std::map refCounter; + bool _useMalloc; + unsigned char* _data; + unsigned int _length; + static std::map refCounter; }; } #endif diff --git a/include/device.h b/include/device.h index 2e01b58..3a88abe 100644 --- a/include/device.h +++ b/include/device.h @@ -30,54 +30,54 @@ namespace gourou class Device { public: - static const int DEVICE_KEY_SIZE = 16; - static const int DEVICE_SERIAL_LEN = 10; + static const int DEVICE_KEY_SIZE = 16; + static const int DEVICE_SERIAL_LEN = 10; - /** - * @brief Main Device constructor - * - * @param processor Instance of DRMProcessor - * @param deviceFile Path of device.xml - * @param deviceKeyFile Path of devicesalt - */ - Device(DRMProcessor* processor, const std::string& deviceFile, const std::string& deviceKeyFile); + /** + * @brief Main Device constructor + * + * @param processor Instance of DRMProcessor + * @param deviceFile Path of device.xml + * @param deviceKeyFile Path of devicesalt + */ + Device(DRMProcessor* processor, const std::string& deviceFile, const std::string& deviceKeyFile); - /** - * @brief Return value of devicesalt file (DEVICE_KEY_SIZE len) - */ - const unsigned char* getDeviceKey(); + /** + * @brief Return value of devicesalt file (DEVICE_KEY_SIZE len) + */ + const unsigned char* getDeviceKey(); - /** - * @brief Get one value of device.xml (deviceClass, deviceSerial, deviceName, deviceType, hobbes, clientOS, clientLocale) - */ - std::string getProperty(const std::string& property, const std::string& _default=std::string("")); - std::string operator[](const std::string& property); + /** + * @brief Get one value of device.xml (deviceClass, deviceSerial, deviceName, deviceType, hobbes, clientOS, clientLocale) + */ + std::string getProperty(const std::string& property, const std::string& _default=std::string("")); + std::string operator[](const std::string& property); + + /** + * @brief Create device.xml and devicesalt files when they did not exists + * + * @param processor Instance of DRMProcessor + * @param dirName Directory where to put files (.adept) + * @param hobbes Hobbes (client version) to set + * @param randomSerial Create a random serial (new device each time) or not (serial computed from machine specs) + */ + static Device* createDevice(DRMProcessor* processor, const std::string& dirName, const std::string& hobbes, bool randomSerial); - /** - * @brief Create device.xml and devicesalt files when they did not exists - * - * @param processor Instance of DRMProcessor - * @param dirName Directory where to put files (.adept) - * @param hobbes Hobbes (client version) to set - * @param randomSerial Create a random serial (new device each time) or not (serial computed from machine specs) - */ - static Device* createDevice(DRMProcessor* processor, const std::string& dirName, const std::string& hobbes, bool randomSerial); - private: - DRMProcessor* processor; + DRMProcessor* processor; std::string deviceFile; std::string deviceKeyFile; - unsigned char deviceKey[DEVICE_KEY_SIZE]; - std::map properties; + unsigned char deviceKey[DEVICE_KEY_SIZE]; + std::map properties; - Device(DRMProcessor* processor); - - std::string makeFingerprint(const std::string& serial); - std::string makeSerial(bool random); - void parseDeviceFile(); - void parseDeviceKeyFile(); - void createDeviceFile(const std::string& hobbes, bool randomSerial); - void createDeviceKeyFile(); + Device(DRMProcessor* processor); + + std::string makeFingerprint(const std::string& serial); + std::string makeSerial(bool random); + void parseDeviceFile(); + void parseDeviceKeyFile(); + void createDeviceFile(const std::string& hobbes, bool randomSerial); + void createDeviceKeyFile(); }; } diff --git a/include/drmprocessorclient.h b/include/drmprocessorclient.h index 8d24ed7..352e2f7 100644 --- a/include/drmprocessorclient.h +++ b/include/drmprocessorclient.h @@ -34,376 +34,376 @@ namespace gourou class DigestInterface { public: - /** - * @brief Create a digest handler - * - * @param digestName Digest name to instanciate - */ - virtual void* createDigest(const std::string& digestName) = 0; + /** + * @brief Create a digest handler + * + * @param digestName Digest name to instanciate + */ + virtual void* createDigest(const std::string& digestName) = 0; - /** - * @brief Update digest engine with new data - * - * @param handler Digest handler - * @param data Data to digest - * @param length Length of data - */ - virtual void digestUpdate(void* handler, unsigned char* data, unsigned int length) = 0; + /** + * @brief Update digest engine with new data + * + * @param handler Digest handler + * @param data Data to digest + * @param length Length of data + */ + virtual void digestUpdate(void* handler, unsigned char* data, unsigned int length) = 0; - /** - * @brief Finalize digest with remained buffered data and destroy handler - * - * @param handler Digest handler - * @param digestOut Digest result (buffer must be pre allocated with right size) - */ - virtual void digestFinalize(void* handler, unsigned char* digestOut) = 0; + /** + * @brief Finalize digest with remained buffered data and destroy handler + * + * @param handler Digest handler + * @param digestOut Digest result (buffer must be pre allocated with right size) + */ + virtual void digestFinalize(void* handler, unsigned char* digestOut) = 0; - /** - * @brief Global digest function - * - * @param digestName Digest name to instanciate - * @param data Data to digest - * @param length Length of data - * @param digestOut Digest result (buffer must be pre allocated with right size) - */ - virtual void digest(const std::string& digestName, unsigned char* data, unsigned int length, unsigned char* digestOut) = 0; + /** + * @brief Global digest function + * + * @param digestName Digest name to instanciate + * @param data Data to digest + * @param length Length of data + * @param digestOut Digest result (buffer must be pre allocated with right size) + */ + virtual void digest(const std::string& digestName, unsigned char* data, unsigned int length, unsigned char* digestOut) = 0; }; class RandomInterface { public: - /** - * @brief Generate random bytes - * - * @param bytesOut Buffer to fill with random bytes - * @param length Length of bytesOut - */ - virtual void randBytes(unsigned char* bytesOut, unsigned int length) = 0; + /** + * @brief Generate random bytes + * + * @param bytesOut Buffer to fill with random bytes + * @param length Length of bytesOut + */ + virtual void randBytes(unsigned char* bytesOut, unsigned int length) = 0; }; class HTTPInterface { public: - - /** - * @brief Send HTTP (GET or POST) request - * - * @param URL HTTP URL - * @param POSTData POST data if needed, if not set, a GET request is done - * @param contentType Optional content type of POST Data - * @param responseHeaders Optional Response headers of HTTP request - * @param fd Optional file descriptor to write request result - * @param resume false if target file should be truncated, true to try resume download (works only in combination with a valid fd) - * - * @return data of HTTP response - */ - virtual std::string sendHTTPRequest(const std::string& URL, const std::string& POSTData=std::string(""), const std::string& contentType=std::string(""), std::map* responseHeaders=0, int fd=0, bool resume=false) = 0; + + /** + * @brief Send HTTP (GET or POST) request + * + * @param URL HTTP URL + * @param POSTData POST data if needed, if not set, a GET request is done + * @param contentType Optional content type of POST Data + * @param responseHeaders Optional Response headers of HTTP request + * @param fd Optional file descriptor to write request result + * @param resume false if target file should be truncated, true to try resume download (works only in combination with a valid fd) + * + * @return data of HTTP response + */ + virtual std::string sendHTTPRequest(const std::string& URL, const std::string& POSTData=std::string(""), const std::string& contentType=std::string(""), std::map* responseHeaders=0, int fd=0, bool resume=false) = 0; }; class RSAInterface { public: - enum RSA_KEY_TYPE { - RSA_KEY_PKCS12 = 0, - RSA_KEY_PKCS8, - RSA_KEY_X509 - }; + enum RSA_KEY_TYPE { + RSA_KEY_PKCS12 = 0, + RSA_KEY_PKCS8, + 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 Decrypt data with RSA private key. Data is padded using PKCS1.5 - * - * @param RSAKey RSA key in binary form - * @param RSAKeyLength RSA key length - * @param keyType Key type - * @param password Optional password for RSA PKCS12 certificate - * @param data Data to encrypt - * @param dataLength Data length - * @param res Encryption result (pre allocated buffer) - */ - virtual void RSAPrivateDecrypt(const unsigned char* RSAKey, unsigned int RSAKeyLength, - const RSA_KEY_TYPE keyType, const std::string& password, - const unsigned char* data, unsigned dataLength, - unsigned char* res) = 0; + /** + * @brief Encrypt data with RSA private key. Data is padded using PKCS1.5 + * + * @param RSAKey RSA key in binary form + * @param RSAKeyLength RSA key length + * @param keyType Key type + * @param password Optional password for RSA PKCS12 certificate + * @param data Data to encrypt + * @param dataLength Data length + * @param res Encryption result (pre allocated buffer) + */ + virtual void RSAPrivateEncrypt(const unsigned char* RSAKey, unsigned int RSAKeyLength, + const RSA_KEY_TYPE keyType, const std::string& password, + const unsigned char* data, unsigned dataLength, + unsigned char* res) = 0; - /** - * @brief Encrypt data with RSA public key. Data is padded using PKCS1.5 - * - * @param RSAKey RSA key in binary form - * @param RSAKeyLength RSA key length - * @param keyType Key type - * @param password Optional password for RSA PKCS12 certificate - * @param data Data to encrypt - * @param dataLength Data length - * @param res Encryption result (pre allocated buffer) - */ - virtual void RSAPublicEncrypt(const unsigned char* RSAKey, unsigned int RSAKeyLength, - const RSA_KEY_TYPE keyType, - const unsigned char* data, unsigned dataLength, - unsigned char* res) = 0; + /** + * @brief Decrypt data with RSA private key. Data is padded using PKCS1.5 + * + * @param RSAKey RSA key in binary form + * @param RSAKeyLength RSA key length + * @param keyType Key type + * @param password Optional password for RSA PKCS12 certificate + * @param data Data to encrypt + * @param dataLength Data length + * @param res Encryption result (pre allocated buffer) + */ + virtual void RSAPrivateDecrypt(const unsigned char* RSAKey, unsigned int RSAKeyLength, + const RSA_KEY_TYPE keyType, const std::string& password, + const unsigned char* data, unsigned dataLength, + unsigned char* res) = 0; - /** - * @brief Generate RSA key. Expnonent is fixed (65537 / 0x10001) - * - * @param keyLengthBits Length of key (in bits) to generate - * - * @return generatedKey - */ - virtual void* generateRSAKey(int keyLengthBits) = 0; + /** + * @brief Encrypt data with RSA public key. Data is padded using PKCS1.5 + * + * @param RSAKey RSA key in binary form + * @param RSAKeyLength RSA key length + * @param keyType Key type + * @param password Optional password for RSA PKCS12 certificate + * @param data Data to encrypt + * @param dataLength Data length + * @param res Encryption result (pre allocated buffer) + */ + virtual void RSAPublicEncrypt(const unsigned char* RSAKey, unsigned int RSAKeyLength, + const RSA_KEY_TYPE keyType, + const unsigned char* data, unsigned dataLength, + unsigned char* res) = 0; - /** - * @brief Destroy key previously generated - * - * @param handler Key to destroy - */ - virtual void destroyRSAHandler(void* handler) = 0; + /** + * @brief Generate RSA key. Expnonent is fixed (65537 / 0x10001) + * + * @param keyLengthBits Length of key (in bits) to generate + * + * @return generatedKey + */ + virtual void* generateRSAKey(int keyLengthBits) = 0; - /** - * @brief Extract public key (big number) from RSA handler - * - * @param handler RSA handler (generated key) - * @param keyOut Pre allocated buffer (if *keyOut != 0). If *keyOut is 0, memory is internally allocated (must be freed) - * @param keyOutLength Length of result - */ - virtual void extractRSAPublicKey(void* handler, unsigned char** keyOut, unsigned int* keyOutLength) = 0; + /** + * @brief Destroy key previously generated + * + * @param handler Key to destroy + */ + virtual void destroyRSAHandler(void* handler) = 0; - /** - * @brief Extract private key (big number) from RSA handler - * - * @param handler RSA handler (generated key) - * @param keyOut Pre allocated buffer (if *keyOut != 0). If *keyOut is 0, memory is internally allocated (must be freed) - * @param keyOutLength Length of result - */ - virtual void extractRSAPrivateKey(void* handler, unsigned char** keyOut, unsigned int* keyOutLength) = 0; + /** + * @brief Extract public key (big number) from RSA handler + * + * @param handler RSA handler (generated key) + * @param keyOut Pre allocated buffer (if *keyOut != 0). If *keyOut is 0, memory is internally allocated (must be freed) + * @param keyOutLength Length of result + */ + virtual void extractRSAPublicKey(void* handler, unsigned char** keyOut, unsigned int* keyOutLength) = 0; - /** - * @brief Extract certificate from PKCS12 blob - * - * @param RSAKey RSA key in binary form - * @param RSAKeyLength RSA key length - * @param keyType Key type - * @param password Optional password for RSA PKCS12 certificate - * @param certOut Result certificate - * @param certOutLength Result certificate length - */ - virtual void extractCertificate(const unsigned char* RSAKey, unsigned int RSAKeyLength, - const RSA_KEY_TYPE keyType, const std::string& password, - unsigned char** certOut, unsigned int* certOutLength) = 0; + /** + * @brief Extract private key (big number) from RSA handler + * + * @param handler RSA handler (generated key) + * @param keyOut Pre allocated buffer (if *keyOut != 0). If *keyOut is 0, memory is internally allocated (must be freed) + * @param keyOutLength Length of result + */ + virtual void extractRSAPrivateKey(void* handler, unsigned char** keyOut, unsigned int* keyOutLength) = 0; + + /** + * @brief Extract certificate from PKCS12 blob + * + * @param RSAKey RSA key in binary form + * @param RSAKeyLength RSA key length + * @param keyType Key type + * @param password Optional password for RSA PKCS12 certificate + * @param certOut Result certificate + * @param certOutLength Result certificate length + */ + virtual void extractCertificate(const unsigned char* RSAKey, unsigned int RSAKeyLength, + const RSA_KEY_TYPE keyType, const std::string& password, + unsigned char** certOut, unsigned int* certOutLength) = 0; }; class CryptoInterface { public: - enum CRYPTO_ALGO { - ALGO_AES=0, - ALGO_RC4 - }; + enum CRYPTO_ALGO { + ALGO_AES=0, + ALGO_RC4 + }; - enum CHAINING_MODE { - CHAIN_ECB=0, - CHAIN_CBC - }; - - /** - * @brief Do encryption. If length of data is not multiple of block size, PKCS#5 padding is done - * - * @param algo Algorithm to use - * @param chaining Chaining mode - * @param key AES key - * @param keyLength AES key length - * @param iv IV key - * @param ivLength IV key length - * @param dataIn Data to encrypt - * @param dataInLength Data length - * @param dataOut Encrypted data - * @param dataOutLength Length of encrypted data - */ - virtual void encrypt(CRYPTO_ALGO algo, CHAINING_MODE chaining, - const unsigned char* key, unsigned int keyLength, - const unsigned char* iv, unsigned int ivLength, - const unsigned char* dataIn, unsigned int dataInLength, - unsigned char* dataOut, unsigned int* dataOutLength) = 0; + enum CHAINING_MODE { + CHAIN_ECB=0, + CHAIN_CBC + }; - /** - * @brief Init encryption - * - * @param chaining Chaining mode - * @param key Key - * @param keyLength Key length - * @param iv Optional IV key - * @param ivLength Optional IV key length - * - * @return AES handler - */ - virtual void* encryptInit(CRYPTO_ALGO algo, CHAINING_MODE chaining, - const unsigned char* key, unsigned int keyLength, - const unsigned char* iv=0, unsigned int ivLength=0) = 0; + /** + * @brief Do encryption. If length of data is not multiple of block size, PKCS#5 padding is done + * + * @param algo Algorithm to use + * @param chaining Chaining mode + * @param key AES key + * @param keyLength AES key length + * @param iv IV key + * @param ivLength IV key length + * @param dataIn Data to encrypt + * @param dataInLength Data length + * @param dataOut Encrypted data + * @param dataOutLength Length of encrypted data + */ + virtual void encrypt(CRYPTO_ALGO algo, CHAINING_MODE chaining, + const unsigned char* key, unsigned int keyLength, + const unsigned char* iv, unsigned int ivLength, + const unsigned char* dataIn, unsigned int dataInLength, + unsigned char* dataOut, unsigned int* dataOutLength) = 0; - /** - * @brief Encrypt data - * - * @param handler Crypto handler - * @param dataIn Data to encrypt - * @param dataInLength Data length - * @param dataOut Encrypted data - * @param dataOutLength Length of encrypted data - */ - virtual void encryptUpdate(void* handler, const unsigned char* dataIn, unsigned int dataInLength, - unsigned char* dataOut, unsigned int* dataOutLength) = 0; + /** + * @brief Init encryption + * + * @param chaining Chaining mode + * @param key Key + * @param keyLength Key length + * @param iv Optional IV key + * @param ivLength Optional IV key length + * + * @return AES handler + */ + virtual void* encryptInit(CRYPTO_ALGO algo, CHAINING_MODE chaining, + const unsigned char* key, unsigned int keyLength, + const unsigned char* iv=0, unsigned int ivLength=0) = 0; - /** - * @brief Finalize encryption (pad and encrypt last block if needed) - * Destroy handler at the end - * - * @param handler Crypto handler - * @param dataOut Last block of encrypted data - * @param dataOutLength Length of encrypted data - */ - virtual void encryptFinalize(void* handler, unsigned char* dataOut, unsigned int* dataOutLength) = 0; + /** + * @brief Encrypt data + * + * @param handler Crypto handler + * @param dataIn Data to encrypt + * @param dataInLength Data length + * @param dataOut Encrypted data + * @param dataOutLength Length of encrypted data + */ + virtual void encryptUpdate(void* handler, const unsigned char* dataIn, unsigned int dataInLength, + unsigned char* dataOut, unsigned int* dataOutLength) = 0; - /** - * @brief Do decryption. If length of data is not multiple of block size, PKCS#5 padding is done - * - * @param algo Algorithm to use - * @param chaining Chaining mode - * @param key AES key - * @param keyLength AES key length - * @param iv IV key - * @param ivLength IV key length - * @param dataIn Data to encrypt - * @param dataInLength Data length - * @param dataOut Encrypted data - * @param dataOutLength Length of encrypted data - */ - virtual void decrypt(CRYPTO_ALGO algo, CHAINING_MODE chaining, - const unsigned char* key, unsigned int keyLength, - const unsigned char* iv, unsigned int ivLength, - const unsigned char* dataIn, unsigned int dataInLength, - unsigned char* dataOut, unsigned int* dataOutLength) = 0; + /** + * @brief Finalize encryption (pad and encrypt last block if needed) + * Destroy handler at the end + * + * @param handler Crypto handler + * @param dataOut Last block of encrypted data + * @param dataOutLength Length of encrypted data + */ + virtual void encryptFinalize(void* handler, unsigned char* dataOut, unsigned int* dataOutLength) = 0; - /** - * @brief Init decryption - * - * @param chaining Chaining mode - * @param key Key - * @param keyLength Key length - * @param iv IV key - * @param ivLength IV key length - * - * @return AES handler - */ - virtual void* decryptInit(CRYPTO_ALGO algo, CHAINING_MODE chaining, - const unsigned char* key, unsigned int keyLength, - const unsigned char* iv=0, unsigned int ivLength=0) = 0; + /** + * @brief Do decryption. If length of data is not multiple of block size, PKCS#5 padding is done + * + * @param algo Algorithm to use + * @param chaining Chaining mode + * @param key AES key + * @param keyLength AES key length + * @param iv IV key + * @param ivLength IV key length + * @param dataIn Data to encrypt + * @param dataInLength Data length + * @param dataOut Encrypted data + * @param dataOutLength Length of encrypted data + */ + virtual void decrypt(CRYPTO_ALGO algo, CHAINING_MODE chaining, + const unsigned char* key, unsigned int keyLength, + const unsigned char* iv, unsigned int ivLength, + const unsigned char* dataIn, unsigned int dataInLength, + unsigned char* dataOut, unsigned int* dataOutLength) = 0; - /** - * @brief Decrypt data - * - * @param handler Crypto handler - * @param dataIn Data to decrypt - * @param dataInLength Data length - * @param dataOut Decrypted data - * @param dataOutLength Length of decrypted data - */ - virtual void decryptUpdate(void* handler, const unsigned char* dataIn, unsigned int dataInLength, - unsigned char* dataOut, unsigned int* dataOutLength) = 0; - /** - * @brief Finalize decryption (decrypt last block and remove padding if it is set). - * Destroy handler at the end - * - * @param handler Crypto handler - * @param dataOut Last block decrypted data - * @param dataOutLength Length of decrypted data - */ - virtual void decryptFinalize(void* handler, unsigned char* dataOut, unsigned int* dataOutLength) = 0; + /** + * @brief Init decryption + * + * @param chaining Chaining mode + * @param key Key + * @param keyLength Key length + * @param iv IV key + * @param ivLength IV key length + * + * @return AES handler + */ + virtual void* decryptInit(CRYPTO_ALGO algo, CHAINING_MODE chaining, + const unsigned char* key, unsigned int keyLength, + const unsigned char* iv=0, unsigned int ivLength=0) = 0; + + /** + * @brief Decrypt data + * + * @param handler Crypto handler + * @param dataIn Data to decrypt + * @param dataInLength Data length + * @param dataOut Decrypted data + * @param dataOutLength Length of decrypted data + */ + virtual void decryptUpdate(void* handler, const unsigned char* dataIn, unsigned int dataInLength, + unsigned char* dataOut, unsigned int* dataOutLength) = 0; + /** + * @brief Finalize decryption (decrypt last block and remove padding if it is set). + * Destroy handler at the end + * + * @param handler Crypto handler + * @param dataOut Last block decrypted data + * @param dataOutLength Length of decrypted data + */ + virtual void decryptFinalize(void* handler, unsigned char* dataOut, unsigned int* dataOutLength) = 0; }; 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 - * @param result Result buffer - * @param decompress If false, don't decompress read data - */ - virtual void zipReadFile(void* handler, const std::string& path, ByteArray& result, bool decompress=true) = 0; - - /** - * @brief Write zip internal file - * - * @param handler ZIP file handler - * @param path Internal path inside zip file - * @param content File content - */ - virtual void zipWriteFile(void* handler, const std::string& path, ByteArray& content) = 0; + /** + * @brief 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 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 Read zip internal file + * + * @param handler ZIP file handler + * @param path Internal path inside zip file + * @param result Result buffer + * @param decompress If false, don't decompress read data + */ + virtual void zipReadFile(void* handler, const std::string& path, ByteArray& result, bool decompress=true) = 0; - /** - * @brief Close ZIP file handler - * - * @param handler ZIP file handler - */ - virtual void zipClose(void* handler) = 0; + /** + * @brief Write zip internal file + * + * @param handler ZIP file handler + * @param path Internal path inside zip file + * @param content File content + */ + virtual void zipWriteFile(void* handler, const std::string& path, ByteArray& content) = 0; - /** - * @brief Inflate algorithm - * - * @param data Data to inflate - * @param result Zipped data - * @param wbits Window bits value for libz - */ - virtual void inflate(gourou::ByteArray& data, gourou::ByteArray& result, - int wbits=-15) = 0; - - /** - * @brief Deflate algorithm - * - * @param data Data to deflate - * @param result Unzipped data - * @param wbits Window bits value for libz - * @param compressionLevel Compression level for libz - */ - virtual void deflate(gourou::ByteArray& data, gourou::ByteArray& result, - int wbits=-15, int compressionLevel=8) = 0; + /** + * @brief Delete zip internal file + * + * @param handler ZIP file handler + * @param path Internal path inside zip file + */ + virtual void zipDeleteFile(void* handler, const std::string& path) = 0; + + /** + * @brief Close ZIP file handler + * + * @param handler ZIP file handler + */ + virtual void zipClose(void* handler) = 0; + + /** + * @brief Inflate algorithm + * + * @param data Data to inflate + * @param result Zipped data + * @param wbits Window bits value for libz + */ + virtual void inflate(gourou::ByteArray& data, gourou::ByteArray& result, + int wbits=-15) = 0; + + /** + * @brief Deflate algorithm + * + * @param data Data to deflate + * @param result Unzipped data + * @param wbits Window bits value for libz + * @param compressionLevel Compression level for libz + */ + virtual void deflate(gourou::ByteArray& data, gourou::ByteArray& result, + int wbits=-15, int compressionLevel=8) = 0; }; - + class DRMProcessorClient: public DigestInterface, public RandomInterface, public HTTPInterface, \ - public RSAInterface, public CryptoInterface, public ZIPInterface + public RSAInterface, public CryptoInterface, public ZIPInterface {}; } #endif diff --git a/include/fulfillment_item.h b/include/fulfillment_item.h index 100bcdf..32d2fe8 100644 --- a/include/fulfillment_item.h +++ b/include/fulfillment_item.h @@ -34,52 +34,52 @@ namespace gourou class FulfillmentItem { public: - /** - * @brief Main constructor. Not to be called by user - * - * @param doc Fulfill reply - * @param user User pointer - */ - FulfillmentItem(pugi::xml_document& doc, User* user); + /** + * @brief Main constructor. Not to be called by user + * + * @param doc Fulfill reply + * @param user User pointer + */ + FulfillmentItem(pugi::xml_document& doc, User* user); - ~FulfillmentItem(); + ~FulfillmentItem(); - /** - * @brief Return metadata value from ACSM metadata section - * - * @param name Name of key to return - */ - std::string getMetadata(std::string name); + /** + * @brief Return metadata value from ACSM metadata section + * + * @param name Name of key to return + */ + std::string getMetadata(std::string name); - /** - * @brief Return rights generated by ACS server (XML format) - */ - std::string getRights(); + /** + * @brief Return rights generated by ACS server (XML format) + */ + std::string getRights(); - /** - * @brief Return epub download URL - */ - std::string getDownloadURL(); + /** + * @brief Return epub download URL + */ + std::string getDownloadURL(); - /** - * @brief Return resource value - */ - std::string getResource(); + /** + * @brief Return resource value + */ + std::string getResource(); - /** - * @brief Return loan token if there is one - */ - LoanToken* getLoanToken(); + /** + * @brief Return loan token if there is one + */ + LoanToken* getLoanToken(); private: - pugi::xml_document fulfillDoc; - pugi::xml_node metadatas; - pugi::xml_document rights; - std::string downloadURL; - std::string resource; - LoanToken* loanToken; - - void buildRights(const pugi::xml_node& licenseToken, User* user); + pugi::xml_document fulfillDoc; + pugi::xml_node metadatas; + pugi::xml_document rights; + std::string downloadURL; + std::string resource; + LoanToken* loanToken; + + void buildRights(const pugi::xml_node& licenseToken, User* user); }; } diff --git a/include/libgourou.h b/include/libgourou.h index cdd3cf4..921937b 100644 --- a/include/libgourou.h +++ b/include/libgourou.h @@ -48,204 +48,204 @@ namespace gourou { public: - static const std::string VERSION; + static const std::string VERSION; - enum ITEM_TYPE { EPUB=0, PDF }; - /** - * @brief Main constructor. To be used once all is configured (user has signedIn, device is activated) - * - * @param client Client processor - * @param deviceFile Path of device.xml - * @param activationFile Path of activation.xml - * @param deviceKeyFile Path of devicesalt - */ + enum ITEM_TYPE { EPUB=0, PDF }; + /** + * @brief Main constructor. To be used once all is configured (user has signedIn, device is activated) + * + * @param client Client processor + * @param deviceFile Path of device.xml + * @param activationFile Path of activation.xml + * @param deviceKeyFile Path of devicesalt + */ 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 - * @param notify Notify server if requested by response - * - * @return a FulfillmentItem if all is OK - */ - FulfillmentItem* fulfill(const std::string& ACSMFile, bool notify=true); + ~DRMProcessor(); - /** - * @brief Once fulfilled, ePub file needs to be downloaded. - * During this operation, DRM information is added into downloaded file - * - * @param item Item from fulfill() method - * @param path Output file path - * @param resume false if target file should be truncated, true to try resume download - * - * @return Type of downloaded item - */ - ITEM_TYPE download(FulfillmentItem* item, std::string path, bool resume=false); + /** + * @brief Fulfill ACSM file to server in order to retrieve ePub fulfillment item + * + * @param ACSMFile Path of ACSMFile + * @param notify Notify server if requested by response + * + * @return a FulfillmentItem if all is OK + */ + FulfillmentItem* fulfill(const std::string& ACSMFile, bool notify=true); - /** - * @brief 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 Once fulfilled, ePub file needs to be downloaded. + * During this operation, DRM information is added into downloaded file + * + * @param item Item from fulfill() method + * @param path Output file path + * @param resume false if target file should be truncated, true to try resume download + * + * @return Type of downloaded item + */ + ITEM_TYPE download(FulfillmentItem* item, std::string path, bool resume=false); - /** - * @brief Activate newly created device (user must have successfuly signedIn before) - */ - void activateDevice(); + /** + * @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 Return loaned book to server - * - * @param loanID Loan ID received during fulfill - * @param operatorURL URL of operator that loans this book - * @param notify Notify server if requested by response - */ - void returnLoan(const std::string& loanID, const std::string& operatorURL, bool notify=true); + /** + * @brief Activate newly created device (user must have successfuly signedIn before) + */ + void activateDevice(); - /** - * @brief Return default ADEPT directory (ie /home//.config/adept) - */ - static std::string getDefaultAdeptDir(void); - - /** - * @brief Create a new ADEPT environment (device.xml, devicesalt and activation.xml). - * - * @param client Client processor - * @param randomSerial Always generate a new device (or not) - * @param dirName Directory where to put generated files (.adept) - * @param hobbes Override hobbes default version - * @param ACSServer Override main ACS server (default adeactivate.adobe.com) - */ + /** + * @brief Return loaned book to server + * + * @param loanID Loan ID received during fulfill + * @param operatorURL URL of operator that loans this book + * @param notify Notify server if requested by response + */ + void returnLoan(const std::string& loanID, const std::string& operatorURL, bool notify=true); + + /** + * @brief Return default ADEPT directory (ie /home//.config/adept) + */ + static std::string getDefaultAdeptDir(void); + + /** + * @brief Create a new ADEPT environment (device.xml, devicesalt and activation.xml). + * + * @param client Client processor + * @param randomSerial Always generate a new device (or not) + * @param dirName Directory where to put generated files (.adept) + * @param hobbes Override hobbes default version + * @param ACSServer Override main ACS server (default adeactivate.adobe.com) + */ static DRMProcessor* createDRMProcessor(DRMProcessorClient* client, - bool randomSerial=false, std::string dirName=std::string(""), - const std::string& hobbes=std::string(HOBBES_DEFAULT_VERSION), - const std::string& ACSServer=ACS_SERVER); + bool randomSerial=false, std::string dirName=std::string(""), + const std::string& hobbes=std::string(HOBBES_DEFAULT_VERSION), + const std::string& ACSServer=ACS_SERVER); - /** - * @brief Get current log level - */ - static int getLogLevel(); + /** + * @brief Get current log level + */ + static int getLogLevel(); - /** - * @brief Set log level (higher number for verbose output) - */ - static void setLogLevel(int logLevel); + /** + * @brief Set log level (higher number for verbose output) + */ + static void setLogLevel(int logLevel); - /** - * Functions used internally, should not be called by user - */ + /** + * Functions used internally, should not be called by user + */ - /** - * @brief Send HTTP (GET or POST) request - * - * @param URL HTTP URL - * @param POSTData POST data if needed, if not set, a GET request is done - * @param contentType Optional content type of POST Data - * @param responseHeaders Optional Response headers of HTTP request - * @param fd Optional File descriptor to write received data - * @param resume false if target file should be truncated, true to try resume download (works only in combination of a valid fd) - * - * @return data of HTTP response - */ - ByteArray sendRequest(const std::string& URL, const std::string& POSTData=std::string(), const char* contentType=0, std::map* responseHeaders=0, int fd=0, bool resume=false); + /** + * @brief Send HTTP (GET or POST) request + * + * @param URL HTTP URL + * @param POSTData POST data if needed, if not set, a GET request is done + * @param contentType Optional content type of POST Data + * @param responseHeaders Optional Response headers of HTTP request + * @param fd Optional File descriptor to write received data + * @param resume false if target file should be truncated, true to try resume download (works only in combination of a valid fd) + * + * @return data of HTTP response + */ + ByteArray sendRequest(const std::string& URL, const std::string& POSTData=std::string(), const char* contentType=0, std::map* responseHeaders=0, int fd=0, bool resume=false); - /** - * @brief Send HTTP 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 Send HTTP POST request to URL with document as POSTData + */ + ByteArray sendRequest(const pugi::xml_document& document, const std::string& url); - /** - * @brief In place decrypt data with private device key - */ - ByteArray decryptWithDeviceKey(const unsigned char* data, unsigned int len); + /** + * @brief In place encrypt data with private device key + */ + ByteArray encryptWithDeviceKey(const unsigned char* data, unsigned int len); - /** - * @brief Return base64 encoded value of RSA public key - */ - std::string serializeRSAPublicKey(void* rsa); + /** + * @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 private key encrypted with private device key - */ - std::string serializeRSAPrivateKey(void* rsa); + /** + * @brief Return base64 encoded value of RSA public key + */ + std::string serializeRSAPublicKey(void* rsa); - /** - * @brief Export clear private license key into path - */ - void exportPrivateLicenseKey(std::string path); - - /** - * @brief Get current user - */ - User* getUser() { return user; } + /** + * @brief Return base64 encoded value of RSA private key encrypted with private device key + */ + std::string serializeRSAPrivateKey(void* rsa); - /** - * @brief Get current device - */ - Device* getDevice() { return device; } + /** + * @brief Export clear private license key into path + */ + void exportPrivateLicenseKey(std::string path); + + /** + * @brief Get current user + */ + User* getUser() { return user; } + + /** + * @brief Get current device + */ + Device* getDevice() { return device; } + + /** + * @brief Get current client + */ + DRMProcessorClient* getClient() { return client; } + + /** + * @brief Remove ADEPT DRM + * Warning: for PDF format, filenameIn must be different than filenameOut + * + * @param filenameIn Input file (with ADEPT DRM) + * @param filenameOut Output file (without ADEPT DRM) + * @param type Type of file (ePub or PDF) + * @param encryptionKey Optional encryption key, do not try to decrypt the one inside input file + * @param encryptionKeySize Size of encryption key (if provided) + */ + void removeDRM(const std::string& filenameIn, const std::string& filenameOut, ITEM_TYPE type, const unsigned char* encryptionKey=0, unsigned encryptionKeySize=0); - /** - * @brief Get current client - */ - DRMProcessorClient* getClient() { return client; } - - /** - * @brief Remove ADEPT DRM - * Warning: for PDF format, filenameIn must be different than filenameOut - * - * @param filenameIn Input file (with ADEPT DRM) - * @param filenameOut Output file (without ADEPT DRM) - * @param type Type of file (ePub or PDF) - * @param encryptionKey Optional encryption key, do not try to decrypt the one inside input file - * @param encryptionKeySize Size of encryption key (if provided) - */ - void removeDRM(const std::string& filenameIn, const std::string& filenameOut, ITEM_TYPE type, const unsigned char* encryptionKey=0, unsigned encryptionKeySize=0); - private: - gourou::DRMProcessorClient* client; + gourou::DRMProcessorClient* client; gourou::Device* device; gourou::User* user; - + DRMProcessor(DRMProcessorClient* client); - - void pushString(void* sha_ctx, const std::string& string); - void pushTag(void* sha_ctx, uint8_t tag); - void hashNode(const pugi::xml_node& root, void *sha_ctx, std::map nsHash); - void hashNode(const pugi::xml_node& root, unsigned char* sha_out); - void signNode(pugi::xml_node& rootNode); - void addNonce(pugi::xml_node& root); - void buildAuthRequest(pugi::xml_document& authReq); - void buildInitLicenseServiceRequest(pugi::xml_document& initLicReq, std::string operatorURL); - void doOperatorAuth(std::string operatorURL); - void operatorAuth(std::string operatorURL); - void buildFulfillRequest(pugi::xml_document& acsmDoc, pugi::xml_document& fulfillReq); - void buildActivateReq(pugi::xml_document& activateReq); - void buildReturnReq(pugi::xml_document& returnReq, const std::string& loanID, const std::string& operatorURL); - ByteArray sendFulfillRequest(const pugi::xml_document& document, const std::string& url); - void buildSignInRequest(pugi::xml_document& signInRequest, const std::string& adobeID, const std::string& adobePassword, const std::string& authenticationCertificate); - void fetchLicenseServiceCertificate(const std::string& licenseURL, - const std::string& operatorURL); - void buildNotifyReq(pugi::xml_document& returnReq, pugi::xml_node& body); - void notifyServer(pugi::xml_node& notifyRoot); - void notifyServer(pugi::xml_document& fulfillReply); - std::string encryptedKeyFirstPass(pugi::xml_document& rightsDoc, const std::string& encryptedKey, const std::string& keyType); - void decryptADEPTKey(pugi::xml_document& rightsDoc, unsigned char* decryptedKey, const unsigned char* encryptionKey=0, unsigned encryptionKeySize=0); - void removeEPubDRM(const std::string& filenameIn, const std::string& filenameOut, const unsigned char* encryptionKey, unsigned encryptionKeySize); - void generatePDFObjectKey(int version, - const unsigned char* masterKey, unsigned int masterKeyLength, - int objectId, int objectGenerationNumber, - unsigned char* keyOut); - void removePDFDRM(const std::string& filenameIn, const std::string& filenameOut, const unsigned char* encryptionKey, unsigned encryptionKeySize); + + void pushString(void* sha_ctx, const std::string& string); + void pushTag(void* sha_ctx, uint8_t tag); + void hashNode(const pugi::xml_node& root, void *sha_ctx, std::map nsHash); + void hashNode(const pugi::xml_node& root, unsigned char* sha_out); + void signNode(pugi::xml_node& rootNode); + void addNonce(pugi::xml_node& root); + void buildAuthRequest(pugi::xml_document& authReq); + void buildInitLicenseServiceRequest(pugi::xml_document& initLicReq, std::string operatorURL); + void doOperatorAuth(std::string operatorURL); + void operatorAuth(std::string operatorURL); + void buildFulfillRequest(pugi::xml_document& acsmDoc, pugi::xml_document& fulfillReq); + void buildActivateReq(pugi::xml_document& activateReq); + void buildReturnReq(pugi::xml_document& returnReq, const std::string& loanID, const std::string& operatorURL); + ByteArray sendFulfillRequest(const pugi::xml_document& document, const std::string& url); + void buildSignInRequest(pugi::xml_document& signInRequest, const std::string& adobeID, const std::string& adobePassword, const std::string& authenticationCertificate); + void fetchLicenseServiceCertificate(const std::string& licenseURL, + const std::string& operatorURL); + void buildNotifyReq(pugi::xml_document& returnReq, pugi::xml_node& body); + void notifyServer(pugi::xml_node& notifyRoot); + void notifyServer(pugi::xml_document& fulfillReply); + std::string encryptedKeyFirstPass(pugi::xml_document& rightsDoc, const std::string& encryptedKey, const std::string& keyType); + void decryptADEPTKey(pugi::xml_document& rightsDoc, unsigned char* decryptedKey, const unsigned char* encryptionKey=0, unsigned encryptionKeySize=0); + void removeEPubDRM(const std::string& filenameIn, const std::string& filenameOut, const unsigned char* encryptionKey, unsigned encryptionKeySize); + void generatePDFObjectKey(int version, + const unsigned char* masterKey, unsigned int masterKeyLength, + int objectId, int objectGenerationNumber, + unsigned char* keyOut); + void removePDFDRM(const std::string& filenameIn, const std::string& filenameOut, const unsigned char* encryptionKey, unsigned encryptionKeySize); }; } diff --git a/include/libgourou_common.h b/include/libgourou_common.h index 72f3915..6c57a4e 100644 --- a/include/libgourou_common.h +++ b/include/libgourou_common.h @@ -43,152 +43,152 @@ namespace gourou /** * Some common utilities */ - - #define ADOBE_ADEPT_NS "http://ns.adobe.com/adept" + +#define ADOBE_ADEPT_NS "http://ns.adobe.com/adept" static const int SHA1_LEN = 20; static const int RSA_KEY_SIZE = 128; static const int RSA_KEY_SIZE_BITS = (RSA_KEY_SIZE*8); - + enum GOUROU_ERROR { - GOUROU_DEVICE_DOES_NOT_MATCH = 0x1000, - GOUROU_INVALID_CLIENT, - GOUROU_TAG_NOT_FOUND, - GOUROU_ADEPT_ERROR, - GOUROU_FILE_ERROR, - GOUROU_INVALID_PROPERTY + GOUROU_DEVICE_DOES_NOT_MATCH = 0x1000, + GOUROU_INVALID_CLIENT, + GOUROU_TAG_NOT_FOUND, + GOUROU_ADEPT_ERROR, + GOUROU_FILE_ERROR, + GOUROU_INVALID_PROPERTY }; enum FULFILL_ERROR { - FF_ACSM_FILE_NOT_EXISTS = 0x1100, - FF_INVALID_ACSM_FILE, - FF_NO_HMAC_IN_ACSM_FILE, - FF_NOT_ACTIVATED, - FF_NO_OPERATOR_URL, - FF_SERVER_INTERNAL_ERROR + FF_ACSM_FILE_NOT_EXISTS = 0x1100, + FF_INVALID_ACSM_FILE, + FF_NO_HMAC_IN_ACSM_FILE, + FF_NOT_ACTIVATED, + FF_NO_OPERATOR_URL, + FF_SERVER_INTERNAL_ERROR }; enum DOWNLOAD_ERROR { - DW_NO_ITEM = 0x1200, - DW_NO_EBX_HANDLER, + DW_NO_ITEM = 0x1200, + DW_NO_EBX_HANDLER, }; enum SIGNIN_ERROR { - SIGN_INVALID_CREDENTIALS = 0x1300, + SIGN_INVALID_CREDENTIALS = 0x1300, }; - + enum ACTIVATE_ERROR { - ACTIVATE_NOT_SIGNEDIN = 0x1400 + ACTIVATE_NOT_SIGNEDIN = 0x1400 }; - + enum DEV_ERROR { - DEV_MKPATH = 0x2000, - DEV_MAC_ERROR, - DEV_INVALID_DEVICE_FILE, - DEV_INVALID_DEVICE_KEY_FILE, - DEV_INVALID_DEV_PROPERTY, + DEV_MKPATH = 0x2000, + DEV_MAC_ERROR, + DEV_INVALID_DEVICE_FILE, + DEV_INVALID_DEVICE_KEY_FILE, + DEV_INVALID_DEV_PROPERTY, }; enum USER_ERROR { - USER_MKPATH = 0x3000, - USER_INVALID_ACTIVATION_FILE, - USER_NO_AUTHENTICATION_URL, - USER_NO_PROPERTY, - USER_INVALID_INPUT, + USER_MKPATH = 0x3000, + USER_INVALID_ACTIVATION_FILE, + USER_NO_AUTHENTICATION_URL, + USER_NO_PROPERTY, + USER_INVALID_INPUT, }; enum FULFILL_ITEM_ERROR { - FFI_INVALID_FULFILLMENT_DATA = 0x4000, - FFI_INVALID_LOAN_TOKEN + FFI_INVALID_FULFILLMENT_DATA = 0x4000, + FFI_INVALID_LOAN_TOKEN }; - + enum CLIENT_ERROR { - CLIENT_BAD_PARAM = 0x5000, - CLIENT_INVALID_PKCS12, - CLIENT_INVALID_CERTIFICATE, - CLIENT_NO_PRIV_KEY, - CLIENT_NO_PUB_KEY, - CLIENT_RSA_ERROR, - CLIENT_BAD_CHAINING, - CLIENT_BAD_KEY_SIZE, - CLIENT_BAD_ZIP_FILE, - CLIENT_ZIP_ERROR, - CLIENT_GENERIC_EXCEPTION, - CLIENT_NETWORK_ERROR, - CLIENT_INVALID_PKCS8, - CLIENT_FILE_ERROR, - CLIENT_OSSL_ERROR, - CLIENT_CRYPT_ERROR, - CLIENT_DIGEST_ERROR, - CLIENT_HTTP_ERROR + CLIENT_BAD_PARAM = 0x5000, + CLIENT_INVALID_PKCS12, + CLIENT_INVALID_CERTIFICATE, + CLIENT_NO_PRIV_KEY, + CLIENT_NO_PUB_KEY, + CLIENT_RSA_ERROR, + CLIENT_BAD_CHAINING, + CLIENT_BAD_KEY_SIZE, + CLIENT_BAD_ZIP_FILE, + CLIENT_ZIP_ERROR, + CLIENT_GENERIC_EXCEPTION, + CLIENT_NETWORK_ERROR, + CLIENT_INVALID_PKCS8, + CLIENT_FILE_ERROR, + CLIENT_OSSL_ERROR, + CLIENT_CRYPT_ERROR, + CLIENT_DIGEST_ERROR, + CLIENT_HTTP_ERROR }; enum DRM_REMOVAL_ERROR { - DRM_ERR_ENCRYPTION_KEY = 0x6000, - DRM_VERSION_NOT_SUPPORTED, - DRM_FILE_ERROR, - DRM_FORMAT_NOT_SUPPORTED, - DRM_IN_OUT_EQUALS, - DRM_MISSING_PARAMETER, - DRM_INVALID_KEY_SIZE, - DRM_ERR_ENCRYPTION_KEY_FP, - DRM_INVALID_USER + DRM_ERR_ENCRYPTION_KEY = 0x6000, + DRM_VERSION_NOT_SUPPORTED, + DRM_FILE_ERROR, + DRM_FORMAT_NOT_SUPPORTED, + DRM_IN_OUT_EQUALS, + DRM_MISSING_PARAMETER, + DRM_INVALID_KEY_SIZE, + DRM_ERR_ENCRYPTION_KEY_FP, + DRM_INVALID_USER }; - - #ifndef _NOEXCEPT - #if __STDC_VERSION__ >= 201112L - # define _NOEXCEPT noexcept - # define _NOEXCEPT_(x) noexcept(x) - #else - # define _NOEXCEPT throw() - # define _NOEXCEPT_(x) - #endif - #endif /* !_NOEXCEPT */ - + +#ifndef _NOEXCEPT +#if __STDC_VERSION__ >= 201112L +# define _NOEXCEPT noexcept +# define _NOEXCEPT_(x) noexcept(x) +#else +# define _NOEXCEPT throw() +# define _NOEXCEPT_(x) +#endif +#endif /* !_NOEXCEPT */ + /** * Generic exception class */ class Exception : public std::exception { public: - Exception(int code, const char* message, const char* file, int line): - code(code), line(line), file(file) - { - std::stringstream msg; - msg << "Exception code : 0x" << std::setbase(16) << code << std::endl; - msg << "Message : " << message << std::endl; - if (logLevel >= LG_LOG_DEBUG) - msg << "File : " << file << ":" << std::setbase(10) << line << std::endl; - fullmessage = strdup(msg.str().c_str()); - } + Exception(int code, const char* message, const char* file, int line): + code(code), line(line), file(file) + { + std::stringstream msg; + msg << "Exception code : 0x" << std::setbase(16) << code << std::endl; + msg << "Message : " << message << std::endl; + if (logLevel >= LG_LOG_DEBUG) + msg << "File : " << file << ":" << std::setbase(10) << line << std::endl; + fullmessage = strdup(msg.str().c_str()); + } - Exception(const Exception& other) - { - this->code = other.code; - this->line = line; - this->file = file; - this->fullmessage = strdup(other.fullmessage); - } + Exception(const Exception& other) + { + this->code = other.code; + this->line = line; + this->file = file; + this->fullmessage = strdup(other.fullmessage); + } - ~Exception() _NOEXCEPT - { - free(fullmessage); - } + ~Exception() _NOEXCEPT + { + free(fullmessage); + } - const char * what () const throw () { return fullmessage; } - - int getErrorCode() {return code;} - - private: - int code, line; - const char* message, *file; - char* fullmessage; + const char * what () const throw () { return fullmessage; } + + int getErrorCode() {return code;} + + private: + int code, line; + const char* message, *file; + char* fullmessage; }; /** * @brief Throw an exception */ -#define EXCEPTION(code, message) \ +#define EXCEPTION(code, message) \ {std::stringstream __msg;__msg << message; throw gourou::Exception(code, __msg.str().c_str(), __FILE__, __LINE__);} /** @@ -197,15 +197,15 @@ namespace gourou class StringXMLWriter : public pugi::xml_writer { public: - virtual void write(const void* data, size_t size) - { - result.append(static_cast(data), size); - } + virtual void write(const void* data, size_t size) + { + result.append(static_cast(data), size); + } - const std::string& getResult() {return result;} + const std::string& getResult() {return result;} private: - std::string result; + std::string result; }; static const char* ws = " \t\n\r\f\v"; @@ -215,8 +215,8 @@ namespace gourou */ inline std::string& rtrim(std::string& s, const char* t = ws) { - s.erase(s.find_last_not_of(t) + 1); - return s; + s.erase(s.find_last_not_of(t) + 1); + return s; } /** @@ -224,8 +224,8 @@ namespace gourou */ inline std::string& ltrim(std::string& s, const char* t = ws) { - s.erase(0, s.find_first_not_of(t)); - return s; + s.erase(0, s.find_first_not_of(t)); + return s; } /** @@ -233,7 +233,7 @@ namespace gourou */ inline std::string& trim(std::string& s, const char* t = ws) { - return ltrim(rtrim(s, t), t); + return ltrim(rtrim(s, t), t); } static inline pugi::xml_node getNode(const pugi::xml_node& root, const char* tagName, bool throwOnNull=true) @@ -241,17 +241,17 @@ namespace gourou pugi::xpath_node xpath_node = root.select_node(tagName); if (!xpath_node) - { - if (throwOnNull) - EXCEPTION(GOUROU_TAG_NOT_FOUND, "Tag " << tagName << " not found"); - - return pugi::xml_node(); - } + { + if (throwOnNull) + EXCEPTION(GOUROU_TAG_NOT_FOUND, "Tag " << tagName << " not found"); - return xpath_node.node(); + return pugi::xml_node(); + } + + return xpath_node.node(); } - + /** * @brief Extract text node from tag in document * It can throw an exception if tag does not exists @@ -259,19 +259,19 @@ namespace gourou */ static inline std::string extractTextElem(const pugi::xml_node& root, const char* tagName, bool throwOnNull=true) { - pugi::xml_node node = getNode(root, tagName, throwOnNull); + pugi::xml_node node = getNode(root, tagName, throwOnNull); - node = node.first_child(); + node = node.first_child(); + + if (!node) + { + if (throwOnNull) + EXCEPTION(GOUROU_TAG_NOT_FOUND, "Text element for tag " << tagName << " not found"); - if (!node) - { - if (throwOnNull) - EXCEPTION(GOUROU_TAG_NOT_FOUND, "Text element for tag " << tagName << " not found"); - return ""; - } + } - std::string res = node.value(); + std::string res = node.value(); return trim(res); } @@ -280,23 +280,23 @@ namespace gourou * It can throw an exception if tag does not exists */ static inline void setTextElem(const pugi::xml_node& root, const char* tagName, - const std::string& value, bool throwOnNull=true) + const std::string& value, bool throwOnNull=true) { - pugi::xml_node node = getNode(root, tagName, throwOnNull); + pugi::xml_node node = getNode(root, tagName, throwOnNull); - if (!node) - { - if (throwOnNull) - EXCEPTION(GOUROU_TAG_NOT_FOUND, "Text element for tag " << tagName << " not found"); + if (!node) + { + if (throwOnNull) + EXCEPTION(GOUROU_TAG_NOT_FOUND, "Text element for tag " << tagName << " not found"); return; - } + } - node = node.first_child(); + node = node.first_child(); - if (!node) - node.append_child(pugi::node_pcdata).set_value(value.c_str()); - else - node.set_value(value.c_str()); + if (!node) + node.append_child(pugi::node_pcdata).set_value(value.c_str()); + else + node.set_value(value.c_str()); } /** @@ -306,20 +306,20 @@ namespace gourou */ static inline std::string extractTextAttribute(const pugi::xml_node& root, const char* tagName, const char* attributeName, bool throwOnNull=true) { - pugi::xml_node node = getNode(root, tagName, throwOnNull); + pugi::xml_node node = getNode(root, tagName, throwOnNull); - pugi::xml_attribute attr = node.attribute(attributeName); + pugi::xml_attribute attr = node.attribute(attributeName); + + if (!attr) + { + if (throwOnNull) + EXCEPTION(GOUROU_TAG_NOT_FOUND, "Attribute element " << attributeName << " for tag " << tagName << " not found"); - if (!attr) - { - if (throwOnNull) - EXCEPTION(GOUROU_TAG_NOT_FOUND, "Attribute element " << attributeName << " for tag " << tagName << " not found"); - return ""; - } + } - std::string res = attr.value(); - return trim(res); + std::string res = attr.value(); + return trim(res); } /** @@ -331,8 +331,8 @@ namespace gourou */ static inline void appendTextElem(pugi::xml_node& root, const std::string& name, const std::string& value) { - pugi::xml_node node = root.append_child(name.c_str()); - node.append_child(pugi::node_pcdata).set_value(value.c_str()); + pugi::xml_node node = root.append_child(name.c_str()); + node.append_child(pugi::node_pcdata).set_value(value.c_str()); } /** @@ -343,21 +343,21 @@ namespace gourou */ static inline std::string extractIdFromUUID(const std::string& uuid) { - unsigned int i = 0; - std::string res; - - if (uuid.find("urn:uuid:") == 0) - i = 9; + unsigned int i = 0; + std::string res; - for(; i properties; + std::map properties; }; } diff --git a/include/user.h b/include/user.h index 4966dc5..8240dab 100644 --- a/include/user.h +++ b/include/user.h @@ -30,7 +30,7 @@ namespace gourou { class DRMProcessor; - + /** * @brief This class is a container for activation.xml (activation info). It should not be used by user. */ @@ -39,73 +39,73 @@ namespace gourou public: User(DRMProcessor* processor, const std::string& activationFile); - /** - * @brief Retrieve some values from activation.xml - */ - std::string& getUUID(); - std::string& getPKCS12(); - std::string& getDeviceUUID(); - std::string& getDeviceFingerprint(); - std::string& getUsername(); - std::string& getLoginMethod(); - std::string getLicenseServiceCertificate(std::string url); - std::string& getAuthenticationCertificate(); - std::string& getPrivateLicenseKey(); + /** + * @brief Retrieve some values from activation.xml + */ + std::string& getUUID(); + std::string& getPKCS12(); + std::string& getDeviceUUID(); + std::string& getDeviceFingerprint(); + std::string& getUsername(); + std::string& getLoginMethod(); + std::string getLicenseServiceCertificate(std::string url); + std::string& getAuthenticationCertificate(); + std::string& getPrivateLicenseKey(); - /** - * @brief Read activation.xml and put result into doc - */ - void readActivation(pugi::xml_document& doc); + /** + * @brief Read activation.xml and put result into doc + */ + void readActivation(pugi::xml_document& doc); - /** - * @brief Update activation.xml with new data - */ - void updateActivationFile(const char* data); + /** + * @brief Update activation.xml with new data + */ + void updateActivationFile(const char* data); - /** - * @brief Update activation.xml with doc data - */ - void updateActivationFile(const pugi::xml_document& doc); + /** + * @brief Update activation.xml with doc data + */ + void updateActivationFile(const pugi::xml_document& doc); - /** - * @brief Get one value of activation.xml - */ - std::string getProperty(const std::string property); - - /** - * @brief Get all nodes with property name - */ - pugi::xpath_node_set getProperties(const std::string property); - - /** - * @brief Create activation.xml and devicesalt files if they did not exists - * - * @param processor Instance of DRMProcessor - * @param dirName Directory where to put files (.adept) - * @param ACSServer Server used for signIn - */ - static User* createUser(DRMProcessor* processor, const std::string& dirName, const std::string& ACSServer); + /** + * @brief Get one value of activation.xml + */ + std::string getProperty(const std::string property); + + /** + * @brief Get all nodes with property name + */ + pugi::xpath_node_set getProperties(const std::string property); + + /** + * @brief Create activation.xml and devicesalt files if they did not exists + * + * @param processor Instance of DRMProcessor + * @param dirName Directory where to put files (.adept) + * @param ACSServer Server used for signIn + */ + static User* createUser(DRMProcessor* processor, const std::string& dirName, const std::string& ACSServer); private: - DRMProcessor* processor; - pugi::xml_document activationDoc; - - std::string activationFile; - std::string pkcs12; - std::string uuid; - std::string deviceUUID; - std::string deviceFingerprint; - std::string username; - std::string loginMethod; - std::map licenseServiceCertificates; - std::string authenticationCertificate; - std::string privateLicenseKey; + DRMProcessor* processor; + pugi::xml_document activationDoc; - User(DRMProcessor* processor); - - void parseActivationFile(bool throwOnNull=true); - ByteArray signIn(const std::string& adobeID, const std::string& adobePassword, - ByteArray authenticationCertificate); + std::string activationFile; + std::string pkcs12; + std::string uuid; + std::string deviceUUID; + std::string deviceFingerprint; + std::string username; + std::string loginMethod; + std::map licenseServiceCertificates; + std::string authenticationCertificate; + std::string privateLicenseKey; + + User(DRMProcessor* processor); + + void parseActivationFile(bool throwOnNull=true); + ByteArray signIn(const std::string& adobeID, const std::string& adobePassword, + ByteArray authenticationCertificate); }; } diff --git a/src/bytearray.cpp b/src/bytearray.cpp index f7ec87f..fb8fd54 100644 --- a/src/bytearray.cpp +++ b/src/bytearray.cpp @@ -26,232 +26,232 @@ namespace gourou { std::map ByteArray::refCounter; - + ByteArray::ByteArray(bool useMalloc):_useMalloc(useMalloc), _data(0), _length(0) {} ByteArray::ByteArray(unsigned int length, bool useMalloc): - _useMalloc(useMalloc) + _useMalloc(useMalloc) { - initData(0, length); + initData(0, length); } - - ByteArray::ByteArray(const unsigned char* data, unsigned int length): - _useMalloc(false) - { - initData(data, length); - } - - ByteArray::ByteArray(const char* data, int length): - _useMalloc(false) - { - if (length == -1) - length = strlen(data); - initData((unsigned char*)data, (unsigned int) length); - } - - ByteArray::ByteArray(const std::string& str): - _useMalloc(false) + ByteArray::ByteArray(const unsigned char* data, unsigned int length): + _useMalloc(false) { - initData((unsigned char*)str.c_str(), (unsigned int)str.length()); + initData(data, length); + } + + ByteArray::ByteArray(const char* data, int length): + _useMalloc(false) + { + if (length == -1) + length = strlen(data); + + initData((unsigned char*)data, (unsigned int) length); + } + + ByteArray::ByteArray(const std::string& str): + _useMalloc(false) + { + initData((unsigned char*)str.c_str(), (unsigned int)str.length()); } void ByteArray::initData(const unsigned char* data, unsigned int length) { - if (_useMalloc) - _data = (unsigned char*)malloc(length); - else - _data = new unsigned char[length]; + if (_useMalloc) + _data = (unsigned char*)malloc(length); + else + _data = new unsigned char[length]; - if (data) - memcpy((void*)_data, data, length); + if (data) + memcpy((void*)_data, data, length); - _length = length; + _length = length; - addRef(); + addRef(); } - + ByteArray::ByteArray(const ByteArray& other) { - this->_useMalloc = other._useMalloc; - this->_data = other._data; - this->_length = other._length; + this->_useMalloc = other._useMalloc; + this->_data = other._data; + this->_length = other._length; - addRef(); + addRef(); } ByteArray& ByteArray::operator=(const ByteArray& other) { - delRef(); - - this->_useMalloc = other._useMalloc; - this->_data = other._data; - this->_length = other._length; + delRef(); - addRef(); - - return *this; + this->_useMalloc = other._useMalloc; + this->_data = other._data; + this->_length = other._length; + + addRef(); + + return *this; } - + ByteArray::~ByteArray() { - delRef(); + delRef(); } - + void ByteArray::addRef() { - if (!_data) return; + if (!_data) return; - if (refCounter.count(_data) == 0) - refCounter[_data] = 1; - else - refCounter[_data]++; + if (refCounter.count(_data) == 0) + refCounter[_data] = 1; + else + refCounter[_data]++; } - + void ByteArray::delRef() { - if (!_data) return; - - if (refCounter[_data] == 1) - { - if (_useMalloc) - free(_data); - else - delete[] _data; - refCounter.erase(_data); - } - else - refCounter[_data]--; + if (!_data) return; + + if (refCounter[_data] == 1) + { + if (_useMalloc) + free(_data); + else + delete[] _data; + refCounter.erase(_data); + } + else + refCounter[_data]--; } ByteArray ByteArray::fromBase64(const ByteArray& other) { - std::string b64; + std::string b64; - macaron::Base64::Decode(std::string((char*)other._data, other._length), b64); + macaron::Base64::Decode(std::string((char*)other._data, other._length), b64); - return ByteArray(b64); + return ByteArray(b64); } - + ByteArray ByteArray::fromBase64(const char* data, int length) { - std::string b64; + std::string b64; - if (length == -1) - length = strlen(data); - - macaron::Base64::Decode(std::string(data, length), b64); + if (length == -1) + length = strlen(data); - return ByteArray(b64); + 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()); + return ByteArray::fromBase64(str.c_str(), str.length()); } - + std::string ByteArray::toBase64() { - return macaron::Base64::Encode(std::string((char*)_data, _length)); + return macaron::Base64::Encode(std::string((char*)_data, _length)); } ByteArray ByteArray::fromHex(const std::string& str) { - if (str.size() % 2) - throw std::invalid_argument("Size of hex string not multiple of 2"); + if (str.size() % 2) + throw std::invalid_argument("Size of hex string not multiple of 2"); - ByteArray res((unsigned int)(str.size()/2)); - unsigned int i; + ByteArray res((unsigned int)(str.size()/2)); + unsigned int i; - unsigned char* data = res.data(); - unsigned char cur, tmp; - - for (i=0; i= 'a' && tmp <= 'f') - cur = (tmp - 'a' + 10) << 4; - else if (tmp >= 'A' && tmp <= 'F') - cur = (tmp - 'A' + 10) << 4; - else if (tmp >= '0' && tmp <= '9') - cur = (tmp - '0') << 4; - else - throw std::invalid_argument("Invalid character in hex string"); + for (i=0; i= 'a' && tmp <= 'f') - cur += tmp - 'a' + 10; - else if (tmp >= 'A' && tmp <= 'F') - cur += tmp - 'A' + 10; - else if (tmp >= '0' && tmp <= '9') - cur += tmp - '0'; - else - throw std::invalid_argument("Invalid character in hex string"); + tmp = str[i]; + if (tmp >= 'a' && tmp <= 'f') + cur = (tmp - 'a' + 10) << 4; + else if (tmp >= 'A' && tmp <= 'F') + cur = (tmp - 'A' + 10) << 4; + else if (tmp >= '0' && tmp <= '9') + cur = (tmp - '0') << 4; + else + throw std::invalid_argument("Invalid character in hex string"); - data[i/2] = cur; - } - - return res; + tmp = str[i+1]; + if (tmp >= 'a' && tmp <= 'f') + cur += tmp - 'a' + 10; + else if (tmp >= 'A' && tmp <= 'F') + cur += tmp - 'A' + 10; + else if (tmp >= '0' && tmp <= '9') + cur += tmp - '0'; + else + throw std::invalid_argument("Invalid character in hex string"); + + data[i/2] = cur; + } + + return res; } - + std::string ByteArray::toHex() { - char* tmp = new char[_length*2+1]; + char* tmp = new char[_length*2+1]; - for(int i=0; i<(int)_length; i++) - sprintf(&tmp[i*2], "%02x", _data[i]); + for(int i=0; i<(int)_length; i++) + sprintf(&tmp[i*2], "%02x", _data[i]); - tmp[_length*2] = 0; + tmp[_length*2] = 0; - std::string res = tmp; - delete[] tmp; - - return res; + std::string res = tmp; + delete[] tmp; + + return res; } void ByteArray::append(const unsigned char* data, unsigned int length) { - if (!length) - return; - - unsigned int oldLength = _length; + if (!length) + return; - resize(_length+length, true); + unsigned int oldLength = _length; - memcpy(&_data[oldLength], data, length); + resize(_length+length, true); + + memcpy(&_data[oldLength], data, length); } - + void ByteArray::append(unsigned char c) { append(&c, 1);} void ByteArray::append(const char* str) { append((const unsigned char*)str, strlen(str));} void ByteArray::append(const std::string& str) { append((const unsigned char*)str.c_str(), str.length()); } void ByteArray::resize(unsigned length, bool keepData) { - if (length == _length) - return; - else if (length < _length) - _length = length ; // Don't touch data - else // New size > - { - unsigned char* newData; + if (length == _length) + return; + else if (length < _length) + _length = length ; // Don't touch data + else // New size > + { + unsigned char* newData; - if (_useMalloc) - newData = (unsigned char*)malloc(_length+length); - else - newData = new unsigned char[_length+length]; + if (_useMalloc) + newData = (unsigned char*)malloc(_length+length); + else + newData = new unsigned char[_length+length]; - if (keepData) - memcpy(newData, _data, _length); - - delRef(); + if (keepData) + memcpy(newData, _data, _length); - _length = length; - _data = newData; - - addRef(); - } + delRef(); + + _length = length; + _data = newData; + + addRef(); + } } } diff --git a/src/device.cpp b/src/device.cpp index 1418d47..75e603e 100644 --- a/src/device.cpp +++ b/src/device.cpp @@ -36,7 +36,7 @@ #include #include #elif (defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__) \ - || defined(__bsdi__) || defined(__DragonFly__) || defined(__APPLE__)) + || defined(__bsdi__) || defined(__DragonFly__) || defined(__APPLE__)) #include #include #include @@ -78,8 +78,8 @@ int get_mac_address(unsigned char* mac_address) if (success) { - memcpy(mac_address, ifr.ifr_hwaddr.sa_data, 6); - return 0; + memcpy(mac_address, ifr.ifr_hwaddr.sa_data, 6); + return 0; } return 1; @@ -110,7 +110,7 @@ int get_mac_address(unsigned char* mac_address, const char* if_name = "en0") int get_mac_address(unsigned char* mac_address) { GOUROU_LOG(INFO, "get_mac_address() not implemented for your platform, using a static address"); - + mac_address[0] = 0x8D; mac_address[1] = 0x70; mac_address[2] = 0x13; @@ -126,227 +126,227 @@ int get_mac_address(unsigned char* mac_address) namespace gourou { Device::Device(DRMProcessor* processor): - processor(processor) + processor(processor) {} - + Device::Device(DRMProcessor* processor, const std::string& deviceFile, const std::string& deviceKeyFile): - processor(processor), deviceFile(deviceFile), deviceKeyFile(deviceKeyFile) + processor(processor), deviceFile(deviceFile), deviceKeyFile(deviceKeyFile) { - parseDeviceKeyFile(); - parseDeviceFile(); + parseDeviceKeyFile(); + parseDeviceFile(); } /* SHA1(uid ":" username ":" macaddress ":" */ std::string Device::makeSerial(bool random) { - unsigned char sha_out[SHA1_LEN]; - DRMProcessorClient* client = processor->getClient(); - - 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}; + unsigned char sha_out[SHA1_LEN]; + DRMProcessorClient* client = processor->getClient(); - get_mac_address(mac_address); + 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}; - 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); + get_mac_address(mac_address); - delete[] dataToHash; - } - else - { - client->randBytes(sha_out, sizeof(sha_out)); - } + int dataToHashLen = 10 /* UID */ + strlen(passwd->pw_name) + sizeof(mac_address)*2 /*mac address*/ + 1 /* \0 */; + dataToHashLen += 8; /* Separators */ + unsigned char* dataToHash = new unsigned char[dataToHashLen]; + dataToHashLen = snprintf((char*)dataToHash, dataToHashLen, "%d:%s:%02x:%02x:%02x:%02x:%02x:%02x:", + uid, passwd->pw_name, + mac_address[0], mac_address[1], mac_address[2], + mac_address[3], mac_address[4], mac_address[5]); - std::string res = ByteArray((const char*)sha_out, DEVICE_SERIAL_LEN).toHex(); - GOUROU_LOG(DEBUG, "Serial : " << res); - return res; + 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]; + DRMProcessorClient* client = processor->getClient(); + unsigned char sha_out[SHA1_LEN]; - void* handler = client->createDigest("SHA1"); - client->digestUpdate(handler, (unsigned char*) serial.c_str(), serial.length()); - client->digestUpdate(handler, deviceKey, sizeof(deviceKey)); - client->digestFinalize(handler, sha_out); + void* handler = client->createDigest("SHA1"); + client->digestUpdate(handler, (unsigned char*) serial.c_str(), serial.length()); + client->digestUpdate(handler, deviceKey, sizeof(deviceKey)); + client->digestFinalize(handler, sha_out); - std::string res = ByteArray(sha_out, sizeof(sha_out)).toBase64(); - GOUROU_LOG(DEBUG, "Fingerprint : " << res); - return res; + std::string res = ByteArray(sha_out, sizeof(sha_out)).toBase64(); + GOUROU_LOG(DEBUG, "Fingerprint : " << res); + return res; } - + void Device::createDeviceFile(const std::string& hobbes, bool randomSerial) { - struct utsname sysname; - uname(&sysname); + struct utsname sysname; + uname(&sysname); - std::string serial = makeSerial(randomSerial); - std::string fingerprint = makeFingerprint(serial); - - 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; + std::string serial = makeSerial(randomSerial); + std::string fingerprint = makeFingerprint(serial); - appendTextElem(root, "adept:deviceClass", "Desktop"); - appendTextElem(root, "adept:deviceSerial", serial); - appendTextElem(root, "adept:deviceName", sysname.nodename); - appendTextElem(root, "adept:deviceType", "standalone"); + pugi::xml_document deviceDoc; + pugi::xml_node decl = deviceDoc.append_child(pugi::node_declaration); + decl.append_attribute("version") = "1.0"; - 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); + pugi::xml_node root = deviceDoc.append_child("adept:deviceInfo"); + root.append_attribute("xmlns:adept") = ADOBE_ADEPT_NS; - appendTextElem(root, "adept:fingerprint", fingerprint); + appendTextElem(root, "adept:deviceClass", "Desktop"); + appendTextElem(root, "adept:deviceSerial", serial); + appendTextElem(root, "adept:deviceName", sysname.nodename); + appendTextElem(root, "adept:deviceType", "standalone"); - StringXMLWriter xmlWriter; - deviceDoc.save(xmlWriter, " "); + pugi::xml_node version = root.append_child("adept:version"); + version.append_attribute("name") = "hobbes"; + version.append_attribute("value") = hobbes.c_str(); - GOUROU_LOG(DEBUG, "Create device file " << deviceFile); + 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(); - writeFile(deviceFile, xmlWriter.getResult()); + 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]; + unsigned char key[DEVICE_KEY_SIZE]; - GOUROU_LOG(DEBUG, "Create device key file " << deviceKeyFile); + GOUROU_LOG(DEBUG, "Create device key file " << deviceKeyFile); - processor->getClient()->randBytes(key, sizeof(key)); + processor->getClient()->randBytes(key, sizeof(key)); - writeFile(deviceKeyFile, key, sizeof(key)); + writeFile(deviceKeyFile, key, sizeof(key)); } - + Device* Device::createDevice(DRMProcessor* processor, const std::string& dirName, const std::string& hobbes, bool randomSerial) { - struct stat _stat; + struct stat _stat; - if (stat(dirName.c_str(), &_stat) != 0) - { - if (mkdir_p(dirName.c_str(), S_IRWXU)) - EXCEPTION(DEV_MKPATH, "Unable to create " << dirName) - } + if (stat(dirName.c_str(), &_stat) != 0) + { + if (mkdir_p(dirName.c_str(), S_IRWXU)) + EXCEPTION(DEV_MKPATH, "Unable to create " << dirName) + } - Device* device = new Device(processor); + Device* device = new Device(processor); - device->deviceFile = dirName + "/device.xml"; - device->deviceKeyFile = dirName + "/devicesalt"; + device->deviceFile = dirName + "/device.xml"; + device->deviceKeyFile = dirName + "/devicesalt"; - try - { - device->parseDeviceKeyFile(); - } - catch (...) - { - device->createDeviceKeyFile(); - device->parseDeviceKeyFile(); - } + try + { + device->parseDeviceKeyFile(); + } + catch (...) + { + device->createDeviceKeyFile(); + device->parseDeviceKeyFile(); + } - try - { - device->parseDeviceFile(); - } - catch (...) - { - device->createDeviceFile(hobbes, randomSerial); - device->parseDeviceFile(); - } - - return device; + try + { + device->parseDeviceFile(); + } + catch (...) + { + device->createDeviceFile(hobbes, randomSerial); + device->parseDeviceFile(); + } + + return device; } - + const unsigned char* Device::getDeviceKey() { - return deviceKey; + return deviceKey; } void Device::parseDeviceFile() { - pugi::xml_document doc; + pugi::xml_document doc; - if (!doc.load_file(deviceFile.c_str())) - EXCEPTION(DEV_INVALID_DEVICE_FILE, "Invalid device file"); + if (!doc.load_file(deviceFile.c_str())) + EXCEPTION(DEV_INVALID_DEVICE_FILE, "Invalid device file"); - try - { - properties["deviceClass"] = gourou::extractTextElem(doc, "/adept:deviceInfo/adept:deviceClass"); - properties["deviceSerial"] = gourou::extractTextElem(doc, "/adept:deviceInfo/adept:deviceSerial"); - properties["deviceName"] = gourou::extractTextElem(doc, "/adept:deviceInfo/adept:deviceName"); - properties["deviceType"] = gourou::extractTextElem(doc, "/adept:deviceInfo/adept:deviceType"); - properties["fingerprint"] = gourou::extractTextElem(doc, "/adept:deviceInfo/adept:fingerprint"); + try + { + properties["deviceClass"] = gourou::extractTextElem(doc, "/adept:deviceInfo/adept:deviceClass"); + properties["deviceSerial"] = gourou::extractTextElem(doc, "/adept:deviceInfo/adept:deviceSerial"); + properties["deviceName"] = gourou::extractTextElem(doc, "/adept:deviceInfo/adept:deviceName"); + properties["deviceType"] = gourou::extractTextElem(doc, "/adept:deviceInfo/adept:deviceType"); + properties["fingerprint"] = gourou::extractTextElem(doc, "/adept:deviceInfo/adept:fingerprint"); - pugi::xpath_node_set nodeSet = doc.select_nodes("/adept:deviceInfo/adept:version"); + pugi::xpath_node_set nodeSet = doc.select_nodes("/adept:deviceInfo/adept:version"); - for (pugi::xpath_node_set::const_iterator it = nodeSet.begin(); - it != nodeSet.end(); ++it) - { - pugi::xml_node node = it->node(); - pugi::xml_attribute name = node.attribute("name"); - pugi::xml_attribute value = node.attribute("value"); + for (pugi::xpath_node_set::const_iterator it = nodeSet.begin(); + it != nodeSet.end(); ++it) + { + pugi::xml_node node = it->node(); + pugi::xml_attribute name = node.attribute("name"); + pugi::xml_attribute value = node.attribute("value"); - properties[name.value()] = value.value(); - } - } - catch (gourou::Exception& e) - { - EXCEPTION(DEV_INVALID_DEVICE_FILE, "Invalid device file"); - } + properties[name.value()] = value.value(); + } + } + catch (gourou::Exception& e) + { + EXCEPTION(DEV_INVALID_DEVICE_FILE, "Invalid device file"); + } } void Device::parseDeviceKeyFile() { - struct stat _stat; + struct stat _stat; - if (stat(deviceKeyFile.c_str(), &_stat) == 0 && - _stat.st_size == DEVICE_KEY_SIZE) - { - readFile(deviceKeyFile, deviceKey, sizeof(deviceKey)); - } - else - EXCEPTION(DEV_INVALID_DEVICE_KEY_FILE, "Invalid device key file"); + if (stat(deviceKeyFile.c_str(), &_stat) == 0 && + _stat.st_size == DEVICE_KEY_SIZE) + { + readFile(deviceKeyFile, deviceKey, sizeof(deviceKey)); + } + else + EXCEPTION(DEV_INVALID_DEVICE_KEY_FILE, "Invalid device key file"); } std::string Device::getProperty(const std::string& property, const std::string& _default) { - if (properties.find(property) == properties.end()) - { - if (_default == "") - EXCEPTION(DEV_INVALID_DEV_PROPERTY, "Invalid property " << property); + if (properties.find(property) == properties.end()) + { + if (_default == "") + EXCEPTION(DEV_INVALID_DEV_PROPERTY, "Invalid property " << property); - return _default; - } + return _default; + } - return properties[property]; + return properties[property]; } std::string Device::operator[](const std::string& property) { - return getProperty(property); + return getProperty(property); } } diff --git a/src/fulfillment_item.cpp b/src/fulfillment_item.cpp index dbd8d31..c3ac17f 100644 --- a/src/fulfillment_item.cpp +++ b/src/fulfillment_item.cpp @@ -25,109 +25,109 @@ namespace gourou { FulfillmentItem::FulfillmentItem(pugi::xml_document& doc, User* user) - : fulfillDoc(), loanToken(0) + : fulfillDoc(), loanToken(0) { - fulfillDoc.reset(doc); /* We must keep a copy */ - metadatas = fulfillDoc.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(); + fulfillDoc.reset(doc); /* We must keep a copy */ + metadatas = fulfillDoc.select_node("//metadata").node(); - if (downloadURL == "") - EXCEPTION(FFI_INVALID_FULFILLMENT_DATA, "No download URL in document"); - - node = doc.select_node("/envelope/fulfillmentResult/resourceItemInfo/resource").node(); - resource = node.first_child().value(); + if (!metadatas) + EXCEPTION(FFI_INVALID_FULFILLMENT_DATA, "No metadata tag in document"); - if (resource == "") - EXCEPTION(FFI_INVALID_FULFILLMENT_DATA, "No resource in document"); + pugi::xml_node node = doc.select_node("/envelope/fulfillmentResult/resourceItemInfo/src").node(); + downloadURL = node.first_child().value(); - pugi::xml_node licenseToken = doc.select_node("/envelope/fulfillmentResult/resourceItemInfo/licenseToken").node(); + if (downloadURL == "") + EXCEPTION(FFI_INVALID_FULFILLMENT_DATA, "No download URL in document"); - if (!licenseToken) - EXCEPTION(FFI_INVALID_FULFILLMENT_DATA, "Any license token in document"); - - buildRights(licenseToken, user); + node = doc.select_node("/envelope/fulfillmentResult/resourceItemInfo/resource").node(); + resource = node.first_child().value(); - node = doc.select_node("/envelope/fulfillmentResult/returnable").node(); - try - { - if (node && node.first_child().value() == std::string("true")) - loanToken = new LoanToken(doc); - } - catch(std::exception& e) - { - GOUROU_LOG(ERROR, "Book is returnable, but contains invalid loan token"); - GOUROU_LOG(ERROR, e.what()); - } + if (resource == "") + EXCEPTION(FFI_INVALID_FULFILLMENT_DATA, "No resource in document"); + + pugi::xml_node licenseToken = doc.select_node("/envelope/fulfillmentResult/resourceItemInfo/licenseToken").node(); + + if (!licenseToken) + EXCEPTION(FFI_INVALID_FULFILLMENT_DATA, "Any license token in document"); + + buildRights(licenseToken, user); + + node = doc.select_node("/envelope/fulfillmentResult/returnable").node(); + try + { + if (node && node.first_child().value() == std::string("true")) + loanToken = new LoanToken(doc); + } + catch(std::exception& e) + { + GOUROU_LOG(ERROR, "Book is returnable, but contains invalid loan token"); + GOUROU_LOG(ERROR, e.what()); + } } FulfillmentItem::~FulfillmentItem() { - if (loanToken) delete loanToken; + if (loanToken) delete loanToken; } - + 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 decl = rights.append_child(pugi::node_declaration); + decl.append_attribute("version") = "1.0"; - pugi::xml_node licenseServiceInfo = root.append_child("adept:licenseServiceInfo"); - pugi::xml_node licenseURL = licenseToken.select_node("licenseURL").node(); - licenseURL.set_name("adept:licenseURL"); - licenseServiceInfo.append_copy(licenseURL); - pugi::xml_node certificate = licenseServiceInfo.append_child("adept:certificate"); - std::string certificateValue = user->getLicenseServiceCertificate(licenseURL.first_child().value()); - certificate.append_child(pugi::node_pcdata).set_value(certificateValue.c_str()); + pugi::xml_node root = rights.append_child("adept:rights"); + root.append_attribute("xmlns:adept") = ADOBE_ADEPT_NS; + + pugi::xml_node newLicenseToken = root.append_copy(licenseToken); + if (!newLicenseToken.attribute("xmlns")) + newLicenseToken.append_attribute("xmlns") = ADOBE_ADEPT_NS; + + pugi::xml_node licenseServiceInfo = root.append_child("adept:licenseServiceInfo"); + pugi::xml_node licenseURL = licenseToken.select_node("licenseURL").node(); + licenseURL.set_name("adept:licenseURL"); + licenseServiceInfo.append_copy(licenseURL); + pugi::xml_node certificate = licenseServiceInfo.append_child("adept:certificate"); + std::string certificateValue = user->getLicenseServiceCertificate(licenseURL.first_child().value()); + certificate.append_child(pugi::node_pcdata).set_value(certificateValue.c_str()); } - + std::string FulfillmentItem::getMetadata(std::string name) { - // https://stackoverflow.com/questions/313970/how-to-convert-an-instance-of-stdstring-to-lower-case - #if __STDC_VERSION__ >= 201112L - std::transform(name.begin(), name.end(), name.begin(), - [](unsigned char c){ return std::tolower(c); }); - #else - std::transform(name.begin(), name.end(), name.begin(), tolower); - #endif - name = std::string("dc:") + name; - pugi::xpath_node path = metadatas.select_node(name.c_str()); + // https://stackoverflow.com/questions/313970/how-to-convert-an-instance-of-stdstring-to-lower-case +#if __STDC_VERSION__ >= 201112L + std::transform(name.begin(), name.end(), name.begin(), + [](unsigned char c){ return std::tolower(c); }); +#else + std::transform(name.begin(), name.end(), name.begin(), tolower); +#endif + name = std::string("dc:") + name; + pugi::xpath_node path = metadatas.select_node(name.c_str()); - if (!path) - return ""; + if (!path) + return ""; - return path.node().first_child().value(); + return path.node().first_child().value(); } - + std::string FulfillmentItem::getRights() { - StringXMLWriter xmlWriter; - rights.save(xmlWriter, " "); - return xmlWriter.getResult(); + StringXMLWriter xmlWriter; + rights.save(xmlWriter, " "); + return xmlWriter.getResult(); } - + std::string FulfillmentItem::getDownloadURL() { - return downloadURL; + return downloadURL; } std::string FulfillmentItem::getResource() { - return resource; + return resource; } LoanToken* FulfillmentItem::getLoanToken() { - return loanToken; + return loanToken; } } diff --git a/src/libgourou.cpp b/src/libgourou.cpp index 4a1da8a..d9b15ce 100644 --- a/src/libgourou.cpp +++ b/src/libgourou.cpp @@ -42,1027 +42,1027 @@ namespace gourou { GOUROU_LOG_LEVEL logLevel = LG_LOG_WARN; const std::string DRMProcessor::VERSION = LIBGOUROU_VERSION; - + DRMProcessor::DRMProcessor(DRMProcessorClient* client):client(client), device(0), user(0) { - if (!client) - EXCEPTION(GOUROU_INVALID_CLIENT, "DRMProcessorClient is NULL"); + if (!client) + EXCEPTION(GOUROU_INVALID_CLIENT, "DRMProcessorClient is NULL"); } DRMProcessor::DRMProcessor(DRMProcessorClient* client, - const std::string& deviceFile, const std::string& activationFile, - const std::string& deviceKeyFile): - client(client), device(0), user(0) + const std::string& deviceFile, const std::string& activationFile, + const std::string& deviceKeyFile): + client(client), device(0), user(0) { - if (!client) - EXCEPTION(GOUROU_INVALID_CLIENT, "DRMProcessorClient is NULL"); - - device = new Device(this, deviceFile, deviceKeyFile); - user = new User(this, activationFile); + if (!client) + EXCEPTION(GOUROU_INVALID_CLIENT, "DRMProcessorClient is NULL"); - if (user->getDeviceFingerprint() != "" && - (*device)["fingerprint"] != user->getDeviceFingerprint()) - EXCEPTION(GOUROU_DEVICE_DOES_NOT_MATCH, "User and device fingerprint does not match"); + 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; + if (device) delete device; + if (user) delete user; } // function to parse a date or time string. // https://www.geeksforgeeks.org/cpp/date-and-time-parsing-in-cpp/ static time_t parseDateTime(const char* datetimeString, const char* format) { - struct tm tmStruct; - strptime(datetimeString, format, &tmStruct); - return mktime(&tmStruct); + struct tm tmStruct; + strptime(datetimeString, format, &tmStruct); + return mktime(&tmStruct); } - + DRMProcessor* DRMProcessor::createDRMProcessor(DRMProcessorClient* client, bool randomSerial, std::string dirName, - const std::string& hobbes, const std::string& ACSServer) + const std::string& hobbes, const std::string& ACSServer) { - DRMProcessor* processor = new DRMProcessor(client); + DRMProcessor* processor = new DRMProcessor(client); - if (dirName == "") - dirName = getDefaultAdeptDir(); - - Device* device = Device::createDevice(processor, dirName, hobbes, randomSerial); - processor->device = device; + if (dirName == "") + dirName = getDefaultAdeptDir(); - User* user = User::createUser(processor, dirName, ACSServer); - processor->user = user; - - return processor; + Device* device = Device::createDevice(processor, dirName, hobbes, randomSerial); + processor->device = device; + + User* user = User::createUser(processor, dirName, ACSServer); + processor->user = user; + + return processor; } - + void DRMProcessor::pushString(void* sha_ctx, const std::string& string) { - int length = string.length(); - uint16_t nlength = htons(length); - char c; + int length = string.length(); + uint16_t nlength = htons(length); + char c; - if (logLevel >= LG_LOG_TRACE) - printf("%02x %02x ", ((uint8_t*)&nlength)[0], ((uint8_t*)&nlength)[1]); - - client->digestUpdate(sha_ctx, (unsigned char*)&nlength, sizeof(nlength)); + if (logLevel >= LG_LOG_TRACE) + printf("%02x %02x ", ((uint8_t*)&nlength)[0], ((uint8_t*)&nlength)[1]); - for(int i=0; idigestUpdate(sha_ctx, (unsigned char*)&c, 1); - if (logLevel >= LG_LOG_TRACE) - printf("%c", c); - } - if (logLevel >= LG_LOG_TRACE) - printf("\n"); + client->digestUpdate(sha_ctx, (unsigned char*)&nlength, sizeof(nlength)); + + for(int i=0; idigestUpdate(sha_ctx, (unsigned char*)&c, 1); + if (logLevel >= LG_LOG_TRACE) + printf("%c", c); + } + if (logLevel >= LG_LOG_TRACE) + printf("\n"); } void DRMProcessor::pushTag(void* sha_ctx, uint8_t tag) { - client->digestUpdate(sha_ctx, &tag, sizeof(tag)); - if (logLevel >= LG_LOG_TRACE) - printf("%02x ", tag); + client->digestUpdate(sha_ctx, &tag, sizeof(tag)); + if (logLevel >= LG_LOG_TRACE) + printf("%02x ", tag); } void DRMProcessor::hashNode(const pugi::xml_node& root, void *sha_ctx, std::map nsHash) { - switch(root.type()) - { - case pugi::node_element: - { - std::string name = root.name(); + switch(root.type()) + { + case pugi::node_element: + { + std::string name = root.name(); - // Look for "xmlns[:]" attribute - for (pugi::xml_attribute_iterator ait = root.attributes_begin(); - ait != root.attributes_end(); ++ait) - { - std::string attrName(ait->name()); + // Look for "xmlns[:]" attribute + for (pugi::xml_attribute_iterator ait = root.attributes_begin(); + ait != root.attributes_end(); ++ait) + { + std::string attrName(ait->name()); - if (attrName.find("xmlns") == 0) - { - std::string ns("GENERICNS"); - // Compound xmlns:Name attribute - if (attrName.find(':') != std::string::npos) - ns = attrName.substr(attrName.find(':')+1); + if (attrName.find("xmlns") == 0) + { + std::string ns("GENERICNS"); + // Compound xmlns:Name attribute + if (attrName.find(':') != std::string::npos) + ns = attrName.substr(attrName.find(':')+1); - nsHash[ns] = ait->value(); - // Don't break here because we may multiple xmlns definitions - // break; - } - } + nsHash[ns] = ait->value(); + // Don't break here because we may multiple xmlns definitions + // break; + } + } - // Remove namespace from tag - // If we have a namespace for the first time, put it to hash - if (name.find(':') != std::string::npos) - { - size_t nsIndex = name.find(':'); - std::string nodeNS = name.substr(0, nsIndex); + // Remove namespace from tag + // If we have a namespace for the first time, put it to hash + if (name.find(':') != std::string::npos) + { + size_t nsIndex = name.find(':'); + std::string nodeNS = name.substr(0, nsIndex); - pushTag(sha_ctx, ASN_NS_TAG); - pushString(sha_ctx, nsHash[nodeNS]); - - 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); + pushTag(sha_ctx, ASN_NS_TAG); + pushString(sha_ctx, nsHash[nodeNS]); - std::vector attributes; - pugi::xml_attribute attr; - - for (attr = root.first_attribute(); - attr; attr = attr.next_attribute()) - { - if (std::string(attr.name()).find("xmlns") != std::string::npos) - continue; + 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"]); + } - attributes.push_back(attr.name()); - } + pushString(sha_ctx, name); - // Attributes must be handled in alphabetical order - std::sort(attributes.begin(), attributes.end()); + std::vector attributes; + pugi::xml_attribute attr; - std::vector::iterator attributesIt; - for(attributesIt = attributes.begin(); - attributesIt != attributes.end(); - attributesIt++) - { - attr = root.attribute(attributesIt->c_str()); - - pushTag(sha_ctx, ASN_ATTRIBUTE); - pushString(sha_ctx, ""); - - pushString(sha_ctx, attr.name()); - pushString(sha_ctx, attr.value()); - } + for (attr = root.first_attribute(); + attr; attr = attr.next_attribute()) + { + if (std::string(attr.name()).find("xmlns") != std::string::npos) + continue; - pushTag(sha_ctx, ASN_CHILD); + attributes.push_back(attr.name()); + } - for (pugi::xml_node child : root.children()) - hashNode(child, sha_ctx, nsHash); + // Attributes must be handled in alphabetical order + std::sort(attributes.begin(), attributes.end()); - pushTag(sha_ctx, ASN_END_TAG); - - break; - } - case pugi::node_pcdata: - { - std::string trimmed = root.value(); - trimmed = trim(trimmed); + std::vector::iterator attributesIt; + for(attributesIt = attributes.begin(); + attributesIt != attributes.end(); + attributesIt++) + { + attr = root.attribute(attributesIt->c_str()); - if (trimmed.length()) - { - pushTag(sha_ctx, ASN_TEXT); - pushString(sha_ctx, trimmed); - } + pushTag(sha_ctx, ASN_ATTRIBUTE); + pushString(sha_ctx, ""); - break; - } - default: - break; - } + 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 nsHash; + void* sha_ctx = client->createDigest("SHA1"); - hashNode(root, sha_ctx, nsHash); + std::map nsHash; - client->digestFinalize(sha_ctx, sha_out); - - dumpBuffer(gourou::LG_LOG_DEBUG, "\nSHA OUT : ", sha_out, SHA1_LEN); + hashNode(root, sha_ctx, nsHash); + + client->digestFinalize(sha_ctx, sha_out); + + dumpBuffer(gourou::LG_LOG_DEBUG, "\nSHA OUT : ", sha_out, SHA1_LEN); } void DRMProcessor::signNode(pugi::xml_node& rootNode) { - // Compute hash - unsigned char sha_out[SHA1_LEN]; + // 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); - - dumpBuffer(gourou::LG_LOG_DEBUG, "Sig : ", res, sizeof(res)); + hashNode(rootNode, sha_out); - std::string signature = ByteArray(res, sizeof(res)).toBase64(); - appendTextElem(rootNode, "adept:signature", signature); + // 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); + + dumpBuffer(gourou::LG_LOG_DEBUG, "Sig : ", res, sizeof(res)); + + std::string signature = ByteArray(res, sizeof(res)).toBase64(); + appendTextElem(rootNode, "adept:signature", signature); } void DRMProcessor::addNonce(pugi::xml_node& root) { - /* - 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}; + /* + 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}; #ifdef STATIC_NONCE - uint64_t bigtime = 0xAA001122BBCCAAULL; + uint64_t bigtime = 0xAA001122BBCCAAULL; #else - uint64_t bigtime = tv.tv_sec*1000; + uint64_t bigtime = tv.tv_sec*1000; #endif - nonce32[0] += (bigtime & 0xFFFFFFFF) + (tv.tv_usec/1000); - nonce32[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()); + nonce32[0] += (bigtime & 0xFFFFFFFF) + (tv.tv_usec/1000); + nonce32[1] += ((bigtime >> 32) & 0xFFFFFFFF); - time_t _time = time(0) + 10*60; // Cur time + 10 minutes - struct tm* tm_info = gmtime(&_time); - char buffer[32]; + 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()); - strftime(buffer, sizeof(buffer), "%Y-%m-%dT%H:%M:%SZ", tm_info); - appendTextElem(root, "adept:expiration", buffer); + time_t _time = time(0) + 10*60; // Cur time + 10 minutes + struct tm* tm_info = gmtime(&_time); + char buffer[32]; + + strftime(buffer, sizeof(buffer), "%Y-%m-%dT%H:%M:%SZ", tm_info); + appendTextElem(root, "adept:expiration", buffer); } - + ByteArray DRMProcessor::sendRequest(const std::string& URL, const std::string& POSTdata, const char* contentType, std::map* responseHeaders, int fd, bool resume) { - if (contentType == 0) - contentType = ""; - std::string reply = client->sendHTTPRequest(URL, POSTdata, contentType, responseHeaders, fd, resume); + if (contentType == 0) + contentType = ""; + std::string reply = client->sendHTTPRequest(URL, POSTdata, contentType, responseHeaders, fd, resume); - if (fd) return ByteArray(); - - pugi::xml_document replyDoc; - replyDoc.load_buffer(reply.c_str(), reply.length()); + if (fd) return ByteArray(); - pugi::xml_node root = replyDoc.first_child(); - if (std::string(root.name()) == "error") - { - EXCEPTION(GOUROU_ADEPT_ERROR, root.attribute("data").value()); - } - - return ByteArray(reply); + pugi::xml_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(); + StringXMLWriter xmlWriter; + document.save(xmlWriter, " "); + std::string xmlStr = xmlWriter.getResult(); - return sendRequest(url, xmlStr, (const char*)"application/vnd.adobe.adept+xml"); + return sendRequest(url, xmlStr, (const char*)"application/vnd.adobe.adept+xml"); } - + void DRMProcessor::buildAuthRequest(pugi::xml_document& authReq) { - pugi::xml_node decl = authReq.append_child(pugi::node_declaration); - decl.append_attribute("version") = "1.0"; - - pugi::xml_node root = authReq.append_child("adept:credentials"); - root.append_attribute("xmlns:adept") = ADOBE_ADEPT_NS; + pugi::xml_node decl = authReq.append_child(pugi::node_declaration); + decl.append_attribute("version") = "1.0"; - appendTextElem(root, "adept:user", user->getUUID()); + pugi::xml_node root = authReq.append_child("adept:credentials"); + root.append_attribute("xmlns:adept") = ADOBE_ADEPT_NS; - ByteArray deviceKey(device->getDeviceKey(), Device::DEVICE_KEY_SIZE); - unsigned char* pkcs12 = 0; - unsigned int pkcs12Length; - ByteArray pkcs12Cert = ByteArray::fromBase64(user->getPKCS12()); - - client->extractCertificate(pkcs12Cert.data(), pkcs12Cert.length(), - RSAInterface::RSA_KEY_PKCS12, deviceKey.toBase64().data(), - &pkcs12, &pkcs12Length); - ByteArray privateCertificate(pkcs12, pkcs12Length); - free(pkcs12); + appendTextElem(root, "adept:user", user->getUUID()); - appendTextElem(root, "adept:certificate", privateCertificate.toBase64()); - appendTextElem(root, "adept:licenseCertificate", user->getProperty("//adept:licenseCertificate")); - appendTextElem(root, "adept:authenticationCertificate", user->getProperty("//adept:authenticationCertificate")); + ByteArray deviceKey(device->getDeviceKey(), Device::DEVICE_KEY_SIZE); + unsigned char* pkcs12 = 0; + unsigned int pkcs12Length; + ByteArray pkcs12Cert = ByteArray::fromBase64(user->getPKCS12()); + + client->extractCertificate(pkcs12Cert.data(), pkcs12Cert.length(), + RSAInterface::RSA_KEY_PKCS12, deviceKey.toBase64().data(), + &pkcs12, &pkcs12Length); + ByteArray privateCertificate(pkcs12, pkcs12Length); + free(pkcs12); + + appendTextElem(root, "adept:certificate", privateCertificate.toBase64()); + appendTextElem(root, "adept:licenseCertificate", user->getProperty("//adept:licenseCertificate")); + appendTextElem(root, "adept:authenticationCertificate", user->getProperty("//adept:authenticationCertificate")); } - + void DRMProcessor::buildInitLicenseServiceRequest(pugi::xml_document& initLicReq, std::string operatorURL) { - pugi::xml_node decl = initLicReq.append_child(pugi::node_declaration); - decl.append_attribute("version") = "1.0"; - - pugi::xml_node root = initLicReq.append_child("adept:licenseServiceRequest"); - root.append_attribute("xmlns:adept") = ADOBE_ADEPT_NS; - root.append_attribute("identity") = "user"; + pugi::xml_node decl = initLicReq.append_child(pugi::node_declaration); + decl.append_attribute("version") = "1.0"; - appendTextElem(root, "adept:operatorURL", operatorURL); - addNonce(root); - appendTextElem(root, "adept:user", user->getUUID()); + pugi::xml_node root = initLicReq.append_child("adept:licenseServiceRequest"); + root.append_attribute("xmlns:adept") = ADOBE_ADEPT_NS; + root.append_attribute("identity") = "user"; - signNode(root); + appendTextElem(root, "adept:operatorURL", operatorURL); + addNonce(root); + appendTextElem(root, "adept:user", user->getUUID()); + + signNode(root); } - + void DRMProcessor::doOperatorAuth(std::string operatorURL) { - pugi::xml_document authReq; - buildAuthRequest(authReq); - std::string authURL = operatorURL; - unsigned int fulfillPos = authURL.rfind("Fulfill"); - if (fulfillPos == (authURL.size() - (sizeof("Fulfill")-1))) - authURL = authURL.substr(0, fulfillPos-1); - ByteArray replyData = sendRequest(authReq, authURL + "/Auth"); + pugi::xml_document authReq; + buildAuthRequest(authReq); + std::string authURL = operatorURL; + unsigned int fulfillPos = authURL.rfind("Fulfill"); + if (fulfillPos == (authURL.size() - (sizeof("Fulfill")-1))) + authURL = authURL.substr(0, fulfillPos-1); + ByteArray replyData = sendRequest(authReq, authURL + "/Auth"); - pugi::xml_document initLicReq; - std::string activationURL = user->getProperty("//adept:activationURL"); - buildInitLicenseServiceRequest(initLicReq, authURL); - sendRequest(initLicReq, activationURL + "/InitLicenseService"); + pugi::xml_document initLicReq; + std::string activationURL = user->getProperty("//adept:activationURL"); + buildInitLicenseServiceRequest(initLicReq, authURL); + sendRequest(initLicReq, activationURL + "/InitLicenseService"); } - + void DRMProcessor::operatorAuth(std::string operatorURL) { - pugi::xpath_node_set operatorList = user->getProperties("//adept:operatorURL"); - - for (pugi::xpath_node_set::const_iterator operatorIt = operatorList.begin(); - operatorIt != operatorList.end(); ++operatorIt) - { - std::string value = operatorIt->node().first_child().value(); - if (trim(value) == operatorURL) - { - GOUROU_LOG(DEBUG, "Already authenticated to operator " << operatorURL); - return; - } - } - - doOperatorAuth(operatorURL); + pugi::xpath_node_set operatorList = user->getProperties("//adept:operatorURL"); + + for (pugi::xpath_node_set::const_iterator operatorIt = operatorList.begin(); + operatorIt != operatorList.end(); ++operatorIt) + { + std::string value = operatorIt->node().first_child().value(); + if (trim(value) == operatorURL) + { + GOUROU_LOG(DEBUG, "Already authenticated to operator " << operatorURL); + return; + } + } + + doOperatorAuth(operatorURL); } 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; + pugi::xml_node decl = fulfillReq.append_child(pugi::node_declaration); + decl.append_attribute("version") = "1.0"; - appendTextElem(root, "adept:user", user->getUUID()); - appendTextElem(root, "adept:device", user->getDeviceUUID()); - appendTextElem(root, "adept:deviceType", (*device)["deviceType"]); + pugi::xml_node root = fulfillReq.append_child("adept:fulfill"); + root.append_attribute("xmlns:adept") = ADOBE_ADEPT_NS; - root.append_copy(acsmDoc.first_child()); + appendTextElem(root, "adept:user", user->getUUID()); + appendTextElem(root, "adept:device", user->getDeviceUUID()); + appendTextElem(root, "adept:deviceType", (*device)["deviceType"]); - 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()); + 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()); } void DRMProcessor::fetchLicenseServiceCertificate(const std::string& licenseURL, - const std::string& operatorURL) + const std::string& operatorURL) { - if (user->getLicenseServiceCertificate(licenseURL) != "") - return; + if (user->getLicenseServiceCertificate(licenseURL) != "") + return; - std::string licenseServiceInfoReq = operatorURL + "/LicenseServiceInfo?licenseURL=" + licenseURL; - - ByteArray replyData; - replyData = sendRequest(licenseServiceInfoReq); + std::string licenseServiceInfoReq = operatorURL + "/LicenseServiceInfo?licenseURL=" + licenseURL; - pugi::xml_document licenseServicesDoc; - licenseServicesDoc.load_buffer(replyData.data(), replyData.length()); + ByteArray replyData; + replyData = sendRequest(licenseServiceInfoReq); - // Add new license certificate - pugi::xml_document activationDoc; - user->readActivation(activationDoc); + pugi::xml_document licenseServicesDoc; + licenseServicesDoc.load_buffer(replyData.data(), replyData.length()); - pugi::xml_node root; - pugi::xpath_node xpathRes = activationDoc.select_node("//adept:licenseServices"); + // Add new license certificate + pugi::xml_document activationDoc; + user->readActivation(activationDoc); - // Create adept:licenseServices if it doesn't exists - if (!xpathRes) - { - xpathRes = activationDoc.select_node("/activationInfo"); - root = xpathRes.node(); - root = root.append_child("adept:licenseServices"); - root.append_attribute("xmlns:adept") = ADOBE_ADEPT_NS; - } - else - root = xpathRes.node(); + pugi::xml_node root; + pugi::xpath_node xpathRes = activationDoc.select_node("//adept:licenseServices"); - root = root.append_child("adept:licenseServiceInfo"); + // Create adept:licenseServices if it doesn't exists + if (!xpathRes) + { + xpathRes = activationDoc.select_node("/activationInfo"); + root = xpathRes.node(); + root = root.append_child("adept:licenseServices"); + root.append_attribute("xmlns:adept") = ADOBE_ADEPT_NS; + } + else + root = xpathRes.node(); - std::string certificate = extractTextElem(licenseServicesDoc, - "/licenseServiceInfo/certificate"); + root = root.append_child("adept:licenseServiceInfo"); - appendTextElem(root, "adept:licenseURL", licenseURL); - appendTextElem(root, "adept:certificate", certificate); + std::string certificate = extractTextElem(licenseServicesDoc, + "/licenseServiceInfo/certificate"); - // Add new operatorURL to list - xpathRes = activationDoc.select_node("//adept:operatorURLList"); + appendTextElem(root, "adept:licenseURL", licenseURL); + appendTextElem(root, "adept:certificate", certificate); - // Create adept:operatorURLList if it doesn't exists - if (!xpathRes) - { - xpathRes = activationDoc.select_node("/activationInfo"); - root = xpathRes.node(); - root = root.append_child("adept:operatorURLList"); - root.append_attribute("xmlns:adept") = ADOBE_ADEPT_NS; + // Add new operatorURL to list + xpathRes = activationDoc.select_node("//adept:operatorURLList"); - appendTextElem(root, "adept:user", user->getUUID()); - } - else - root = xpathRes.node(); + // Create adept:operatorURLList if it doesn't exists + if (!xpathRes) + { + xpathRes = activationDoc.select_node("/activationInfo"); + root = xpathRes.node(); + root = root.append_child("adept:operatorURLList"); + root.append_attribute("xmlns:adept") = ADOBE_ADEPT_NS; - appendTextElem(root, "adept:operatorURL", operatorURL); + appendTextElem(root, "adept:user", user->getUUID()); + } + else + root = xpathRes.node(); - user->updateActivationFile(activationDoc); + appendTextElem(root, "adept:operatorURL", operatorURL); + + user->updateActivationFile(activationDoc); } - + FulfillmentItem* DRMProcessor::fulfill(const std::string& ACSMFile, bool notify) { - if (!user->getPKCS12().length()) - EXCEPTION(FF_NOT_ACTIVATED, "Device not activated"); - - pugi::xml_document acsmDoc; + if (!user->getPKCS12().length()) + EXCEPTION(FF_NOT_ACTIVATED, "Device not activated"); - if (!acsmDoc.load_file(ACSMFile.c_str(), pugi::parse_ws_pcdata_single|pugi::parse_escapes, pugi::encoding_utf8)) - EXCEPTION(FF_INVALID_ACSM_FILE, "Invalid ACSM file " << ACSMFile); + pugi::xml_document acsmDoc; - // Could be an server internal error - pugi::xml_node rootNode = acsmDoc.first_child(); - if (std::string(rootNode.name()) == "error") - { - EXCEPTION(FF_SERVER_INTERNAL_ERROR, rootNode.attribute("data").value()); - } + if (!acsmDoc.load_file(ACSMFile.c_str(), pugi::parse_ws_pcdata_single|pugi::parse_escapes, pugi::encoding_utf8)) + EXCEPTION(FF_INVALID_ACSM_FILE, "Invalid ACSM file " << ACSMFile); - GOUROU_LOG(INFO, "Fulfill " << ACSMFile); + // Could be an server internal error + pugi::xml_node rootNode = acsmDoc.first_child(); + if (std::string(rootNode.name()) == "error") + { + EXCEPTION(FF_SERVER_INTERNAL_ERROR, rootNode.attribute("data").value()); + } - std::string expiration = extractTextElem(rootNode, "expiration", false); + GOUROU_LOG(INFO, "Fulfill " << ACSMFile); - if (expiration != "") - { - time_t expirationTime = parseDateTime(expiration.c_str(), "%Y-%m-%dT%H:%M:%S"); + std::string expiration = extractTextElem(rootNode, "expiration", false); - if (time(NULL) > expirationTime) - GOUROU_LOG(WARN, "WARNING: ACSM file expired (" << expiration << "). It may not work"); - } - - // Build req file - pugi::xml_document fulfillReq; + if (expiration != "") + { + time_t expirationTime = parseDateTime(expiration.c_str(), "%Y-%m-%dT%H:%M:%S"); - buildFulfillRequest(acsmDoc, fulfillReq); - pugi::xpath_node root = fulfillReq.select_node("//adept:fulfill"); - rootNode = root.node(); + if (time(NULL) > expirationTime) + GOUROU_LOG(WARN, "WARNING: ACSM file expired (" << expiration << "). It may not work"); + } - // Remove HMAC - pugi::xpath_node xpathRes = fulfillReq.select_node("//hmac"); + // Build req file + pugi::xml_document fulfillReq; - if (!xpathRes) - EXCEPTION(FF_NO_HMAC_IN_ACSM_FILE, "hmac tag not found in ACSM file"); + buildFulfillRequest(acsmDoc, fulfillReq); + pugi::xpath_node root = fulfillReq.select_node("//adept:fulfill"); + rootNode = root.node(); - pugi::xml_node hmacNode = xpathRes.node(); - pugi::xml_node hmacParentNode = hmacNode.parent(); - - hmacParentNode.remove_child(hmacNode); + // Remove HMAC + pugi::xpath_node xpathRes = fulfillReq.select_node("//hmac"); - signNode(rootNode); - - // Add removed HMAC - appendTextElem(hmacParentNode, hmacNode.name(), hmacNode.first_child().value()); + if (!xpathRes) + EXCEPTION(FF_NO_HMAC_IN_ACSM_FILE, "hmac tag not found in ACSM file"); - pugi::xpath_node node = acsmDoc.select_node("//operatorURL"); - if (!node) - EXCEPTION(FF_NO_OPERATOR_URL, "OperatorURL not found in ACSM document"); - - std::string operatorURL = node.node().first_child().value(); - operatorURL = trim(operatorURL); - std::string fulfillURL = operatorURL + "/Fulfill"; + pugi::xml_node hmacNode = xpathRes.node(); + pugi::xml_node hmacParentNode = hmacNode.parent(); - operatorAuth(fulfillURL); - - ByteArray replyData; + hmacParentNode.remove_child(hmacNode); - try - { - replyData = sendRequest(fulfillReq, fulfillURL); - } - catch (gourou::Exception& e) - { - /* - Operator requires authentication even if it's already in - our operator list - */ - std::string errorMsg(e.what()); - if (e.getErrorCode() == GOUROU_ADEPT_ERROR && - errorMsg.find("E_ADEPT_DISTRIBUTOR_AUTH") != std::string::npos) - { - doOperatorAuth(fulfillURL); - replyData = sendRequest(fulfillReq, fulfillURL); - } - else - { - throw e; - } - } + signNode(rootNode); - pugi::xml_document fulfillReply; + // Add removed HMAC + appendTextElem(hmacParentNode, hmacNode.name(), hmacNode.first_child().value()); - fulfillReply.load_string((const char*)replyData.data()); - - std::string licenseURL = extractTextElem(fulfillReply, "//licenseToken/licenseURL"); - - fetchLicenseServiceCertificate(licenseURL, operatorURL); + pugi::xpath_node node = acsmDoc.select_node("//operatorURL"); + if (!node) + EXCEPTION(FF_NO_OPERATOR_URL, "OperatorURL not found in ACSM document"); - FulfillmentItem* item = new FulfillmentItem(fulfillReply, user); + std::string operatorURL = node.node().first_child().value(); + operatorURL = trim(operatorURL); + std::string fulfillURL = operatorURL + "/Fulfill"; - if (notify) - notifyServer(fulfillReply); - - return item; + operatorAuth(fulfillURL); + + ByteArray replyData; + + try + { + replyData = sendRequest(fulfillReq, fulfillURL); + } + catch (gourou::Exception& e) + { + /* + Operator requires authentication even if it's already in + our operator list + */ + std::string errorMsg(e.what()); + if (e.getErrorCode() == GOUROU_ADEPT_ERROR && + errorMsg.find("E_ADEPT_DISTRIBUTOR_AUTH") != std::string::npos) + { + doOperatorAuth(fulfillURL); + replyData = sendRequest(fulfillReq, fulfillURL); + } + else + { + throw e; + } + } + + pugi::xml_document fulfillReply; + + fulfillReply.load_string((const char*)replyData.data()); + + std::string licenseURL = extractTextElem(fulfillReply, "//licenseToken/licenseURL"); + + fetchLicenseServiceCertificate(licenseURL, operatorURL); + + FulfillmentItem* item = new FulfillmentItem(fulfillReply, user); + + if (notify) + notifyServer(fulfillReply); + + return item; } DRMProcessor::ITEM_TYPE DRMProcessor::download(FulfillmentItem* item, std::string path, bool resume) { - ITEM_TYPE res = EPUB; - - if (!item) - EXCEPTION(DW_NO_ITEM, "No item"); + ITEM_TYPE res = EPUB; - std::map headers; + if (!item) + EXCEPTION(DW_NO_ITEM, "No item"); - int fd = createNewFile(path, !resume); - - sendRequest(item->getDownloadURL(), "", 0, &headers, fd, resume); + std::map headers; - close(fd); + int fd = createNewFile(path, !resume); - GOUROU_LOG(INFO, "Download into " << path); + sendRequest(item->getDownloadURL(), "", 0, &headers, fd, resume); - ByteArray rightsStr(item->getRights()); + close(fd); - if (item->getMetadata("format").find("application/pdf") != std::string::npos) - res = PDF; + GOUROU_LOG(INFO, "Download into " << path); - if (headers.count("Content-Type") && - headers["Content-Type"].find("application/pdf") != std::string::npos) - res = PDF; - - if (res == EPUB) - { - void* handler = client->zipOpen(path); - client->zipWriteFile(handler, "META-INF/rights.xml", rightsStr); - client->zipClose(handler); - } - else if (res == PDF) - { - uPDFParser::Parser parser; - bool EBXHandlerFound = false; - - try - { - GOUROU_LOG(DEBUG, "Parse PDF"); - parser.parse(path); - } - catch(std::invalid_argument& e) - { - GOUROU_LOG(ERROR, "Invalid PDF"); - return res; - } + ByteArray rightsStr(item->getRights()); - std::vector objects = parser.objects(); - std::vector::reverse_iterator it; + if (item->getMetadata("format").find("application/pdf") != std::string::npos) + res = PDF; - for(it = objects.rbegin(); it != objects.rend(); it++) - { - // Update EBX_HANDLER with rights - if ((*it)->hasKey("Filter") && (**it)["Filter"]->str() == "/EBX_HANDLER") - { - EBXHandlerFound = true; - uPDFParser::Object* ebx = (*it)->clone(); - (*ebx)["ADEPT_ID"] = new uPDFParser::String(item->getResource()); - (*ebx)["EBX_BOOKID"] = new uPDFParser::String(item->getResource()); - ByteArray zipped; - client->deflate(rightsStr, zipped); - (*ebx)["ADEPT_LICENSE"] = new uPDFParser::String(zipped.toBase64()); - parser.addObject(ebx); - break; - } - } + if (headers.count("Content-Type") && + headers["Content-Type"].find("application/pdf") != std::string::npos) + res = PDF; - if (EBXHandlerFound) - parser.write(path, true); - else - { - EXCEPTION(DW_NO_EBX_HANDLER, "EBX_HANDLER not found"); - } - } + if (res == EPUB) + { + void* handler = client->zipOpen(path); + client->zipWriteFile(handler, "META-INF/rights.xml", rightsStr); + client->zipClose(handler); + } + else if (res == PDF) + { + uPDFParser::Parser parser; + bool EBXHandlerFound = false; - return res; + try + { + GOUROU_LOG(DEBUG, "Parse PDF"); + parser.parse(path); + } + catch(std::invalid_argument& e) + { + GOUROU_LOG(ERROR, "Invalid PDF"); + return res; + } + + std::vector objects = parser.objects(); + std::vector::reverse_iterator it; + + for(it = objects.rbegin(); it != objects.rend(); it++) + { + // Update EBX_HANDLER with rights + if ((*it)->hasKey("Filter") && (**it)["Filter"]->str() == "/EBX_HANDLER") + { + EBXHandlerFound = true; + uPDFParser::Object* ebx = (*it)->clone(); + (*ebx)["ADEPT_ID"] = new uPDFParser::String(item->getResource()); + (*ebx)["EBX_BOOKID"] = new uPDFParser::String(item->getResource()); + ByteArray zipped; + client->deflate(rightsStr, zipped); + (*ebx)["ADEPT_LICENSE"] = new uPDFParser::String(zipped.toBase64()); + parser.addObject(ebx); + break; + } + } + + if (EBXHandlerFound) + parser.write(path, true); + else + { + EXCEPTION(DW_NO_EBX_HANDLER, "EBX_HANDLER not found"); + } + } + + return res; } void DRMProcessor::buildSignInRequest(pugi::xml_document& signInRequest, - const std::string& adobeID, const std::string& adobePassword, - const std::string& authenticationCertificate) + const std::string& adobeID, const std::string& adobePassword, + const std::string& authenticationCertificate) { - pugi::xml_node decl = signInRequest.append_child(pugi::node_declaration); - decl.append_attribute("version") = "1.0"; - pugi::xml_node signIn = signInRequest.append_child("adept:signIn"); - signIn.append_attribute("xmlns:adept") = ADOBE_ADEPT_NS; - std::string loginMethod = user->getLoginMethod(); + pugi::xml_node decl = signInRequest.append_child(pugi::node_declaration); + decl.append_attribute("version") = "1.0"; + pugi::xml_node signIn = signInRequest.append_child("adept:signIn"); + signIn.append_attribute("xmlns:adept") = ADOBE_ADEPT_NS; + std::string loginMethod = user->getLoginMethod(); - if (adobeID == "anonymous") - signIn.append_attribute("method") = "anonymous"; - else if (loginMethod.size()) - signIn.append_attribute("method") = loginMethod.c_str(); - else - signIn.append_attribute("method") = "AdobeID"; + if (adobeID == "anonymous") + signIn.append_attribute("method") = "anonymous"; + else if (loginMethod.size()) + signIn.append_attribute("method") = loginMethod.c_str(); + else + signIn.append_attribute("method") = "AdobeID"; - unsigned char encryptedSignInData[RSA_KEY_SIZE]; - const unsigned char* deviceKey = device->getDeviceKey(); + unsigned char encryptedSignInData[RSA_KEY_SIZE]; + const unsigned char* deviceKey = device->getDeviceKey(); - ByteArray _authenticationCertificate = ByteArray::fromBase64(authenticationCertificate); + ByteArray _authenticationCertificate = ByteArray::fromBase64(authenticationCertificate); - // Build buffer - ByteArray ar(deviceKey, Device::DEVICE_KEY_SIZE); - ar.append((unsigned char)adobeID.length()); - ar.append(adobeID); - ar.append((unsigned char)adobePassword.length()); - ar.append(adobePassword); + // Build buffer + ByteArray ar(deviceKey, Device::DEVICE_KEY_SIZE); + ar.append((unsigned char)adobeID.length()); + ar.append(adobeID); + ar.append((unsigned char)adobePassword.length()); + ar.append(adobePassword); - // Encrypt with authentication certificate (public part) - client->RSAPublicEncrypt(_authenticationCertificate.data(), - _authenticationCertificate.length(), - RSAInterface::RSA_KEY_X509, - ar.data(), ar.length(), encryptedSignInData); + // Encrypt with authentication certificate (public part) + client->RSAPublicEncrypt(_authenticationCertificate.data(), + _authenticationCertificate.length(), + RSAInterface::RSA_KEY_X509, + ar.data(), ar.length(), encryptedSignInData); - ar = ByteArray(encryptedSignInData, sizeof(encryptedSignInData)); - appendTextElem(signIn, "adept:signInData", ar.toBase64()); - - // Generate Auth key and License Key - void* rsaAuth = client->generateRSAKey(RSA_KEY_SIZE_BITS); - void* rsaLicense = client->generateRSAKey(RSA_KEY_SIZE_BITS); + ar = ByteArray(encryptedSignInData, sizeof(encryptedSignInData)); + appendTextElem(signIn, "adept:signInData", ar.toBase64()); - std::string serializedData = serializeRSAPublicKey(rsaAuth); - appendTextElem(signIn, "adept:publicAuthKey", serializedData); - serializedData = serializeRSAPrivateKey(rsaAuth); - appendTextElem(signIn, "adept:encryptedPrivateAuthKey", serializedData.data()); + // Generate Auth key and License Key + void* rsaAuth = client->generateRSAKey(RSA_KEY_SIZE_BITS); + void* rsaLicense = client->generateRSAKey(RSA_KEY_SIZE_BITS); - serializedData = serializeRSAPublicKey(rsaLicense); - appendTextElem(signIn, "adept:publicLicenseKey", serializedData.data()); - serializedData = serializeRSAPrivateKey(rsaLicense); - appendTextElem(signIn, "adept:encryptedPrivateLicenseKey", serializedData.data()); + std::string serializedData = serializeRSAPublicKey(rsaAuth); + appendTextElem(signIn, "adept:publicAuthKey", serializedData); + serializedData = serializeRSAPrivateKey(rsaAuth); + appendTextElem(signIn, "adept:encryptedPrivateAuthKey", serializedData.data()); - client->destroyRSAHandler(rsaAuth); - client->destroyRSAHandler(rsaLicense); + 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); + pugi::xml_document signInRequest; + std::string authenticationCertificate = user->getAuthenticationCertificate(); - GOUROU_LOG(INFO, "SignIn " << adobeID); - - std::string signInURL = user->getProperty("//adept:authURL"); - signInURL += "/SignInDirect"; + buildSignInRequest(signInRequest, adobeID, adobePassword, authenticationCertificate); - ByteArray credentials = sendRequest(signInRequest, signInURL); - - pugi::xml_document credentialsDoc; - if (!credentialsDoc.load_buffer(credentials.data(), credentials.length())) - EXCEPTION(SIGN_INVALID_CREDENTIALS, "Invalid credentials reply"); + GOUROU_LOG(INFO, "SignIn " << adobeID); - 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; + std::string signInURL = user->getProperty("//adept:authURL"); + signInURL += "/SignInDirect"; - pugi::xml_node credentialsNode = credentialsDoc.first_child(); + ByteArray credentials = sendRequest(signInRequest, signInURL); - 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()); + pugi::xml_document credentialsDoc; + if (!credentialsDoc.load_buffer(credentials.data(), credentials.length())) + EXCEPTION(SIGN_INVALID_CREDENTIALS, "Invalid credentials reply"); - // 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()); + 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()); + } - pugi::xml_document activationDoc; - user->readActivation(activationDoc); - pugi::xml_node activationInfo = activationDoc.select_node("activationInfo").node(); - activationInfo.append_copy(credentialsNode); + bool begin(pugi::xml_node& node) + { + changeName(node); + return true; + } - user->updateActivationFile(activationDoc); + 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"; + pugi::xml_node decl = activateReq.append_child(pugi::node_declaration); + decl.append_attribute("version") = "1.0"; - 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 root = activateReq.append_child("adept:activate"); + root.append_attribute("xmlns:adept") = ADOBE_ADEPT_NS; + root.append_attribute("requestType") = "initial"; - 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"]); + 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"]); - addNonce(root); - - appendTextElem(root, "adept:user", user->getUUID()); + pugi::xml_node targetDevice = root.append_child("adept:targetDevice"); + appendTextElem(targetDevice, "adept:softwareVersion", (*device)["hobbes"]); + appendTextElem(targetDevice, "adept:clientOS", (*device)["clientOS"]); + appendTextElem(targetDevice, "adept:clientLocale", (*device)["clientLocale"]); + appendTextElem(targetDevice, "adept:clientVersion", (*device)["deviceClass"]); + appendTextElem(targetDevice, "adept:deviceType", (*device)["deviceType"]); + appendTextElem(targetDevice, "adept:fingerprint", (*device)["fingerprint"]); + + addNonce(root); + + appendTextElem(root, "adept:user", user->getUUID()); } - + void DRMProcessor::activateDevice() { - pugi::xml_document activateReq; + pugi::xml_document activateReq; - GOUROU_LOG(INFO, "Activate device"); + GOUROU_LOG(INFO, "Activate device"); - buildActivateReq(activateReq); + buildActivateReq(activateReq); - pugi::xml_node root = activateReq.select_node("adept:activate").node(); + pugi::xml_node root = activateReq.select_node("adept:activate").node(); - signNode(root); + signNode(root); - pugi::xml_document activationDoc; - user->readActivation(activationDoc); + pugi::xml_document activationDoc; + user->readActivation(activationDoc); - std::string activationURL = user->getProperty("//adept:activationURL"); - activationURL += "/Activate"; - - ByteArray reply = sendRequest(activateReq, activationURL); + std::string activationURL = user->getProperty("//adept:activationURL"); + activationURL += "/Activate"; - 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 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); } - + void DRMProcessor::buildReturnReq(pugi::xml_document& returnReq, const std::string& loanID, const std::string& operatorURL) { - pugi::xml_node decl = returnReq.append_child(pugi::node_declaration); - decl.append_attribute("version") = "1.0"; - - pugi::xml_node root = returnReq.append_child("adept:loanReturn"); - root.append_attribute("xmlns:adept") = ADOBE_ADEPT_NS; + pugi::xml_node decl = returnReq.append_child(pugi::node_declaration); + decl.append_attribute("version") = "1.0"; - appendTextElem(root, "adept:user", user->getUUID()); - appendTextElem(root, "adept:device", user->getDeviceUUID()); - appendTextElem(root, "adept:loan", loanID); + pugi::xml_node root = returnReq.append_child("adept:loanReturn"); + root.append_attribute("xmlns:adept") = ADOBE_ADEPT_NS; - addNonce(root); - signNode(root); + appendTextElem(root, "adept:user", user->getUUID()); + appendTextElem(root, "adept:device", user->getDeviceUUID()); + appendTextElem(root, "adept:loan", loanID); + + addNonce(root); + signNode(root); } std::string DRMProcessor::getDefaultAdeptDir(void) { #ifndef DEFAULT_ADEPT_DIR - const char* home = getenv("HOME"); - - if (home) - return home + std::string("/.config/adept/"); - else - { - const char* user = getenv("USER"); - - if (user && user[0]) - { - return std::string("/home/") + user + std::string("/.config/adept/"); - } - else - return LOCAL_ADEPT_DIR; - } + const char* home = getenv("HOME"); + + if (home) + return home + std::string("/.config/adept/"); + else + { + const char* user = getenv("USER"); + + if (user && user[0]) + { + return std::string("/home/") + user + std::string("/.config/adept/"); + } + else + return LOCAL_ADEPT_DIR; + } #else - return DEFAULT_ADEPT_DIR "/"; + return DEFAULT_ADEPT_DIR "/"; #endif } void DRMProcessor::returnLoan(const std::string& loanID, const std::string& operatorURL, - bool notify) + bool notify) { - pugi::xml_document returnReq; + pugi::xml_document returnReq; - GOUROU_LOG(INFO, "Return loan " << loanID); + GOUROU_LOG(INFO, "Return loan " << loanID); - buildReturnReq(returnReq, loanID, operatorURL); + buildReturnReq(returnReq, loanID, operatorURL); - ByteArray replyData = sendRequest(returnReq, operatorURL + "/LoanReturn"); - - pugi::xml_document fulfillReply; + ByteArray replyData = sendRequest(returnReq, operatorURL + "/LoanReturn"); - fulfillReply.load_string((const char*)replyData.data()); + pugi::xml_document fulfillReply; - if (notify) - notifyServer(fulfillReply); + fulfillReply.load_string((const char*)replyData.data()); + + if (notify) + notifyServer(fulfillReply); } void DRMProcessor::buildNotifyReq(pugi::xml_document& returnReq, pugi::xml_node& body) { - pugi::xml_node decl = returnReq.append_child(pugi::node_declaration); - decl.append_attribute("version") = "1.0"; - - pugi::xml_node root = returnReq.append_child("adept:notification"); - root.append_attribute("xmlns:adept") = ADOBE_ADEPT_NS; + pugi::xml_node decl = returnReq.append_child(pugi::node_declaration); + decl.append_attribute("version") = "1.0"; - appendTextElem(root, "adept:user", user->getUUID()); - appendTextElem(root, "adept:device", user->getDeviceUUID()); - body = root.append_copy(body); - body.append_attribute("xmlns") = ADOBE_ADEPT_NS; - - addNonce(root); - signNode(root); + pugi::xml_node root = returnReq.append_child("adept:notification"); + root.append_attribute("xmlns:adept") = ADOBE_ADEPT_NS; + + appendTextElem(root, "adept:user", user->getUUID()); + appendTextElem(root, "adept:device", user->getDeviceUUID()); + body = root.append_copy(body); + body.append_attribute("xmlns") = ADOBE_ADEPT_NS; + + addNonce(root); + signNode(root); } void DRMProcessor::notifyServer(pugi::xml_node& notifyRoot) { - std::string notifyUrl = extractTextElem(notifyRoot, "//notifyURL", false); - pugi::xml_node notifyBody = getNode(notifyRoot, "//body", false); - - if (notifyUrl == "") - { - GOUROU_LOG(INFO, "No notify URL"); - return; - } + std::string notifyUrl = extractTextElem(notifyRoot, "//notifyURL", false); + pugi::xml_node notifyBody = getNode(notifyRoot, "//body", false); - if (!notifyBody) - { - GOUROU_LOG(INFO, "No notify body"); - return; - } + if (notifyUrl == "") + { + GOUROU_LOG(INFO, "No notify URL"); + return; + } - pugi::xml_document notifyReq; - buildNotifyReq(notifyReq, notifyBody); + if (!notifyBody) + { + GOUROU_LOG(INFO, "No notify body"); + return; + } - sendRequest(notifyReq, notifyUrl); + pugi::xml_document notifyReq; + buildNotifyReq(notifyReq, notifyBody); + + sendRequest(notifyReq, notifyUrl); } - + void DRMProcessor::notifyServer(pugi::xml_document& fulfillReply) { - pugi::xpath_node_set notifySet = fulfillReply.select_nodes("//notify"); + pugi::xpath_node_set notifySet = fulfillReply.select_nodes("//notify"); - if (notifySet.empty()) - { - GOUROU_LOG(DEBUG, "No notify request"); - return; - } + if (notifySet.empty()) + { + GOUROU_LOG(DEBUG, "No notify request"); + return; + } - for (pugi::xpath_node_set::const_iterator it = notifySet.begin(); it != notifySet.end(); ++it) - { - pugi::xml_node notifyRoot = it->node(); - notifyServer(notifyRoot); - } + for (pugi::xpath_node_set::const_iterator it = notifySet.begin(); it != notifySet.end(); ++it) + { + pugi::xml_node notifyRoot = it->node(); + notifyServer(notifyRoot); + } } - + ByteArray DRMProcessor::encryptWithDeviceKey(const unsigned char* data, unsigned int len) { - const unsigned char* deviceKey = device->getDeviceKey(); - unsigned int outLen; - int remain = 0; - if ((len % 16)) - remain = 16 - (len%16); - int encrypted_data_len = 16 + len + remain; // IV + data + pad - unsigned char* encrypted_data = new unsigned char[encrypted_data_len]; - - // Generate IV in front - client->randBytes(encrypted_data, 16); - - client->encrypt(CryptoInterface::ALGO_AES, CryptoInterface::CHAIN_CBC, - deviceKey, 16, encrypted_data, 16, - data, len, - encrypted_data+16, &outLen); + 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]; - ByteArray res(encrypted_data, outLen+16); + // Generate IV in front + client->randBytes(encrypted_data, 16); - delete[] encrypted_data; + client->encrypt(CryptoInterface::ALGO_AES, CryptoInterface::CHAIN_CBC, + deviceKey, 16, encrypted_data, 16, + data, len, + encrypted_data+16, &outLen); - return res; + 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]; + unsigned int outLen; + const unsigned char* deviceKey = device->getDeviceKey(); + unsigned char* decrypted_data = new unsigned char[len-16]; - client->decrypt(CryptoInterface::ALGO_AES, CryptoInterface::CHAIN_CBC, - deviceKey, 16, data, 16, - data+16, len-16, - decrypted_data, &outLen); + client->decrypt(CryptoInterface::ALGO_AES, CryptoInterface::CHAIN_CBC, + deviceKey, 16, data, 16, + data+16, len-16, + decrypted_data, &outLen); - ByteArray res(decrypted_data, outLen); + ByteArray res(decrypted_data, outLen); - delete[] decrypted_data; + delete[] decrypted_data; - return res; + return res; } std::string DRMProcessor::serializeRSAPublicKey(void* rsa) { - unsigned char* data = 0; - unsigned int len; - - client->extractRSAPublicKey(rsa, &data, &len); + unsigned char* data = 0; + unsigned int len; - ByteArray res(data, len); + client->extractRSAPublicKey(rsa, &data, &len); - free(data); - - return res.toBase64(); + 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); + unsigned char* data = 0; + unsigned int len; - ByteArray res = encryptWithDeviceKey(data, len); + client->extractRSAPrivateKey(rsa, &data, &len); - free(data); - - return res.toBase64(); + ByteArray res = encryptWithDeviceKey(data, len); + + free(data); + + return res.toBase64(); } void DRMProcessor::exportPrivateLicenseKey(std::string path) { - int fd = open(path.c_str(), O_CREAT|O_TRUNC|O_WRONLY, S_IRWXU); - int ret; - - if (fd <= 0) - EXCEPTION(GOUROU_FILE_ERROR, "Unable to open " << path); + int fd = open(path.c_str(), O_CREAT|O_TRUNC|O_WRONLY, S_IRWXU); + int ret; - ByteArray privateLicenseKey = ByteArray::fromBase64(user->getPrivateLicenseKey()); - /* In adobekey.py, we get base64 decoded data [26:] */ - ret = write(fd, privateLicenseKey.data()+26, privateLicenseKey.length()-26); - close(fd); - if (ret != (int)(privateLicenseKey.length()-26)) - { - EXCEPTION(gourou::GOUROU_FILE_ERROR, "Error writing " << path); - } + if (fd <= 0) + EXCEPTION(GOUROU_FILE_ERROR, "Unable to open " << path); + + ByteArray privateLicenseKey = ByteArray::fromBase64(user->getPrivateLicenseKey()); + /* In adobekey.py, we get base64 decoded data [26:] */ + ret = write(fd, privateLicenseKey.data()+26, privateLicenseKey.length()-26); + close(fd); + if (ret != (int)(privateLicenseKey.length()-26)) + { + EXCEPTION(gourou::GOUROU_FILE_ERROR, "Error writing " << path); + } } int DRMProcessor::getLogLevel() {return (int)gourou::logLevel;} @@ -1078,456 +1078,456 @@ namespace gourou */ std::string DRMProcessor::encryptedKeyFirstPass(pugi::xml_document& rightsDoc, const std::string& encryptedKey, const std::string& keyType) { - unsigned char digest[32], key[16], iv[16]; - unsigned int dataOutLength; - std::string id; - - client->digest("SHA256", (unsigned char*)keyType.c_str(), keyType.size(), digest); + unsigned char digest[32], key[16], iv[16]; + unsigned int dataOutLength; + std::string id; - dumpBuffer(gourou::LG_LOG_DEBUG, "SHA of KeyType : ", digest, sizeof(digest)); + client->digest("SHA256", (unsigned char*)keyType.c_str(), keyType.size(), digest); - long nonce = std::stol(keyType); - int remainder = nonce % 16; + dumpBuffer(gourou::LG_LOG_DEBUG, "SHA of KeyType : ", digest, sizeof(digest)); - memcpy(key, &digest[remainder*2], 16-remainder); - memcpy(&key[16-remainder], &digest[remainder], remainder); - - id = extractTextElem(rightsDoc, "/adept:rights/licenseToken/device"); - if (id == "") - EXCEPTION(DRM_ERR_ENCRYPTION_KEY_FP, "Device id not found in rights.xml"); - ByteArray deviceId = ByteArray::fromHex(extractIdFromUUID(id)); - unsigned char* _deviceId = deviceId.data(); - - id = extractTextElem(rightsDoc, "/adept:rights/licenseToken/fulfillment"); - if (id == "") - EXCEPTION(DRM_ERR_ENCRYPTION_KEY_FP, "Fulfillment id not found in rights.xml"); - ByteArray fulfillmentId = ByteArray::fromHex(extractIdFromUUID(id)); - unsigned char* _fulfillmentId = fulfillmentId.data(); - - id = extractTextElem(rightsDoc, "/adept:rights/licenseToken/voucher"); - if (id == "") - EXCEPTION(DRM_ERR_ENCRYPTION_KEY_FP, "Voucher id not found in rights.xml"); - ByteArray voucherId = ByteArray::fromHex(extractIdFromUUID(id)); - unsigned char* _voucherId = voucherId.data(); - - if (deviceId.size() < sizeof(iv) || fulfillmentId.size() < sizeof(iv) || voucherId.size() < sizeof(iv)) - EXCEPTION(DRM_ERR_ENCRYPTION_KEY_FP, "One id has a bad length"); + long nonce = std::stol(keyType); + int remainder = nonce % 16; - for(unsigned int i=0; idecrypt(CryptoInterface::ALGO_AES, CryptoInterface::CHAIN_CBC, - (const unsigned char*)key, (unsigned int)sizeof(key), - (const unsigned char*)iv, (unsigned int)sizeof(iv), - (const unsigned char*)arrayEncryptedKey.data(), arrayEncryptedKey.size(), - (unsigned char*)clearRSAKey, &dataOutLength); + id = extractTextElem(rightsDoc, "/adept:rights/licenseToken/voucher"); + if (id == "") + EXCEPTION(DRM_ERR_ENCRYPTION_KEY_FP, "Voucher id not found in rights.xml"); + ByteArray voucherId = ByteArray::fromHex(extractIdFromUUID(id)); + unsigned char* _voucherId = voucherId.data(); - dumpBuffer(gourou::LG_LOG_DEBUG, "\nDecrypted key : ", clearRSAKey, dataOutLength); + if (deviceId.size() < sizeof(iv) || fulfillmentId.size() < sizeof(iv) || voucherId.size() < sizeof(iv)) + EXCEPTION(DRM_ERR_ENCRYPTION_KEY_FP, "One id has a bad length"); - /* Last block could be 0x10*16 which is OpenSSL padding, remove it if it's the case */ - bool skipLastLine = true; - for(unsigned int i=dataOutLength-16; idecrypt(CryptoInterface::ALGO_AES, CryptoInterface::CHAIN_CBC, + (const unsigned char*)key, (unsigned int)sizeof(key), + (const unsigned char*)iv, (unsigned int)sizeof(iv), + (const unsigned char*)arrayEncryptedKey.data(), arrayEncryptedKey.size(), + (unsigned char*)clearRSAKey, &dataOutLength); + + dumpBuffer(gourou::LG_LOG_DEBUG, "\nDecrypted key : ", clearRSAKey, dataOutLength); + + /* Last block could be 0x10*16 which is OpenSSL padding, remove it if it's the case */ + bool skipLastLine = true; + for(unsigned int i=dataOutLength-16; iuser->getUUID() != user) - { - EXCEPTION(DRM_INVALID_USER, "This book has been downloaded for another user (" << user << ")"); - } + std::string user = extractTextElem(rightsDoc, "/adept:rights/licenseToken/user"); - std::string encryptedKey = extractTextElem(rightsDoc, "/adept:rights/licenseToken/encryptedKey"); - std::string keyType = extractTextAttribute(rightsDoc, "/adept:rights/licenseToken/encryptedKey", "keyType", false); + if (!encryptionKey) + { + if (this->user->getUUID() != user) + { + EXCEPTION(DRM_INVALID_USER, "This book has been downloaded for another user (" << user << ")"); + } - if (keyType != "") - encryptedKey = encryptedKeyFirstPass(rightsDoc, encryptedKey, keyType); - - if (encryptedKey.size() != 172) - EXCEPTION(DRM_INVALID_KEY_SIZE, "Invalid encrypted key size (" << encryptedKey.size() << "). DRM version not supported"); + std::string encryptedKey = extractTextElem(rightsDoc, "/adept:rights/licenseToken/encryptedKey"); + std::string keyType = extractTextAttribute(rightsDoc, "/adept:rights/licenseToken/encryptedKey", "keyType", false); - ByteArray arrayEncryptedKey = ByteArray::fromBase64(encryptedKey); + if (keyType != "") + encryptedKey = encryptedKeyFirstPass(rightsDoc, encryptedKey, keyType); - std::string privateKeyData = this->user->getPrivateLicenseKey(); - ByteArray privateRSAKey = ByteArray::fromBase64(privateKeyData); - - dumpBuffer(gourou::LG_LOG_DEBUG, "To decrypt : ", arrayEncryptedKey.data(), arrayEncryptedKey.length()); + if (encryptedKey.size() != 172) + EXCEPTION(DRM_INVALID_KEY_SIZE, "Invalid encrypted key size (" << encryptedKey.size() << "). DRM version not supported"); - client->RSAPrivateDecrypt(privateRSAKey.data(), privateRSAKey.length(), - RSAInterface::RSA_KEY_PKCS8, "", - arrayEncryptedKey.data(), arrayEncryptedKey.length(), rsaKey); + ByteArray arrayEncryptedKey = ByteArray::fromBase64(encryptedKey); - dumpBuffer(gourou::LG_LOG_DEBUG, "Decrypted : ", rsaKey, sizeof(rsaKey)); + std::string privateKeyData = this->user->getPrivateLicenseKey(); + ByteArray privateRSAKey = ByteArray::fromBase64(privateKeyData); - if (rsaKey[0] != 0x00 || rsaKey[1] != 0x02 || - rsaKey[RSA_KEY_SIZE-16-1] != 0x00) - EXCEPTION(DRM_ERR_ENCRYPTION_KEY, "Unable to retrieve encryption key"); + dumpBuffer(gourou::LG_LOG_DEBUG, "To decrypt : ", arrayEncryptedKey.data(), arrayEncryptedKey.length()); - memcpy(decryptedKey, &rsaKey[sizeof(rsaKey)-16], 16); - } - else - { - GOUROU_LOG(DEBUG, "Use provided encryption key"); - if (encryptionKeySize != 16) - EXCEPTION(DRM_ERR_ENCRYPTION_KEY, "Provided encryption key must be 16 bytes"); + client->RSAPrivateDecrypt(privateRSAKey.data(), privateRSAKey.length(), + RSAInterface::RSA_KEY_PKCS8, "", + arrayEncryptedKey.data(), arrayEncryptedKey.length(), rsaKey); - memcpy(decryptedKey, encryptionKey, encryptionKeySize); - } + dumpBuffer(gourou::LG_LOG_DEBUG, "Decrypted : ", rsaKey, sizeof(rsaKey)); + + if (rsaKey[0] != 0x00 || rsaKey[1] != 0x02 || + rsaKey[RSA_KEY_SIZE-16-1] != 0x00) + EXCEPTION(DRM_ERR_ENCRYPTION_KEY, "Unable to retrieve encryption key"); + + memcpy(decryptedKey, &rsaKey[sizeof(rsaKey)-16], 16); + } + else + { + GOUROU_LOG(DEBUG, "Use provided encryption key"); + if (encryptionKeySize != 16) + EXCEPTION(DRM_ERR_ENCRYPTION_KEY, "Provided encryption key must be 16 bytes"); + + memcpy(decryptedKey, encryptionKey, encryptionKeySize); + } } - + void DRMProcessor::removeEPubDRM(const std::string& filenameIn, const std::string& filenameOut, - const unsigned char* encryptionKey, unsigned encryptionKeySize) + const unsigned char* encryptionKey, unsigned encryptionKeySize) { - ByteArray zipData; - bool removeEncryptionXML = true; - void* zipHandler = client->zipOpen(filenameOut); + ByteArray zipData; + bool removeEncryptionXML = true; + void* zipHandler = client->zipOpen(filenameOut); - client->zipReadFile(zipHandler, "META-INF/rights.xml", zipData); - pugi::xml_document rightsDoc; - rightsDoc.load_string((const char*)zipData.data()); + client->zipReadFile(zipHandler, "META-INF/rights.xml", zipData); + pugi::xml_document rightsDoc; + rightsDoc.load_string((const char*)zipData.data()); - unsigned char decryptedKey[16]; + unsigned char decryptedKey[16]; - decryptADEPTKey(rightsDoc, decryptedKey, encryptionKey, encryptionKeySize); - - client->zipReadFile(zipHandler, "META-INF/encryption.xml", zipData); - pugi::xml_document encryptionDoc; - encryptionDoc.load_string((const char*)zipData.data()); + decryptADEPTKey(rightsDoc, decryptedKey, encryptionKey, encryptionKeySize); - pugi::xpath_node_set nodeSet = encryptionDoc.select_nodes("//EncryptedData"); + client->zipReadFile(zipHandler, "META-INF/encryption.xml", zipData); + pugi::xml_document encryptionDoc; + encryptionDoc.load_string((const char*)zipData.data()); - for (pugi::xpath_node_set::const_iterator it = nodeSet.begin(); - it != nodeSet.end(); ++it) - { - pugi::xml_node encryptionMethod = it->node().child("EncryptionMethod"); - pugi::xml_node cipherReference = it->node().child("CipherData").child("CipherReference"); + pugi::xpath_node_set nodeSet = encryptionDoc.select_nodes("//EncryptedData"); - std::string encryptionType = encryptionMethod.attribute("Algorithm").value(); - std::string encryptedFile = cipherReference.attribute("URI").value(); - - if (encryptionType == "") - { - EXCEPTION(DRM_MISSING_PARAMETER, "Missing Algorithm attribute in encryption.xml"); - } - else if (encryptionType == "http://www.w3.org/2001/04/xmlenc#aes128-cbc") - { - if (encryptedFile == "") - { - EXCEPTION(DRM_MISSING_PARAMETER, "Missing URI attribute in encryption.xml"); - } + for (pugi::xpath_node_set::const_iterator it = nodeSet.begin(); + it != nodeSet.end(); ++it) + { + pugi::xml_node encryptionMethod = it->node().child("EncryptionMethod"); + pugi::xml_node cipherReference = it->node().child("CipherData").child("CipherReference"); - GOUROU_LOG(DEBUG, "Encrypted file " << encryptedFile); + std::string encryptionType = encryptionMethod.attribute("Algorithm").value(); + std::string encryptedFile = cipherReference.attribute("URI").value(); - client->zipReadFile(zipHandler, encryptedFile, zipData, false); - - unsigned char* _data = zipData.data(); - ByteArray clearData(zipData.length()-16+1, true); /* Reserve 1 byte for 'Z' */ - unsigned char* _clearData = clearData.data(); - gourou::ByteArray inflateData(true); - unsigned int dataOutLength; + if (encryptionType == "") + { + EXCEPTION(DRM_MISSING_PARAMETER, "Missing Algorithm attribute in encryption.xml"); + } + else if (encryptionType == "http://www.w3.org/2001/04/xmlenc#aes128-cbc") + { + if (encryptedFile == "") + { + EXCEPTION(DRM_MISSING_PARAMETER, "Missing URI attribute in encryption.xml"); + } - client->decrypt(CryptoInterface::ALGO_AES, CryptoInterface::CHAIN_CBC, - decryptedKey, sizeof(decryptedKey), /* Key */ - _data, 16, /* IV */ - &_data[16], zipData.length()-16, - _clearData, &dataOutLength); + GOUROU_LOG(DEBUG, "Encrypted file " << encryptedFile); - // Add 'Z' at the end, done in ineptepub.py - _clearData[dataOutLength] = 'Z'; - clearData.resize(dataOutLength+1); + client->zipReadFile(zipHandler, encryptedFile, zipData, false); - try - { - client->inflate(clearData, inflateData); - client->zipWriteFile(zipHandler, encryptedFile, inflateData); - } - catch(gourou::Exception& e) - { - if (e.getErrorCode() == CLIENT_ZIP_ERROR) - { - GOUROU_LOG(ERROR, e.what() << std::endl << "Skip file " << encryptedFile); - } - else - throw e; - } + unsigned char* _data = zipData.data(); + ByteArray clearData(zipData.length()-16+1, true); /* Reserve 1 byte for 'Z' */ + unsigned char* _clearData = clearData.data(); + gourou::ByteArray inflateData(true); + unsigned int dataOutLength; - it->node().parent().remove_child(it->node()); - } - else - { - GOUROU_LOG(WARN, "Unsupported encryption algorithm " << encryptionType << ", for file " << encryptedFile); - removeEncryptionXML = false; - } - } - - client->zipDeleteFile(zipHandler, "META-INF/rights.xml"); - if (removeEncryptionXML) - client->zipDeleteFile(zipHandler, "META-INF/encryption.xml"); - else - { - StringXMLWriter xmlWriter; - encryptionDoc.save(xmlWriter, " "); - std::string xmlStr = xmlWriter.getResult(); - ByteArray ba(xmlStr); - client->zipWriteFile(zipHandler, "META-INF/encryption.xml", ba); - } - - client->zipClose(zipHandler); + client->decrypt(CryptoInterface::ALGO_AES, CryptoInterface::CHAIN_CBC, + decryptedKey, sizeof(decryptedKey), /* Key */ + _data, 16, /* IV */ + &_data[16], zipData.length()-16, + _clearData, &dataOutLength); + + // Add 'Z' at the end, done in ineptepub.py + _clearData[dataOutLength] = 'Z'; + clearData.resize(dataOutLength+1); + + try + { + client->inflate(clearData, inflateData); + client->zipWriteFile(zipHandler, encryptedFile, inflateData); + } + catch(gourou::Exception& e) + { + if (e.getErrorCode() == CLIENT_ZIP_ERROR) + { + GOUROU_LOG(ERROR, e.what() << std::endl << "Skip file " << encryptedFile); + } + else + throw e; + } + + it->node().parent().remove_child(it->node()); + } + else + { + GOUROU_LOG(WARN, "Unsupported encryption algorithm " << encryptionType << ", for file " << encryptedFile); + removeEncryptionXML = false; + } + } + + client->zipDeleteFile(zipHandler, "META-INF/rights.xml"); + if (removeEncryptionXML) + client->zipDeleteFile(zipHandler, "META-INF/encryption.xml"); + else + { + StringXMLWriter xmlWriter; + encryptionDoc.save(xmlWriter, " "); + std::string xmlStr = xmlWriter.getResult(); + ByteArray ba(xmlStr); + client->zipWriteFile(zipHandler, "META-INF/encryption.xml", ba); + } + + client->zipClose(zipHandler); } - + void DRMProcessor::generatePDFObjectKey(int version, - const unsigned char* masterKey, unsigned int masterKeyLength, - int objectId, int objectGenerationNumber, - unsigned char* keyOut) + const unsigned char* masterKey, unsigned int masterKeyLength, + int objectId, int objectGenerationNumber, + unsigned char* keyOut) { - switch(version) - { - case 4: - ByteArray toHash(masterKey, masterKeyLength); - uint32_t _objectId = objectId; - uint32_t _objectGenerationNumber = objectGenerationNumber; - toHash.append((const unsigned char*)&_objectId, 3); // Fill 3 bytes - toHash.append((const unsigned char*)&_objectGenerationNumber, 2); // Fill 2 bytes + switch(version) + { + case 4: + ByteArray toHash(masterKey, masterKeyLength); + uint32_t _objectId = objectId; + uint32_t _objectGenerationNumber = objectGenerationNumber; + toHash.append((const unsigned char*)&_objectId, 3); // Fill 3 bytes + toHash.append((const unsigned char*)&_objectGenerationNumber, 2); // Fill 2 bytes - client->digest("md5", toHash.data(), toHash.length(), keyOut); - break; - } + client->digest("md5", toHash.data(), toHash.length(), keyOut); + break; + } } - + void DRMProcessor::removePDFDRM(const std::string& filenameIn, const std::string& filenameOut, - const unsigned char* encryptionKey, unsigned encryptionKeySize) + const unsigned char* encryptionKey, unsigned encryptionKeySize) { - uPDFParser::Parser parser; - bool EBXHandlerFound = false; - - if (filenameIn == filenameOut) - { - EXCEPTION(DRM_IN_OUT_EQUALS, "PDF IN must be different of PDF OUT"); - } - - try - { - GOUROU_LOG(DEBUG, "Parse PDF"); - parser.parse(filenameIn); - } - catch(std::invalid_argument& e) - { - GOUROU_LOG(ERROR, "Invalid PDF"); - return; - } + uPDFParser::Parser parser; + bool EBXHandlerFound = false; - uPDFParser::Integer* ebxVersion; - std::vector objects = parser.objects(); - std::vector::iterator it; - std::vector::reverse_iterator rIt; - std::vector ebxObjects; - unsigned char decryptedKey[16]; - int ebxId; - - for(rIt = objects.rbegin(); rIt != objects.rend(); rIt++) - { - // Update EBX_HANDLER with rights - if ((*rIt)->hasKey("Filter") && (**rIt)["Filter"]->str() == "/EBX_HANDLER") - { - EBXHandlerFound = true; - uPDFParser::Object* ebx = *rIt; - - ebxVersion = (uPDFParser::Integer*)(*ebx)["V"]; - if (ebxVersion->value() != 4) - { - EXCEPTION(DRM_VERSION_NOT_SUPPORTED, "EBX encryption version not supported " << ebxVersion->value()); - } + if (filenameIn == filenameOut) + { + EXCEPTION(DRM_IN_OUT_EQUALS, "PDF IN must be different of PDF OUT"); + } - if (!(ebx->hasKey("ADEPT_LICENSE"))) - { - EXCEPTION(DRM_ERR_ENCRYPTION_KEY, "No ADEPT_LICENSE found"); - } - - uPDFParser::String* licenseObject = (uPDFParser::String*)(*ebx)["ADEPT_LICENSE"]; - - std::string value = licenseObject->value(); - // Pad with '=' - while ((value.size() % 4)) - value += "="; - ByteArray zippedData = ByteArray::fromBase64(value); + try + { + GOUROU_LOG(DEBUG, "Parse PDF"); + parser.parse(filenameIn); + } + catch(std::invalid_argument& e) + { + GOUROU_LOG(ERROR, "Invalid PDF"); + return; + } - if (zippedData.size() == 0) - EXCEPTION(DRM_ERR_ENCRYPTION_KEY, "Invalid ADEPT_LICENSE"); - - ByteArray rightsStr; - client->inflate(zippedData, rightsStr); + uPDFParser::Integer* ebxVersion; + std::vector objects = parser.objects(); + std::vector::iterator it; + std::vector::reverse_iterator rIt; + std::vector ebxObjects; + unsigned char decryptedKey[16]; + int ebxId; - pugi::xml_document rightsDoc; - rightsDoc.load_string((const char*)rightsStr.data()); + for(rIt = objects.rbegin(); rIt != objects.rend(); rIt++) + { + // Update EBX_HANDLER with rights + if ((*rIt)->hasKey("Filter") && (**rIt)["Filter"]->str() == "/EBX_HANDLER") + { + EBXHandlerFound = true; + uPDFParser::Object* ebx = *rIt; - decryptADEPTKey(rightsDoc, decryptedKey, encryptionKey, encryptionKeySize); - - ebxId = ebx->objectId(); + ebxVersion = (uPDFParser::Integer*)(*ebx)["V"]; + if (ebxVersion->value() != 4) + { + EXCEPTION(DRM_VERSION_NOT_SUPPORTED, "EBX encryption version not supported " << ebxVersion->value()); + } - break; - } - } + if (!(ebx->hasKey("ADEPT_LICENSE"))) + { + EXCEPTION(DRM_ERR_ENCRYPTION_KEY, "No ADEPT_LICENSE found"); + } - if (!EBXHandlerFound) - { - EXCEPTION(DRM_ERR_ENCRYPTION_KEY, "EBX_HANDLER not found"); - } + uPDFParser::String* licenseObject = (uPDFParser::String*)(*ebx)["ADEPT_LICENSE"]; - for(it = objects.begin(); it != objects.end(); it++) - { - uPDFParser::Object* object = *it; - - if (object->objectId() == ebxId) - { - ebxObjects.push_back(object); - continue; - } + std::string value = licenseObject->value(); + // Pad with '=' + while ((value.size() % 4)) + value += "="; + ByteArray zippedData = ByteArray::fromBase64(value); - // Should not decrypt XRef stream - if (object->hasKey("Type") && (*object)["Type"]->str() == "/XRef") - { - GOUROU_LOG(DEBUG, "XRef stream at " << object->offset()); - continue; - } - - GOUROU_LOG(DEBUG, "Obj " << object->objectId()); + if (zippedData.size() == 0) + EXCEPTION(DRM_ERR_ENCRYPTION_KEY, "Invalid ADEPT_LICENSE"); - unsigned char tmpKey[sizeof(decryptedKey)]; + ByteArray rightsStr; + client->inflate(zippedData, rightsStr); - generatePDFObjectKey(ebxVersion->value(), - decryptedKey, sizeof(decryptedKey), - object->objectId(), object->generationNumber(), - tmpKey); + pugi::xml_document rightsDoc; + rightsDoc.load_string((const char*)rightsStr.data()); - uPDFParser::Dictionary& dictionary = object->dictionary(); - std::map& dictValues = dictionary.value(); - std::map::iterator dictIt; - std::map decodedStrings; - std::string string; - - /* Parse dictionary */ - for (dictIt = dictValues.begin(); dictIt != dictValues.end(); dictIt++) - { - uPDFParser::DataType* dictData = dictIt->second; - if (dictData->type() == uPDFParser::DataType::STRING) - { - string = ((uPDFParser::String*) dictData)->unescapedValue(); - - unsigned char* encryptedData = (unsigned char*)string.c_str(); - unsigned int dataLength = string.size(); - unsigned char* clearData = new unsigned char[dataLength]; - unsigned int dataOutLength; + decryptADEPTKey(rightsDoc, decryptedKey, encryptionKey, encryptionKeySize); - GOUROU_LOG(DEBUG, "Decrypt string " << dictIt->first << " " << dataLength); + ebxId = ebx->objectId(); - client->decrypt(CryptoInterface::ALGO_RC4, CryptoInterface::CHAIN_ECB, - tmpKey, sizeof(tmpKey), /* Key */ - NULL, 0, /* IV */ - encryptedData, dataLength, - clearData, &dataOutLength); + break; + } + } - decodedStrings[dictIt->first] = new uPDFParser::String( - std::string((const char*)clearData, dataOutLength)); + if (!EBXHandlerFound) + { + EXCEPTION(DRM_ERR_ENCRYPTION_KEY, "EBX_HANDLER not found"); + } - delete[] clearData; - } - else if (dictData->type() == uPDFParser::DataType::HEXASTRING) - { - string = ((uPDFParser::HexaString*) dictData)->value(); - ByteArray hexStr = ByteArray::fromHex(string); - - unsigned char* encryptedData = hexStr.data(); - unsigned int dataLength = hexStr.size(); - unsigned char* clearData = new unsigned char[dataLength]; - unsigned int dataOutLength; + for(it = objects.begin(); it != objects.end(); it++) + { + uPDFParser::Object* object = *it; - GOUROU_LOG(DEBUG, "Decrypt hexa string " << dictIt->first << " " << dataLength); + if (object->objectId() == ebxId) + { + ebxObjects.push_back(object); + continue; + } - client->decrypt(CryptoInterface::ALGO_RC4, CryptoInterface::CHAIN_ECB, - tmpKey, sizeof(tmpKey), /* Key */ - NULL, 0, /* IV */ - encryptedData, dataLength, - clearData, &dataOutLength); + // Should not decrypt XRef stream + if (object->hasKey("Type") && (*object)["Type"]->str() == "/XRef") + { + GOUROU_LOG(DEBUG, "XRef stream at " << object->offset()); + continue; + } - ByteArray clearHexStr = ByteArray(clearData, dataOutLength); - decodedStrings[dictIt->first] = new uPDFParser::HexaString( - clearHexStr.toHex()); + GOUROU_LOG(DEBUG, "Obj " << object->objectId()); - delete[] clearData; - } - } - - for (dictIt = decodedStrings.begin(); dictIt != decodedStrings.end(); dictIt++) - dictionary.replace(dictIt->first, dictIt->second); - - std::vector::iterator datasIt; - std::vector& datas = object->data(); - uPDFParser::Stream* stream; - - for (datasIt = datas.begin(); datasIt != datas.end(); datasIt++) - { - if ((*datasIt)->type() != uPDFParser::DataType::STREAM) - continue; + unsigned char tmpKey[sizeof(decryptedKey)]; - stream = (uPDFParser::Stream*) (*datasIt); - unsigned char* encryptedData = stream->data(); - unsigned int dataLength = stream->dataLength(); - unsigned char* clearData = new unsigned char[dataLength]; - unsigned int dataOutLength; - - GOUROU_LOG(DEBUG, "Decrypt stream id " << object->objectId() << ", size " << stream->dataLength()); + generatePDFObjectKey(ebxVersion->value(), + decryptedKey, sizeof(decryptedKey), + object->objectId(), object->generationNumber(), + tmpKey); - client->decrypt(CryptoInterface::ALGO_RC4, CryptoInterface::CHAIN_ECB, - tmpKey, sizeof(tmpKey), /* Key */ - NULL, 0, /* IV */ - encryptedData, dataLength, - clearData, &dataOutLength); - - stream->setData(clearData, dataOutLength, true); - if (dataOutLength != dataLength) - GOUROU_LOG(DEBUG, "New size " << dataOutLength); - } - } + uPDFParser::Dictionary& dictionary = object->dictionary(); + std::map& dictValues = dictionary.value(); + std::map::iterator dictIt; + std::map decodedStrings; + std::string string; - for(it = ebxObjects.begin(); it != ebxObjects.end(); it++) - parser.removeObject(*it); - - uPDFParser::Object& trailer = parser.getTrailer(); - trailer.deleteKey("Encrypt"); + /* Parse dictionary */ + for (dictIt = dictValues.begin(); dictIt != dictValues.end(); dictIt++) + { + uPDFParser::DataType* dictData = dictIt->second; + if (dictData->type() == uPDFParser::DataType::STRING) + { + string = ((uPDFParser::String*) dictData)->unescapedValue(); - parser.write(filenameOut); + unsigned char* encryptedData = (unsigned char*)string.c_str(); + unsigned int dataLength = string.size(); + unsigned char* clearData = new unsigned char[dataLength]; + unsigned int dataOutLength; + + GOUROU_LOG(DEBUG, "Decrypt string " << dictIt->first << " " << dataLength); + + client->decrypt(CryptoInterface::ALGO_RC4, CryptoInterface::CHAIN_ECB, + tmpKey, sizeof(tmpKey), /* Key */ + NULL, 0, /* IV */ + encryptedData, dataLength, + clearData, &dataOutLength); + + decodedStrings[dictIt->first] = new uPDFParser::String( + std::string((const char*)clearData, dataOutLength)); + + delete[] clearData; + } + else if (dictData->type() == uPDFParser::DataType::HEXASTRING) + { + string = ((uPDFParser::HexaString*) dictData)->value(); + ByteArray hexStr = ByteArray::fromHex(string); + + unsigned char* encryptedData = hexStr.data(); + unsigned int dataLength = hexStr.size(); + unsigned char* clearData = new unsigned char[dataLength]; + unsigned int dataOutLength; + + GOUROU_LOG(DEBUG, "Decrypt hexa string " << dictIt->first << " " << dataLength); + + client->decrypt(CryptoInterface::ALGO_RC4, CryptoInterface::CHAIN_ECB, + tmpKey, sizeof(tmpKey), /* Key */ + NULL, 0, /* IV */ + encryptedData, dataLength, + clearData, &dataOutLength); + + ByteArray clearHexStr = ByteArray(clearData, dataOutLength); + decodedStrings[dictIt->first] = new uPDFParser::HexaString( + clearHexStr.toHex()); + + delete[] clearData; + } + } + + for (dictIt = decodedStrings.begin(); dictIt != decodedStrings.end(); dictIt++) + dictionary.replace(dictIt->first, dictIt->second); + + std::vector::iterator datasIt; + std::vector& datas = object->data(); + uPDFParser::Stream* stream; + + for (datasIt = datas.begin(); datasIt != datas.end(); datasIt++) + { + if ((*datasIt)->type() != uPDFParser::DataType::STREAM) + continue; + + stream = (uPDFParser::Stream*) (*datasIt); + unsigned char* encryptedData = stream->data(); + unsigned int dataLength = stream->dataLength(); + unsigned char* clearData = new unsigned char[dataLength]; + unsigned int dataOutLength; + + GOUROU_LOG(DEBUG, "Decrypt stream id " << object->objectId() << ", size " << stream->dataLength()); + + client->decrypt(CryptoInterface::ALGO_RC4, CryptoInterface::CHAIN_ECB, + tmpKey, sizeof(tmpKey), /* Key */ + NULL, 0, /* IV */ + encryptedData, dataLength, + clearData, &dataOutLength); + + stream->setData(clearData, dataOutLength, true); + if (dataOutLength != dataLength) + GOUROU_LOG(DEBUG, "New size " << dataOutLength); + } + } + + for(it = ebxObjects.begin(); it != ebxObjects.end(); it++) + parser.removeObject(*it); + + uPDFParser::Object& trailer = parser.getTrailer(); + trailer.deleteKey("Encrypt"); + + parser.write(filenameOut); } - + void DRMProcessor::removeDRM(const std::string& filenameIn, const std::string& filenameOut, - ITEM_TYPE type, const unsigned char* encryptionKey, unsigned encryptionKeySize) + ITEM_TYPE type, const unsigned char* encryptionKey, unsigned encryptionKeySize) { - if (type == PDF) - removePDFDRM(filenameIn, filenameOut, encryptionKey, encryptionKeySize); - else - removeEPubDRM(filenameIn, filenameOut, encryptionKey, encryptionKeySize); + if (type == PDF) + removePDFDRM(filenameIn, filenameOut, encryptionKey, encryptionKeySize); + else + removeEPubDRM(filenameIn, filenameOut, encryptionKey, encryptionKeySize); } } diff --git a/src/loan_token.cpp b/src/loan_token.cpp index 14b50eb..fe320aa 100644 --- a/src/loan_token.cpp +++ b/src/loan_token.cpp @@ -24,57 +24,57 @@ namespace gourou { LoanToken::LoanToken(pugi::xml_document& doc) { - pugi::xml_node node = doc.select_node("/envelope/loanToken").node(); + pugi::xml_node node = doc.select_node("/envelope/loanToken").node(); - if (!node) - EXCEPTION(FFI_INVALID_LOAN_TOKEN, "No loanToken element in document"); + if (!node) + EXCEPTION(FFI_INVALID_LOAN_TOKEN, "No loanToken element in document"); - node = doc.select_node("/envelope/fulfillmentResult/fulfillment").node(); + node = doc.select_node("/envelope/fulfillmentResult/fulfillment").node(); - if (node) - properties["id"] = node.first_child().value(); - else - { - EXCEPTION(FFI_INVALID_LOAN_TOKEN, "No fulfillment element in document"); - } + if (node) + properties["id"] = node.first_child().value(); + else + { + EXCEPTION(FFI_INVALID_LOAN_TOKEN, "No fulfillment element in document"); + } - node = doc.select_node("/envelope/loanToken/operatorURL").node(); + node = doc.select_node("/envelope/loanToken/operatorURL").node(); - if (!node) - EXCEPTION(FFI_INVALID_LOAN_TOKEN, "No loanToken/operatorURL element in document"); + if (!node) + EXCEPTION(FFI_INVALID_LOAN_TOKEN, "No loanToken/operatorURL element in document"); - properties["operatorURL"] = node.first_child().value(); + properties["operatorURL"] = node.first_child().value(); - node = doc.select_node("/envelope/fulfillmentResult/resourceItemInfo/licenseToken/permissions/display/until").node(); + node = doc.select_node("/envelope/fulfillmentResult/resourceItemInfo/licenseToken/permissions/display/until").node(); - if (node) - properties["validity"] = node.first_child().value(); - else - { - node = doc.select_node("/envelope/fulfillmentResult/resourceItemInfo/licenseToken/permissions/play/until").node(); + if (node) + properties["validity"] = node.first_child().value(); + else + { + node = doc.select_node("/envelope/fulfillmentResult/resourceItemInfo/licenseToken/permissions/play/until").node(); - if (node) - properties["validity"] = node.first_child().value(); - else - EXCEPTION(FFI_INVALID_LOAN_TOKEN, "No loanToken/operatorURL element in document"); - } + if (node) + properties["validity"] = node.first_child().value(); + else + EXCEPTION(FFI_INVALID_LOAN_TOKEN, "No loanToken/operatorURL element in document"); + } } std::string LoanToken::getProperty(const std::string& property, const std::string& _default) { - if (properties.find(property) == properties.end()) - { - if (_default == "") - EXCEPTION(GOUROU_INVALID_PROPERTY, "Invalid property " << property); + if (properties.find(property) == properties.end()) + { + if (_default == "") + EXCEPTION(GOUROU_INVALID_PROPERTY, "Invalid property " << property); - return _default; - } + return _default; + } - return properties[property]; + return properties[property]; } std::string LoanToken::operator[](const std::string& property) { - return getProperty(property); + return getProperty(property); } } diff --git a/src/user.cpp b/src/user.cpp index f09cb73..14251b0 100644 --- a/src/user.cpp +++ b/src/user.cpp @@ -24,60 +24,60 @@ namespace gourou { User::User(DRMProcessor* processor):processor(processor) {} - + User::User(DRMProcessor* processor, const std::string& activationFile): - processor(processor), activationFile(activationFile) + processor(processor), activationFile(activationFile) { - parseActivationFile(); + parseActivationFile(); } void User::parseActivationFile(bool throwOnNull) { - GOUROU_LOG(DEBUG, "Parse activation file " << activationFile); - - if (!activationDoc.load_file(activationFile.c_str())) - { - if (throwOnNull) - EXCEPTION(USER_INVALID_ACTIVATION_FILE, "Invalid activation file"); - return; - } + GOUROU_LOG(DEBUG, "Parse activation file " << activationFile); - try - { - pkcs12 = gourou::extractTextElem(activationDoc, "//adept:pkcs12", throwOnNull); - uuid = gourou::extractTextElem(activationDoc, "//adept:user", throwOnNull); - deviceUUID = gourou::extractTextElem(activationDoc, "//device", throwOnNull); - deviceFingerprint = gourou::extractTextElem(activationDoc, "//fingerprint", throwOnNull); - authenticationCertificate = gourou::extractTextElem(activationDoc, "//adept:authenticationCertificate", throwOnNull); - privateLicenseKey = gourou::extractTextElem(activationDoc, "//adept:privateLicenseKey", throwOnNull); + if (!activationDoc.load_file(activationFile.c_str())) + { + if (throwOnNull) + EXCEPTION(USER_INVALID_ACTIVATION_FILE, "Invalid activation file"); + return; + } - pugi::xpath_node xpath_node = activationDoc.select_node("//adept:username"); - if (xpath_node) - loginMethod = xpath_node.node().attribute("method").value(); - else - { - if (throwOnNull) - EXCEPTION(USER_INVALID_ACTIVATION_FILE, "Invalid activation file"); - } - - if (loginMethod == "anonymous") - username = "anonymous"; - else - username = gourou::extractTextElem(activationDoc, "//adept:username", throwOnNull); - - pugi::xpath_node_set nodeSet = activationDoc.select_nodes("//adept:licenseServices/adept:licenseServiceInfo"); - for (pugi::xpath_node_set::const_iterator it = nodeSet.begin(); - it != nodeSet.end(); ++it) - { - std::string url = gourou::extractTextElem(it->node(), "adept:licenseURL"); - std::string certificate = gourou::extractTextElem(it->node(), "adept:certificate"); - licenseServiceCertificates[url] = certificate; - } - } - catch(gourou::Exception& e) - { - EXCEPTION(USER_INVALID_ACTIVATION_FILE, "Invalid activation file"); - } + try + { + pkcs12 = gourou::extractTextElem(activationDoc, "//adept:pkcs12", throwOnNull); + uuid = gourou::extractTextElem(activationDoc, "//adept:user", throwOnNull); + deviceUUID = gourou::extractTextElem(activationDoc, "//device", throwOnNull); + deviceFingerprint = gourou::extractTextElem(activationDoc, "//fingerprint", throwOnNull); + authenticationCertificate = gourou::extractTextElem(activationDoc, "//adept:authenticationCertificate", throwOnNull); + privateLicenseKey = gourou::extractTextElem(activationDoc, "//adept:privateLicenseKey", throwOnNull); + + pugi::xpath_node xpath_node = activationDoc.select_node("//adept:username"); + if (xpath_node) + loginMethod = xpath_node.node().attribute("method").value(); + else + { + if (throwOnNull) + EXCEPTION(USER_INVALID_ACTIVATION_FILE, "Invalid activation file"); + } + + if (loginMethod == "anonymous") + username = "anonymous"; + else + username = gourou::extractTextElem(activationDoc, "//adept:username", throwOnNull); + + pugi::xpath_node_set nodeSet = activationDoc.select_nodes("//adept:licenseServices/adept:licenseServiceInfo"); + for (pugi::xpath_node_set::const_iterator it = nodeSet.begin(); + it != nodeSet.end(); ++it) + { + std::string url = gourou::extractTextElem(it->node(), "adept:licenseURL"); + std::string certificate = gourou::extractTextElem(it->node(), "adept:certificate"); + licenseServiceCertificates[url] = certificate; + } + } + catch(gourou::Exception& e) + { + EXCEPTION(USER_INVALID_ACTIVATION_FILE, "Invalid activation file"); + } } std::string& User::getUUID() { return uuid; } @@ -88,136 +88,136 @@ namespace gourou { std::string& User::getLoginMethod() { return loginMethod; } std::string& User::getAuthenticationCertificate() { return authenticationCertificate; } std::string& User::getPrivateLicenseKey() { return privateLicenseKey; } - + void User::readActivation(pugi::xml_document& doc) { - if (!doc.load_file(activationFile.c_str())) - EXCEPTION(USER_INVALID_ACTIVATION_FILE, "Invalid activation file"); + if (!doc.load_file(activationFile.c_str())) + EXCEPTION(USER_INVALID_ACTIVATION_FILE, "Invalid activation file"); } void User::updateActivationFile(const char* data) { - GOUROU_LOG(INFO, "Update Activation file : " << std::endl << data); + GOUROU_LOG(INFO, "Update Activation file : " << std::endl << data); - writeFile(activationFile, (unsigned char*)data, strlen(data)); - - parseActivationFile(false); + writeFile(activationFile, (unsigned char*)data, strlen(data)); + + parseActivationFile(false); } - + void User::updateActivationFile(const pugi::xml_document& doc) { - StringXMLWriter xmlWriter; - doc.save(xmlWriter, " "); - updateActivationFile(xmlWriter.getResult().c_str()); + StringXMLWriter xmlWriter; + doc.save(xmlWriter, " "); + updateActivationFile(xmlWriter.getResult().c_str()); } std::string User::getProperty(const std::string property) { - pugi::xpath_node xpathRes = activationDoc.select_node(property.c_str()); - if (!xpathRes) - EXCEPTION(USER_NO_PROPERTY, "Property " << property << " not found in activation.xml"); + pugi::xpath_node xpathRes = activationDoc.select_node(property.c_str()); + if (!xpathRes) + EXCEPTION(USER_NO_PROPERTY, "Property " << property << " not found in activation.xml"); - std::string res = xpathRes.node().first_child().value(); - return trim(res); + std::string res = xpathRes.node().first_child().value(); + return trim(res); } - + pugi::xpath_node_set User::getProperties(const std::string property) { - return activationDoc.select_nodes(property.c_str()); + return activationDoc.select_nodes(property.c_str()); } User* User::createUser(DRMProcessor* processor, const std::string& dirName, const std::string& ACSServer) { - struct stat _stat; + struct stat _stat; - if (stat(dirName.c_str(), &_stat) != 0) - { - if (mkdir_p(dirName.c_str(), S_IRWXU)) - EXCEPTION(USER_MKPATH, "Unable to create " << dirName) - } + if (stat(dirName.c_str(), &_stat) != 0) + { + if (mkdir_p(dirName.c_str(), S_IRWXU)) + EXCEPTION(USER_MKPATH, "Unable to create " << dirName) + } - User* user = new User(processor); - bool doUpdate = false; - - user->activationFile = dirName + "/activation.xml"; - user->parseActivationFile(false); + User* user = new User(processor); + bool doUpdate = 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->activationFile = dirName + "/activation.xml"; + user->parseActivationFile(false); - 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 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; - 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 (nodeActivationInfo && nodeActivationServiceInfo) + { + GOUROU_LOG(DEBUG, "Read previous activation configuration"); + activationInfo = nodeActivationInfo.node(); + activationServiceInfo = nodeActivationServiceInfo.node(); + } + else + { + GOUROU_LOG(DEBUG, "Create new activation"); - if (!nodeAuthenticationCertificate) - { - GOUROU_LOG(DEBUG, "Create new activation, authentication part"); + user->activationDoc.reset(); - 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; - } + 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; - if (doUpdate) - user->updateActivationFile(user->activationDoc); - - - return user; + // 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; } std::string User::getLicenseServiceCertificate(std::string url) { - if (licenseServiceCertificates.count(trim(url))) - return licenseServiceCertificates[trim(url)]; + if (licenseServiceCertificates.count(trim(url))) + return licenseServiceCertificates[trim(url)]; - return ""; + return ""; } } diff --git a/utils/Makefile b/utils/Makefile index 5fa5fe2..ba85377 100644 --- a/utils/Makefile +++ b/utils/Makefile @@ -6,9 +6,10 @@ TARGETS=$(TARGET_BINARIES) launcher MAN_PAGES=acsmdownloader adept_activate adept_remove adept_loan_mgt -CXXFLAGS=-Wall -fPIC -I$(ROOT)/include +CXXFLAGS=-Wall -fPIC -I$(ROOT)/include -fdata-sections -ffunction-sections STATIC_DEP= +# LDFLAGS += -Wl,--gc-sections LDFLAGS += -L$(ROOT) -lcrypto -lzip -lz -lcurl -lpugixml ifneq ($(STATIC_UTILS),) @@ -31,7 +32,7 @@ COMMON_LIB = utils.a all: $(TARGETS) ${COMMON_LIB}: $(COMMON_DEPS) - $(CXX) $(CXXFLAGS) $(COMMON_DEPS) $(LDFLAGS) -c + $(CXX) $(CXXFLAGS) $(COMMON_DEPS) -c $(AR) crs $@ $(COMMON_OBJECTS) %: %.cpp $(COMMON_LIB) $(STATIC_DEP) diff --git a/utils/acsmdownloader.cpp b/utils/acsmdownloader.cpp index 4d23748..554a1a9 100644 --- a/utils/acsmdownloader.cpp +++ b/utils/acsmdownloader.cpp @@ -4,16 +4,16 @@ All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - + * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. + notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. * Neither the name of the copyright holder nor the - names of its contributors may be used to endorse or promote products - derived from this software without specific prior written permission. - + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE @@ -52,137 +52,137 @@ static bool notify = true; class ACSMDownloader { public: - + int run() { - int ret = 0; - try - { - gourou::DRMProcessor processor(&client, deviceFile, activationFile, devicekeyFile); - gourou::User* user = processor.getUser(); - - if (exportPrivateKey) - { - std::string filename; - if (!outputFile) - filename = std::string("Adobe_PrivateLicenseKey--") + user->getUsername() + ".der"; - else - filename = outputFile; - - if (outputDir) - { - if (!fileExists(outputDir)) - mkpath(outputDir); - - filename = std::string(outputDir) + "/" + filename; - } + int ret = 0; + try + { + gourou::DRMProcessor processor(&client, deviceFile, activationFile, devicekeyFile); + gourou::User* user = processor.getUser(); + + if (exportPrivateKey) + { + std::string filename; + if (!outputFile) + filename = std::string("Adobe_PrivateLicenseKey--") + user->getUsername() + ".der"; + else + filename = outputFile; + + if (outputDir) + { + if (!fileExists(outputDir)) + mkpath(outputDir); + + filename = std::string(outputDir) + "/" + filename; + } - processor.exportPrivateLicenseKey(filename); + processor.exportPrivateLicenseKey(filename); - std::cout << "Private license key exported to " << filename << std::endl; - } - else - { - gourou::FulfillmentItem* item = processor.fulfill(acsmFile, notify); + std::cout << "Private license key exported to " << filename << std::endl; + } + else + { + gourou::FulfillmentItem* item = processor.fulfill(acsmFile, notify); - std::string filename; - if (!outputFile) - { - filename = item->getMetadata("title"); - if (filename == "") - filename = "output"; - else - { - // Remove invalid characters - std::replace(filename.begin(), filename.end(), '/', '_'); - } - } - else - filename = outputFile; - - if (outputDir) - { - if (!fileExists(outputDir)) - mkpath(outputDir); + std::string filename; + if (!outputFile) + { + filename = item->getMetadata("title"); + if (filename == "") + filename = "output"; + else + { + // Remove invalid characters + std::replace(filename.begin(), filename.end(), '/', '_'); + } + } + else + filename = outputFile; + + if (outputDir) + { + if (!fileExists(outputDir)) + mkpath(outputDir); - filename = std::string(outputDir) + "/" + filename; - } - - gourou::DRMProcessor::ITEM_TYPE type = processor.download(item, filename, resume); + filename = std::string(outputDir) + "/" + filename; + } + + gourou::DRMProcessor::ITEM_TYPE type = processor.download(item, filename, resume); - if (!outputFile) - { - std::string finalName = filename; - if (type == gourou::DRMProcessor::ITEM_TYPE::PDF) - finalName += ".pdf"; - else - finalName += ".epub"; - rename(filename.c_str(), finalName.c_str()); - filename = finalName; - } - std::cout << "Created " << filename << std::endl; + if (!outputFile) + { + std::string finalName = filename; + if (type == gourou::DRMProcessor::ITEM_TYPE::PDF) + finalName += ".pdf"; + else + finalName += ".epub"; + rename(filename.c_str(), finalName.c_str()); + filename = finalName; + } + std::cout << "Created " << filename << std::endl; - serializeLoanToken(item); - } - } catch(std::exception& e) - { - std::cout << e.what() << std::endl; - ret = 1; - } + serializeLoanToken(item); + } + } catch(std::exception& e) + { + std::cout << e.what() << std::endl; + ret = 1; + } - return ret; + return ret; } void serializeLoanToken(gourou::FulfillmentItem* item) { - gourou::LoanToken* token = item->getLoanToken(); + gourou::LoanToken* token = item->getLoanToken(); - // No loan token available - if (!token) - return; + // No loan token available + if (!token) + return; - pugi::xml_document doc; + pugi::xml_document doc; - pugi::xml_node decl = doc.append_child(pugi::node_declaration); - decl.append_attribute("version") = "1.0"; + pugi::xml_node decl = doc.append_child(pugi::node_declaration); + decl.append_attribute("version") = "1.0"; - pugi::xml_node root = doc.append_child("loanToken"); - gourou::appendTextElem(root, "id", (*token)["id"]); - gourou::appendTextElem(root, "operatorURL", (*token)["operatorURL"]); - gourou::appendTextElem(root, "validity", (*token)["validity"]); - gourou::appendTextElem(root, "name", item->getMetadata("title")); + pugi::xml_node root = doc.append_child("loanToken"); + gourou::appendTextElem(root, "id", (*token)["id"]); + gourou::appendTextElem(root, "operatorURL", (*token)["operatorURL"]); + gourou::appendTextElem(root, "validity", (*token)["validity"]); + gourou::appendTextElem(root, "name", item->getMetadata("title")); - char * activationDir = strdup(deviceFile); - activationDir = dirname(activationDir); - - gourou::StringXMLWriter xmlWriter; - doc.save(xmlWriter, " "); - std::string xmlStr = xmlWriter.getResult(); + char * activationDir = strdup(deviceFile); + activationDir = dirname(activationDir); + + gourou::StringXMLWriter xmlWriter; + doc.save(xmlWriter, " "); + std::string xmlStr = xmlWriter.getResult(); - // Use first bytes of SHA1(id) as filename - unsigned char sha1[gourou::SHA1_LEN]; - client.digest("SHA1", (unsigned char*)(*token)["id"].c_str(), (*token)["id"].size(), sha1); - gourou::ByteArray tmp(sha1, sizeof(sha1)); - std::string filenameHex = tmp.toHex(); - std::string filename(filenameHex.c_str(), ID_HASH_SIZE); - std::string fullPath = std::string(activationDir); - fullPath += std::string ("/") + std::string(LOANS_DIR); - mkpath(fullPath.c_str()); - fullPath += filename + std::string(".xml"); - gourou::writeFile(fullPath, xmlStr); + // Use first bytes of SHA1(id) as filename + unsigned char sha1[gourou::SHA1_LEN]; + client.digest("SHA1", (unsigned char*)(*token)["id"].c_str(), (*token)["id"].size(), sha1); + gourou::ByteArray tmp(sha1, sizeof(sha1)); + std::string filenameHex = tmp.toHex(); + std::string filename(filenameHex.c_str(), ID_HASH_SIZE); + std::string fullPath = std::string(activationDir); + fullPath += std::string ("/") + std::string(LOANS_DIR); + mkpath(fullPath.c_str()); + fullPath += filename + std::string(".xml"); + gourou::writeFile(fullPath, xmlStr); - std::cout << "Loan token serialized into " << fullPath << std::endl; + std::cout << "Loan token serialized into " << fullPath << std::endl; - free(activationDir); + free(activationDir); } - + private: DRMProcessorClientImpl client; -}; +}; static void usage(const char* cmd) -{ +{ std::cout << basename((char*)cmd) << " download EPUB file from ACSM request file" << std::endl << std::endl; std::cout << "Usage: " << basename((char*)cmd) << " [OPTIONS] file.acsm" << std::endl << std::endl; std::cout << "Global Options:" << std::endl; @@ -203,7 +203,7 @@ static void usage(const char* cmd) std::cout << " " << "-k|--device-key-file" << "\t" << "private device key file (eg devicesalt/devkey.bin) from eReader" << std::endl; std::cout << std::endl; - + std::cout << "Environment:" << std::endl; std::cout << "Device file, activation file and device key file are optionals. If not set, they are looked into :" << std::endl << std::endl; std::cout << " * $ADEPT_DIR environment variable" << std::endl; @@ -223,90 +223,90 @@ int main(int argc, char** argv) int verbose = gourou::DRMProcessor::getLogLevel(); while (1) { - int option_index = 0; - static struct option long_options[] = { - {"adept-directory", required_argument, 0, 'D' }, - {"device-file", required_argument, 0, 'd' }, - {"activation-file", required_argument, 0, 'a' }, - {"device-key-file", required_argument, 0, 'k' }, - {"output-dir", required_argument, 0, 'O' }, - {"output-file", required_argument, 0, 'o' }, - {"acsm-file", required_argument, 0, 'f' }, - {"export-private-key",no_argument, 0, 'e' }, - {"resume", no_argument, 0, 'r' }, - {"no-notify", no_argument, 0, 'N' }, - {"verbose", no_argument, 0, 'v' }, - {"version", no_argument, 0, 'V' }, - {"help", no_argument, 0, 'h' }, - {0, 0, 0, 0 } - }; + int option_index = 0; + static struct option long_options[] = { + {"adept-directory", required_argument, 0, 'D' }, + {"device-file", required_argument, 0, 'd' }, + {"activation-file", required_argument, 0, 'a' }, + {"device-key-file", required_argument, 0, 'k' }, + {"output-dir", required_argument, 0, 'O' }, + {"output-file", required_argument, 0, 'o' }, + {"acsm-file", required_argument, 0, 'f' }, + {"export-private-key",no_argument, 0, 'e' }, + {"resume", no_argument, 0, 'r' }, + {"no-notify", no_argument, 0, 'N' }, + {"verbose", no_argument, 0, 'v' }, + {"version", no_argument, 0, 'V' }, + {"help", no_argument, 0, 'h' }, + {0, 0, 0, 0 } + }; - c = getopt_long(argc, argv, "D:d:a:k:O:o:f:erNvVh", + c = getopt_long(argc, argv, "D:d:a:k:O:o:f:erNvVh", long_options, &option_index); - if (c == -1) - break; + if (c == -1) + break; - switch (c) { - case 'D': - _deviceFile = std::string(optarg) + "/device.xml"; - _activationFile = std::string(optarg) + "/activation.xml"; - _devicekeyFile = std::string(optarg) + "/devicesalt"; - deviceFile = _deviceFile.c_str(); - activationFile = _activationFile.c_str(); - devicekeyFile = _devicekeyFile.c_str(); - break; - case 'd': - deviceFile = optarg; - break; - case 'a': - activationFile = optarg; - break; - case 'k': - devicekeyFile = optarg; - break; - case 'f': - acsmFile = optarg; - break; - case 'O': - outputDir = optarg; - break; - case 'o': - outputFile = optarg; - break; - case 'e': - exportPrivateKey = true; - break; - case 'r': - resume = true; - break; - case 'N': - notify = false; - break; - case 'v': - verbose++; - break; - case 'V': - version(); - return 0; - case 'h': - usage(argv[0]); - return 0; - default: - usage(argv[0]); - return -1; - } + switch (c) { + case 'D': + _deviceFile = std::string(optarg) + "/device.xml"; + _activationFile = std::string(optarg) + "/activation.xml"; + _devicekeyFile = std::string(optarg) + "/devicesalt"; + deviceFile = _deviceFile.c_str(); + activationFile = _activationFile.c_str(); + devicekeyFile = _devicekeyFile.c_str(); + break; + case 'd': + deviceFile = optarg; + break; + case 'a': + activationFile = optarg; + break; + case 'k': + devicekeyFile = optarg; + break; + case 'f': + acsmFile = optarg; + break; + case 'O': + outputDir = optarg; + break; + case 'o': + outputFile = optarg; + break; + case 'e': + exportPrivateKey = true; + break; + case 'r': + resume = true; + break; + case 'N': + notify = false; + break; + case 'v': + verbose++; + break; + case 'V': + version(); + return 0; + case 'h': + usage(argv[0]); + return 0; + default: + usage(argv[0]); + return -1; + } } - + gourou::DRMProcessor::setLogLevel(verbose); if (optind == argc-1) - acsmFile = argv[optind]; + acsmFile = argv[optind]; if ((!acsmFile && !exportPrivateKey) || (outputDir && !outputDir[0]) || - (outputFile && !outputFile[0])) + (outputFile && !outputFile[0])) { - usage(argv[0]); - return -1; + usage(argv[0]); + return -1; } ACSMDownloader downloader; @@ -316,44 +316,44 @@ int main(int argc, char** argv) const char* orig; for (i=0; i<(int)ARRAY_SIZE(files); i++) { - orig = *files[i]; - *files[i] = findFile(*files[i]); - if (!*files[i]) - { - std::cout << "Error : " << orig << " doesn't exists, did you activate your device ?" << std::endl; - ret = -1; - hasErrors = true; - } + orig = *files[i]; + *files[i] = findFile(*files[i]); + if (!*files[i]) + { + std::cout << "Error : " << orig << " doesn't exists, did you activate your device ?" << std::endl; + ret = -1; + hasErrors = true; + } } if (hasErrors) - goto end; - + goto end; + if (exportPrivateKey) { - if (acsmFile) - { - usage(argv[0]); - return -1; - } + if (acsmFile) + { + usage(argv[0]); + return -1; + } } else { - if (!fileExists(acsmFile)) - { - std::cout << "Error : " << acsmFile << " doesn't exists" << std::endl; - ret = -1; - goto end; - } + if (!fileExists(acsmFile)) + { + std::cout << "Error : " << acsmFile << " doesn't exists" << std::endl; + ret = -1; + goto end; + } } - + ret = downloader.run(); end: for (i=0; i<(int)ARRAY_SIZE(files); i++) { - if (*files[i]) - free((void*)*files[i]); + if (*files[i]) + free((void*)*files[i]); } return ret; diff --git a/utils/adept_activate.cpp b/utils/adept_activate.cpp index 808fbe1..b224de5 100644 --- a/utils/adept_activate.cpp +++ b/utils/adept_activate.cpp @@ -4,16 +4,16 @@ All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - + * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. + notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. * Neither the name of the copyright holder nor the - names of its contributors may be used to endorse or promote products - derived from this software without specific prior written permission. - + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE @@ -64,69 +64,69 @@ static int getch() { static std::string getpass(const char *prompt, bool show_asterisk=false) { - const char BACKSPACE=127; - const char RETURN=10; + const char BACKSPACE=127; + const char RETURN=10; - std::string password; - unsigned char ch=0; + std::string password; + unsigned char ch=0; - std::cout <signIn(username, password); - processor->activateDevice(); + processor->signIn(username, password); + processor->activateDevice(); - std::cout << username << " fully signed and device activated in " << outputDir << std::endl; - } catch(std::exception& e) - { - std::cout << e.what() << std::endl; - ret = 1; - } + std::cout << username << " fully signed and device activated in " << outputDir << std::endl; + } catch(std::exception& e) + { + std::cout << e.what() << std::endl; + ret = 1; + } - return ret; + return ret; } -}; +}; static void usage(const char* cmd) { std::cout << basename((char*)cmd) << " create new device files used by ADEPT DRM" << std::endl << std::endl; - + std::cout << "Usage: " << basename((char*)cmd) << " OPTIONS" << std::endl << std::endl; std::cout << "Global Options:" << std::endl; @@ -160,129 +160,129 @@ int main(int argc, char** argv) const char* _outputDir = outputDir; int verbose = gourou::DRMProcessor::getLogLevel(); bool anonymous = false; - + while (1) { - int option_index = 0; - static struct option long_options[] = { - {"anonymous", no_argument , 0, 'a' }, - {"username", required_argument, 0, 'u' }, - {"password", required_argument, 0, 'p' }, - {"output-dir", required_argument, 0, 'O' }, - {"hobbes-version",required_argument, 0, 'H' }, - {"random-serial", no_argument, 0, 'r' }, - {"verbose", no_argument, 0, 'v' }, - {"version", no_argument, 0, 'V' }, - {"help", no_argument, 0, 'h' }, - {0, 0, 0, 0 } - }; + int option_index = 0; + static struct option long_options[] = { + {"anonymous", no_argument , 0, 'a' }, + {"username", required_argument, 0, 'u' }, + {"password", required_argument, 0, 'p' }, + {"output-dir", required_argument, 0, 'O' }, + {"hobbes-version",required_argument, 0, 'H' }, + {"random-serial", no_argument, 0, 'r' }, + {"verbose", no_argument, 0, 'v' }, + {"version", no_argument, 0, 'V' }, + {"help", no_argument, 0, 'h' }, + {0, 0, 0, 0 } + }; - c = getopt_long(argc, argv, "au:p:O:H:rvVh", + c = getopt_long(argc, argv, "au:p:O:H:rvVh", long_options, &option_index); - if (c == -1) - break; + if (c == -1) + break; - switch (c) { - case 'a': - anonymous = true; - break; - case 'u': - username = optarg; - break; - case 'p': - password = optarg; - break; - case 'O': - _outputDir = optarg; - break; - case 'H': - hobbesVersion = optarg; - break; - case 'v': - verbose++; - break; - case 'V': - version(); - return 0; - case 'h': - usage(argv[0]); - return 0; - case 'r': - randomSerial = true; - break; - default: - usage(argv[0]); - return -1; - } + switch (c) { + case 'a': + anonymous = true; + break; + case 'u': + username = optarg; + break; + case 'p': + password = optarg; + break; + case 'O': + _outputDir = optarg; + break; + case 'H': + hobbesVersion = optarg; + break; + case 'v': + verbose++; + break; + case 'V': + version(); + return 0; + case 'h': + usage(argv[0]); + return 0; + case 'r': + randomSerial = true; + break; + default: + usage(argv[0]); + return -1; + } } - + gourou::DRMProcessor::setLogLevel(verbose); if ((!username && !anonymous) || - (username && anonymous)) + (username && anonymous)) { - usage(argv[0]); - return -1; + usage(argv[0]); + return -1; } if (anonymous) { - username = "anonymous"; - password = ""; + username = "anonymous"; + password = ""; } - + if (!_outputDir || _outputDir[0] == 0) { - outputDir = strdup(gourou::DRMProcessor::getDefaultAdeptDir().c_str()); + outputDir = strdup(gourou::DRMProcessor::getDefaultAdeptDir().c_str()); } else { - // Relative path - if (_outputDir[0] == '.' || _outputDir[0] != '/') - { - // realpath doesn't works if file/dir doesn't exists - if (fileExists(_outputDir)) - outputDir = strdup(realpath(_outputDir, 0)); - else - outputDir = strdup(abspath(_outputDir)); - } - else - outputDir = strdup(_outputDir); + // Relative path + if (_outputDir[0] == '.' || _outputDir[0] != '/') + { + // realpath doesn't works if file/dir doesn't exists + if (fileExists(_outputDir)) + outputDir = strdup(realpath(_outputDir, 0)); + else + outputDir = strdup(abspath(_outputDir)); + } + else + outputDir = strdup(_outputDir); } std::string pass; if (fileExists(outputDir)) { - int key; - - while (true) - { - std::cout << "!! Warning !! : " << outputDir << " already exists." << std::endl; - std::cout << "All your data will be overwrite. Would you like to continue ? [y/N] " << std::flush ; - key = getchar(); - if (key == 'n' || key == 'N' || key == '\n' || key == '\r') - goto end; - if (key == 'y' || key == 'Y') - break; - } + int key; + + while (true) + { + std::cout << "!! Warning !! : " << outputDir << " already exists." << std::endl; + std::cout << "All your data will be overwrite. Would you like to continue ? [y/N] " << std::flush ; + key = getchar(); + if (key == 'n' || key == 'N' || key == '\n' || key == '\r') + goto end; + if (key == 'y' || key == 'Y') + break; + } - // Clean STDIN buf - while ((key = getchar()) != '\n') - ; + // Clean STDIN buf + while ((key = getchar()) != '\n') + ; } if (!password) { - char prompt[128]; - std::snprintf(prompt, sizeof(prompt), "Enter password for <%s> : ", username); - pass = getpass((const char*)prompt, false); - password = pass.c_str(); + char prompt[128]; + std::snprintf(prompt, sizeof(prompt), "Enter password for <%s> : ", username); + pass = getpass((const char*)prompt, false); + password = pass.c_str(); } - + ADEPTActivate activate; ret = activate.run(); -end: +end: free((void*)outputDir); return ret; } diff --git a/utils/adept_loan_mgt.cpp b/utils/adept_loan_mgt.cpp index ab52d3f..fd060ab 100644 --- a/utils/adept_loan_mgt.cpp +++ b/utils/adept_loan_mgt.cpp @@ -4,16 +4,16 @@ All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - + * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. + notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. * Neither the name of the copyright holder nor the - names of its contributors may be used to endorse or promote products - derived from this software without specific prior written permission. - + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE @@ -60,297 +60,297 @@ struct Loan std::string operatorURL; std::string validity; std::string bookName; - + std::string path; }; - + class LoanMGT { public: ~LoanMGT() { - for (const auto& kv : loanedBooks) - delete kv.second; + for (const auto& kv : loanedBooks) + delete kv.second; } - + int run() { - int ret = 0; - try - { - DRMProcessorClientImpl client; - gourou::DRMProcessor processor(&client, deviceFile, activationFile, devicekeyFile); + int ret = 0; + try + { + DRMProcessorClientImpl client; + gourou::DRMProcessor processor(&client, deviceFile, activationFile, devicekeyFile); - loadLoanedBooks(); + loadLoanedBooks(); - if (list) - displayLoanList(); - else if (returnID) - returnBook(processor); - else if (deleteID) - deleteLoan(); - } catch(std::exception& e) - { - std::cout << e.what() << std::endl; - ret = 1; - } + if (list) + displayLoanList(); + else if (returnID) + returnBook(processor); + else if (deleteID) + deleteLoan(); + } catch(std::exception& e) + { + std::cout << e.what() << std::endl; + ret = 1; + } - return ret; + return ret; } private: void loadLoanedBooks() { - DIR *dp; - struct dirent *ep; - int entryLen; - struct Loan* loan; - char * res; - - std::string loanDir = std::string(adeptDir) + std::string("/") + LOANS_DIR; + DIR *dp; + struct dirent *ep; + int entryLen; + struct Loan* loan; + char * res; + + std::string loanDir = std::string(adeptDir) + std::string("/") + LOANS_DIR; - if (!fileExists(loanDir.c_str())) - return; - - dp = opendir (loanDir.c_str()); + if (!fileExists(loanDir.c_str())) + return; + + dp = opendir (loanDir.c_str()); - if(!dp) - EXCEPTION(gourou::USER_INVALID_INPUT, "Cannot read directory " << loanDir); + if(!dp) + EXCEPTION(gourou::USER_INVALID_INPUT, "Cannot read directory " << loanDir); - while ((ep = readdir (dp))) - { - if (ep->d_type != DT_LNK && - ep->d_type != DT_REG) - continue; + while ((ep = readdir (dp))) + { + if (ep->d_type != DT_LNK && + ep->d_type != DT_REG) + continue; - entryLen = strlen(ep->d_name); + entryLen = strlen(ep->d_name); - if (entryLen <= 4 || - ep->d_name[entryLen-4] != '.' || - ep->d_name[entryLen-3] != 'x' || - ep->d_name[entryLen-2] != 'm' || - ep->d_name[entryLen-1] != 'l') - continue; + if (entryLen <= 4 || + ep->d_name[entryLen-4] != '.' || + ep->d_name[entryLen-3] != 'x' || + ep->d_name[entryLen-2] != 'm' || + ep->d_name[entryLen-1] != 'l') + continue; - std::string id = std::string(ep->d_name, entryLen-4); - - loan = new Loan; - loan->path = loanDir + std::string("/") + ep->d_name; + std::string id = std::string(ep->d_name, entryLen-4); + + loan = new Loan; + loan->path = loanDir + std::string("/") + ep->d_name; - pugi::xml_document xmlDoc; - pugi::xml_node node; + pugi::xml_document xmlDoc; + pugi::xml_node node; - if (!xmlDoc.load_file(loan->path.c_str(), pugi::parse_ws_pcdata_single|pugi::parse_escapes, pugi::encoding_utf8)) - { - std::cout << "Invalid loan entry " << loan->path << std::endl; - goto error; - } + if (!xmlDoc.load_file(loan->path.c_str(), pugi::parse_ws_pcdata_single|pugi::parse_escapes, pugi::encoding_utf8)) + { + std::cout << "Invalid loan entry " << loan->path << std::endl; + goto error; + } - // id - node = xmlDoc.select_node("//id").node(); - if (!node) - { - std::cout << "Invalid loan entry " << ep->d_name << ", no id element" << std::endl; - goto error; - } - loan->id = node.first_child().value(); + // id + node = xmlDoc.select_node("//id").node(); + if (!node) + { + std::cout << "Invalid loan entry " << ep->d_name << ", no id element" << std::endl; + goto error; + } + loan->id = node.first_child().value(); - // operatorURL - node = xmlDoc.select_node("//operatorURL").node(); - if (!node) - { - std::cout << "Invalid loan entry " << ep->d_name << ", no operatorURL element" << std::endl; - goto error; - } - loan->operatorURL = node.first_child().value(); + // operatorURL + node = xmlDoc.select_node("//operatorURL").node(); + if (!node) + { + std::cout << "Invalid loan entry " << ep->d_name << ", no operatorURL element" << std::endl; + goto error; + } + loan->operatorURL = node.first_child().value(); - // validity - node = xmlDoc.select_node("//validity").node(); - if (!node) - { - std::cout << "Invalid loan entry " << ep->d_name << ", no validity element" << std::endl; - goto error; - } - loan->validity = node.first_child().value(); + // validity + node = xmlDoc.select_node("//validity").node(); + if (!node) + { + std::cout << "Invalid loan entry " << ep->d_name << ", no validity element" << std::endl; + goto error; + } + loan->validity = node.first_child().value(); - // bookName - node = xmlDoc.select_node("//name").node(); - if (!node) - { - std::cout << "Invalid loan entry " << ep->d_name << ", no name element" << std::endl; - goto error; - } - loan->bookName = node.first_child().value(); + // bookName + node = xmlDoc.select_node("//name").node(); + if (!node) + { + std::cout << "Invalid loan entry " << ep->d_name << ", no name element" << std::endl; + goto error; + } + loan->bookName = node.first_child().value(); - struct tm tm; + struct tm tm; #ifdef __ANDROID__ - res = strptime(loan->validity.c_str(), "%Y-%m-%dT%H:%M:%S%z", &tm); + res = strptime(loan->validity.c_str(), "%Y-%m-%dT%H:%M:%S%z", &tm); #else - res = strptime(loan->validity.c_str(), "%Y-%m-%dT%H:%M:%S%Z", &tm); + res = strptime(loan->validity.c_str(), "%Y-%m-%dT%H:%M:%S%Z", &tm); #endif - - if (res != NULL && *res == 0) - { - if (mktime(&tm) <= time(NULL)) - loan->validity = " (Expired)"; - } - else - { - std::cout << "Unable to parse validity timestamp :" << loan->validity << std::endl; - loan->validity = " (Unknown)"; - } - - loanedBooks[id] = loan; - continue; + + if (res != NULL && *res == 0) + { + if (mktime(&tm) <= time(NULL)) + loan->validity = " (Expired)"; + } + else + { + std::cout << "Unable to parse validity timestamp :" << loan->validity << std::endl; + loan->validity = " (Unknown)"; + } + + loanedBooks[id] = loan; + continue; - error: - if (loan) - delete loan; - } + error: + if (loan) + delete loan; + } - closedir (dp); + closedir (dp); } void displayLoanList() { - if (!loanedBooks.size()) - { - std::cout << "No books loaned" << std::endl; - return; - } + if (!loanedBooks.size()) + { + std::cout << "No books loaned" << std::endl; + return; + } - struct Loan* loan; - unsigned int maxSizeBookName=0; - // Compute max size - for (const auto& kv : loanedBooks) - { - loan = kv.second; - if (loan->bookName.size() > maxSizeBookName) - maxSizeBookName = loan->bookName.size(); - } + struct Loan* loan; + unsigned int maxSizeBookName=0; + // Compute max size + for (const auto& kv : loanedBooks) + { + loan = kv.second; + if (loan->bookName.size() > maxSizeBookName) + maxSizeBookName = loan->bookName.size(); + } - /* Manage empty names */ - if (maxSizeBookName == 0) - maxSizeBookName = sizeof("No name ")-1; - else if (maxSizeBookName < 4) - maxSizeBookName = 4; - else if (maxSizeBookName > MAX_SIZE_BOOK_NAME) - maxSizeBookName = MAX_SIZE_BOOK_NAME; - else if ((maxSizeBookName % 2)) - maxSizeBookName++; + /* Manage empty names */ + if (maxSizeBookName == 0) + maxSizeBookName = sizeof("No name ")-1; + else if (maxSizeBookName < 4) + maxSizeBookName = 4; + else if (maxSizeBookName > MAX_SIZE_BOOK_NAME) + maxSizeBookName = MAX_SIZE_BOOK_NAME; + else if ((maxSizeBookName % 2)) + maxSizeBookName++; - // std::cout << " ID Book Expiration" << std::endl; - // std::cout << "------------------------------" << std::endl; + // std::cout << " ID Book Expiration" << std::endl; + // std::cout << "------------------------------" << std::endl; - int fillID, fillBookName, fillExpiration=(20 - 10)/2; - - fillID = (ID_HASH_SIZE - 2) / 2; - fillBookName = (maxSizeBookName - 4) / 2; + int fillID, fillBookName, fillExpiration=(20 - 10)/2; + + fillID = (ID_HASH_SIZE - 2) / 2; + fillBookName = (maxSizeBookName - 4) / 2; - std::cout.width (fillID); - std::cout << ""; - std::cout << "ID" ; - std::cout.width (fillID); - std::cout << ""; - std::cout << " " ; - - std::cout.width (fillBookName); - std::cout << ""; - std::cout << "Book" ; - std::cout.width (fillBookName); - std::cout << ""; - std::cout << " " ; + std::cout.width (fillID); + std::cout << ""; + std::cout << "ID" ; + std::cout.width (fillID); + std::cout << ""; + std::cout << " " ; + + std::cout.width (fillBookName); + std::cout << ""; + std::cout << "Book" ; + std::cout.width (fillBookName); + std::cout << ""; + std::cout << " " ; - std::cout.width (fillExpiration); - std::cout << ""; - std::cout << "Expiration"; - std::cout.width (fillExpiration); - std::cout << "" << std::endl; + std::cout.width (fillExpiration); + std::cout << ""; + std::cout << "Expiration"; + std::cout.width (fillExpiration); + std::cout << "" << std::endl; - std::cout.fill ('-'); - std::cout.width (ID_HASH_SIZE + 4 + maxSizeBookName + 4 + 20); - std::cout << "" << std::endl; - std::cout.fill (' '); + std::cout.fill ('-'); + std::cout.width (ID_HASH_SIZE + 4 + maxSizeBookName + 4 + 20); + std::cout << "" << std::endl; + std::cout.fill (' '); - std::string bookName; + std::string bookName; - for (const auto& kv : loanedBooks) - { - loan = kv.second; + for (const auto& kv : loanedBooks) + { + loan = kv.second; - std::cout << kv.first; - std::cout << " "; + std::cout << kv.first; + std::cout << " "; - if (loan->bookName.size() == 0) - bookName = std::string("No name "); - else if (loan->bookName.size() > MAX_SIZE_BOOK_NAME) - bookName = std::string(loan->bookName.c_str(), MAX_SIZE_BOOK_NAME); - else - bookName = loan->bookName; + if (loan->bookName.size() == 0) + bookName = std::string("No name "); + else if (loan->bookName.size() > MAX_SIZE_BOOK_NAME) + bookName = std::string(loan->bookName.c_str(), MAX_SIZE_BOOK_NAME); + else + bookName = loan->bookName; - std::cout << bookName; - std::cout.width (maxSizeBookName - bookName.size()); - std::cout << ""; - std::cout << " "; - - std::cout << loan->validity << std::endl; - } + std::cout << bookName; + std::cout.width (maxSizeBookName - bookName.size()); + std::cout << ""; + std::cout << " "; + + std::cout << loan->validity << std::endl; + } - std::cout << std::endl; + std::cout << std::endl; } void returnBook(gourou::DRMProcessor& processor) { - struct Loan* loan = loanedBooks[std::string(returnID)]; + struct Loan* loan = loanedBooks[std::string(returnID)]; - if (!loan) - { - std::cout << "Error : Loan " << returnID << " doesn't exists" << std::endl; - return; - } + if (!loan) + { + std::cout << "Error : Loan " << returnID << " doesn't exists" << std::endl; + return; + } - processor.returnLoan(loan->id, loan->operatorURL, notify); - - deleteID = returnID; - if (deleteLoan(false)) - { - std::cout << "Loan " << returnID << " successfully returned" << std::endl; - } - } + processor.returnLoan(loan->id, loan->operatorURL, notify); + deleteID = returnID; + if (deleteLoan(false)) + { + std::cout << "Loan " << returnID << " successfully returned" << std::endl; + } + } + bool deleteLoan(bool displayResult=true) { - struct Loan* loan = loanedBooks[std::string(deleteID)]; + struct Loan* loan = loanedBooks[std::string(deleteID)]; - if (!loan) - { - std::cout << "Error : Loan " << deleteID << " doesn't exists" << std::endl; - return false; - } + if (!loan) + { + std::cout << "Error : Loan " << deleteID << " doesn't exists" << std::endl; + return false; + } - if (unlink(loan->path.c_str())) - { - std::cout << "Error : Cannot delete " << loan->path << std::endl; - return false; - } - else if (displayResult) - { - std::cout << "Loan " << deleteID << " deleted" << std::endl; - } - - return true; + if (unlink(loan->path.c_str())) + { + std::cout << "Error : Cannot delete " << loan->path << std::endl; + return false; + } + else if (displayResult) + { + std::cout << "Loan " << deleteID << " deleted" << std::endl; + } + + return true; } std::map loanedBooks; -}; +}; static void usage(const char* cmd) { std::cout << basename((char*)cmd) << " manage loaned books" << std::endl << std::endl; - + std::cout << "Usage: " << basename((char*)cmd) << " [OPTIONS]" << std::endl << std::endl; - + std::cout << "Global Options:" << std::endl; std::cout << " " << "-l|--list" << "\t\t" << "List all loaned books" << std::endl; std::cout << " " << "-r|--return" << "\t\t" << "Return a loaned book" << std::endl; @@ -381,69 +381,69 @@ int main(int argc, char** argv) const char** files[] = {&devicekeyFile, &deviceFile, &activationFile}; int verbose = gourou::DRMProcessor::getLogLevel(); int actions = 0; - + while (1) { - int option_index = 0; - static struct option long_options[] = { - {"adept-directory", required_argument, 0, 'D' }, - {"list", no_argument, 0, 'l' }, - {"return", no_argument, 0, 'r' }, - {"delete", no_argument, 0, 'd' }, - {"no-notify", no_argument, 0, 'N' }, - {"verbose", no_argument, 0, 'v' }, - {"version", no_argument, 0, 'V' }, - {"help", no_argument, 0, 'h' }, - {0, 0, 0, 0 } - }; + int option_index = 0; + static struct option long_options[] = { + {"adept-directory", required_argument, 0, 'D' }, + {"list", no_argument, 0, 'l' }, + {"return", no_argument, 0, 'r' }, + {"delete", no_argument, 0, 'd' }, + {"no-notify", no_argument, 0, 'N' }, + {"verbose", no_argument, 0, 'v' }, + {"version", no_argument, 0, 'V' }, + {"help", no_argument, 0, 'h' }, + {0, 0, 0, 0 } + }; - c = getopt_long(argc, argv, "d:lr:D:vVh", + c = getopt_long(argc, argv, "d:lr:D:vVh", long_options, &option_index); - if (c == -1) - break; + if (c == -1) + break; - switch (c) { - case 'D': - adeptDir = optarg; - break; - case 'l': - list = true; - actions++; - break; - case 'r': - returnID = optarg; - actions++; - break; - case 'd': - deleteID = optarg; - actions++; - break; - case 'N': - notify = false; - break; - case 'v': - verbose++; - break; - case 'V': - version(); - return 0; - case 'h': - usage(argv[0]); - return 0; - default: - usage(argv[0]); - return -1; - } + switch (c) { + case 'D': + adeptDir = optarg; + break; + case 'l': + list = true; + actions++; + break; + case 'r': + returnID = optarg; + actions++; + break; + case 'd': + deleteID = optarg; + actions++; + break; + case 'N': + notify = false; + break; + case 'v': + verbose++; + break; + case 'V': + version(); + return 0; + case 'h': + usage(argv[0]); + return 0; + default: + usage(argv[0]); + return -1; + } } - + gourou::DRMProcessor::setLogLevel(verbose); // By default, simply list books loaned if (actions == 0) - list = true; + list = true; else if (actions != 1) { - usage(argv[0]); - return -1; + usage(argv[0]); + return -1; } LoanMGT loanMGT; @@ -454,50 +454,50 @@ int main(int argc, char** argv) char *filename; for (i=0; i<(int)ARRAY_SIZE(files); i++) { - orig = *files[i]; - - if (adeptDir) - { - std::string path = std::string(adeptDir) + std::string("/") + orig; - filename = strdup(path.c_str()); - } - else - filename = strdup(orig); - *files[i] = findFile(filename); - free(filename); - if (!*files[i]) - { - std::cout << "Error : " << orig << " doesn't exists, did you activate your device ?" << std::endl; - hasErrors = true; - } + orig = *files[i]; + + if (adeptDir) + { + std::string path = std::string(adeptDir) + std::string("/") + orig; + filename = strdup(path.c_str()); + } + else + filename = strdup(orig); + *files[i] = findFile(filename); + free(filename); + if (!*files[i]) + { + std::cout << "Error : " << orig << " doesn't exists, did you activate your device ?" << std::endl; + hasErrors = true; + } } if (hasErrors) { - // In case of adept dir was provided by user - adeptDir = 0; - goto end; + // In case of adept dir was provided by user + adeptDir = 0; + goto end; } if (adeptDir) - adeptDir = strdup(adeptDir); // For below free + adeptDir = strdup(adeptDir); // For below free else { - adeptDir = strdup(deviceFile); - adeptDir = dirname(adeptDir); + adeptDir = strdup(deviceFile); + adeptDir = dirname(adeptDir); } ret = loanMGT.run(); - + end: for (i=0; i<(int)ARRAY_SIZE(files); i++) { - if (*files[i]) - free((void*)*files[i]); + if (*files[i]) + free((void*)*files[i]); } if (adeptDir) - free(adeptDir); - + free(adeptDir); + return ret; } diff --git a/utils/adept_remove.cpp b/utils/adept_remove.cpp index c8d3ecb..c678f3d 100644 --- a/utils/adept_remove.cpp +++ b/utils/adept_remove.cpp @@ -4,16 +4,16 @@ All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - + * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. + notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. * Neither the name of the copyright holder nor the - names of its contributors may be used to endorse or promote products - derived from this software without specific prior written permission. - + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE @@ -51,13 +51,13 @@ static unsigned encryptionKeySize = 0; static inline unsigned char htoi(unsigned char c) { if (c >= '0' && c <= '9') - c -= '0'; + c -= '0'; else if (c >= 'a' && c <= 'f') - c -= 'a' - 10; + c -= 'a' - 10; else if (c >= 'A' && c <= 'F') - c -= 'A' - 10; + c -= 'A' - 10; else - EXCEPTION(gourou::USER_INVALID_INPUT, "Invalid character " << c << " in encryption key"); + EXCEPTION(gourou::USER_INVALID_INPUT, "Invalid character " << c << " in encryption key"); return c; } @@ -70,82 +70,82 @@ static inline bool endsWith(const std::string& s, const std::string& suffix) class ADEPTRemove { public: - + int run() { - int ret = 0; - try - { - gourou::DRMProcessor::ITEM_TYPE type; - DRMProcessorClientImpl client; - gourou::DRMProcessor processor(&client, deviceFile, activationFile, devicekeyFile); - - std::string filename; - if (!outputFile) - filename = std::string(inputFile); - else - filename = outputFile; - - if (outputDir) - { - if (!fileExists(outputDir)) - mkpath(outputDir); + int ret = 0; + try + { + gourou::DRMProcessor::ITEM_TYPE type; + DRMProcessorClientImpl client; + gourou::DRMProcessor processor(&client, deviceFile, activationFile, devicekeyFile); + + std::string filename; + if (!outputFile) + filename = std::string(inputFile); + else + filename = outputFile; + + if (outputDir) + { + if (!fileExists(outputDir)) + mkpath(outputDir); - filename = std::string(outputDir) + "/" + filename; - } + filename = std::string(outputDir) + "/" + filename; + } - if (endsWith(filename, ".epub")) - type = gourou::DRMProcessor::ITEM_TYPE::EPUB; - else if (endsWith(filename, ".pdf")) - type = gourou::DRMProcessor::ITEM_TYPE::PDF; - else - { - EXCEPTION(gourou::DRM_FORMAT_NOT_SUPPORTED, "Unsupported file format of " << filename); - } - - if (inputFile != filename) - { - unlink(filename.c_str()); - fileCopy(inputFile, filename.c_str()); - processor.removeDRM(inputFile, filename, type, encryptionKey, encryptionKeySize); - std::cout << "DRM removed into new file " << filename << std::endl; - } - else - { - // Use temp file for PDF - if (type == gourou::DRMProcessor::ITEM_TYPE::PDF) - { - std::string tempFile = filename + ".tmp"; - /* Be sure there is not already a temp file */ - unlink(tempFile.c_str()); - processor.removeDRM(filename, tempFile, type, encryptionKey, encryptionKeySize); - /* Original file must be removed before doing a copy... */ - unlink(filename.c_str()); - if (rename(tempFile.c_str(), filename.c_str())) - { - EXCEPTION(gourou::DRM_FILE_ERROR, "Unable to copy " << tempFile << " into " << filename); - } - } - else - processor.removeDRM(inputFile, filename, type, encryptionKey, encryptionKeySize); - std::cout << "DRM removed from " << filename << std::endl; - } - } catch(std::exception& e) - { - std::cout << e.what() << std::endl; - ret = 1; - } + if (endsWith(filename, ".epub")) + type = gourou::DRMProcessor::ITEM_TYPE::EPUB; + else if (endsWith(filename, ".pdf")) + type = gourou::DRMProcessor::ITEM_TYPE::PDF; + else + { + EXCEPTION(gourou::DRM_FORMAT_NOT_SUPPORTED, "Unsupported file format of " << filename); + } + + if (inputFile != filename) + { + unlink(filename.c_str()); + fileCopy(inputFile, filename.c_str()); + processor.removeDRM(inputFile, filename, type, encryptionKey, encryptionKeySize); + std::cout << "DRM removed into new file " << filename << std::endl; + } + else + { + // Use temp file for PDF + if (type == gourou::DRMProcessor::ITEM_TYPE::PDF) + { + std::string tempFile = filename + ".tmp"; + /* Be sure there is not already a temp file */ + unlink(tempFile.c_str()); + processor.removeDRM(filename, tempFile, type, encryptionKey, encryptionKeySize); + /* Original file must be removed before doing a copy... */ + unlink(filename.c_str()); + if (rename(tempFile.c_str(), filename.c_str())) + { + EXCEPTION(gourou::DRM_FILE_ERROR, "Unable to copy " << tempFile << " into " << filename); + } + } + else + processor.removeDRM(inputFile, filename, type, encryptionKey, encryptionKeySize); + std::cout << "DRM removed from " << filename << std::endl; + } + } catch(std::exception& e) + { + std::cout << e.what() << std::endl; + ret = 1; + } - return ret; + return ret; } -}; +}; static void usage(const char* cmd) { std::cout << basename((char*)cmd) << " remove ADEPT DRM (from Adobe) of EPUB/PDF file" << std::endl << std::endl; - + std::cout << "Usage: " << basename((char*)cmd) << " [OPTIONS] file(.epub|pdf)" << std::endl << std::endl; - + std::cout << "Global Options:" << std::endl; std::cout << " " << "-O|--output-dir" << "\t" << "Optional output directory were to put result (default ./)" << std::endl; std::cout << " " << "-o|--output-file" << "\t" << "Optional output filename (default inplace DRM removal>)" << std::endl; @@ -181,82 +181,82 @@ int main(int argc, char** argv) std::string _deviceFile, _activationFile, _devicekeyFile; while (1) { - int option_index = 0; - static struct option long_options[] = { - {"adept-directory", required_argument, 0, 'D' }, - {"device-file", required_argument, 0, 'd' }, - {"activation-file", required_argument, 0, 'a' }, - {"device-key-file", required_argument, 0, 'k' }, - {"output-dir", required_argument, 0, 'O' }, - {"output-file", required_argument, 0, 'o' }, - {"input-file", required_argument, 0, 'f' }, - {"encryption-key", required_argument, 0, 'K' }, // Private option - {"verbose", no_argument, 0, 'v' }, - {"version", no_argument, 0, 'V' }, - {"help", no_argument, 0, 'h' }, - {0, 0, 0, 0 } - }; + int option_index = 0; + static struct option long_options[] = { + {"adept-directory", required_argument, 0, 'D' }, + {"device-file", required_argument, 0, 'd' }, + {"activation-file", required_argument, 0, 'a' }, + {"device-key-file", required_argument, 0, 'k' }, + {"output-dir", required_argument, 0, 'O' }, + {"output-file", required_argument, 0, 'o' }, + {"input-file", required_argument, 0, 'f' }, + {"encryption-key", required_argument, 0, 'K' }, // Private option + {"verbose", no_argument, 0, 'v' }, + {"version", no_argument, 0, 'V' }, + {"help", no_argument, 0, 'h' }, + {0, 0, 0, 0 } + }; - c = getopt_long(argc, argv, "D:d:a:k:O:o:f:K:vVh", + c = getopt_long(argc, argv, "D:d:a:k:O:o:f:K:vVh", long_options, &option_index); - if (c == -1) - break; + if (c == -1) + break; - switch (c) { - case 'D': - _deviceFile = std::string(optarg) + "/device.xml"; - _activationFile = std::string(optarg) + "/activation.xml"; - _devicekeyFile = std::string(optarg) + "/devicesalt"; - deviceFile = _deviceFile.c_str(); - activationFile = _activationFile.c_str(); - devicekeyFile = _devicekeyFile.c_str(); - break; - case 'd': - deviceFile = optarg; - break; - case 'a': - activationFile = optarg; - break; - case 'k': - devicekeyFile = optarg; - break; - case 'f': - inputFile = optarg; - break; - case 'O': - outputDir = optarg; - break; - case 'o': - outputFile = optarg; - break; - case 'K': - encryptionKeyUser = optarg; - break; - case 'v': - verbose++; - break; - case 'V': - version(); - return 0; - case 'h': - usage(argv[0]); - return 0; - default: - usage(argv[0]); - return -1; - } + switch (c) { + case 'D': + _deviceFile = std::string(optarg) + "/device.xml"; + _activationFile = std::string(optarg) + "/activation.xml"; + _devicekeyFile = std::string(optarg) + "/devicesalt"; + deviceFile = _deviceFile.c_str(); + activationFile = _activationFile.c_str(); + devicekeyFile = _devicekeyFile.c_str(); + break; + case 'd': + deviceFile = optarg; + break; + case 'a': + activationFile = optarg; + break; + case 'k': + devicekeyFile = optarg; + break; + case 'f': + inputFile = optarg; + break; + case 'O': + outputDir = optarg; + break; + case 'o': + outputFile = optarg; + break; + case 'K': + encryptionKeyUser = optarg; + break; + case 'v': + verbose++; + break; + case 'V': + version(); + return 0; + case 'h': + usage(argv[0]); + return 0; + default: + usage(argv[0]); + return -1; + } } - + gourou::DRMProcessor::setLogLevel(verbose); if (optind == argc-1) - inputFile = argv[optind]; + inputFile = argv[optind]; if (!inputFile || (outputDir && !outputDir[0]) || - (outputFile && !outputFile[0])) + (outputFile && !outputFile[0])) { - usage(argv[0]); - return -1; + usage(argv[0]); + return -1; } ADEPTRemove remover; @@ -266,56 +266,56 @@ int main(int argc, char** argv) const char* orig; for (i=0; i<(int)ARRAY_SIZE(files); i++) { - orig = *files[i]; - *files[i] = findFile(*files[i]); - if (!*files[i]) - { - std::cout << "Error : " << orig << " doesn't exists, did you activate your device ?" << std::endl; - ret = -1; - hasErrors = true; - } + orig = *files[i]; + *files[i] = findFile(*files[i]); + if (!*files[i]) + { + std::cout << "Error : " << orig << " doesn't exists, did you activate your device ?" << std::endl; + ret = -1; + hasErrors = true; + } } if (encryptionKeyUser) { - int size = std::string(encryptionKeyUser).size(); - if ((size % 2)) - { - std::cout << "Error : Encryption key must be odd length" << std::endl; - goto end; - } + int size = std::string(encryptionKeyUser).size(); + if ((size % 2)) + { + std::cout << "Error : Encryption key must be odd length" << std::endl; + goto end; + } - if (encryptionKeyUser[0] == '0' && encryptionKeyUser[1] == 'x') - { - encryptionKeyUser += 2; - size -= 2; - } + if (encryptionKeyUser[0] == '0' && encryptionKeyUser[1] == 'x') + { + encryptionKeyUser += 2; + size -= 2; + } - encryptionKey = new unsigned char[size/2]; + encryptionKey = new unsigned char[size/2]; - for(i=0; i -#include +#include #include #include #include @@ -62,13 +62,13 @@ DRMProcessorClientImpl::DRMProcessorClientImpl(): legacy = OSSL_PROVIDER_load(NULL, "legacy"); if (!legacy) { - ERR_print_errors_cb(error_cb, NULL); - EXCEPTION(gourou::CLIENT_OSSL_ERROR, "Error, OpenSSL legacy provider not available"); + ERR_print_errors_cb(error_cb, NULL); + EXCEPTION(gourou::CLIENT_OSSL_ERROR, "Error, OpenSSL legacy provider not available"); } deflt = OSSL_PROVIDER_load(NULL, "default"); if (!deflt) - EXCEPTION(gourou::CLIENT_OSSL_ERROR, "Error, OpenSSL default provider not available"); + EXCEPTION(gourou::CLIENT_OSSL_ERROR, "Error, OpenSSL default provider not available"); OSSL_PROVIDER_load(NULL, "base"); #endif @@ -77,13 +77,13 @@ DRMProcessorClientImpl::DRMProcessorClientImpl(): #else strcpy(cookiejar, "/tmp/libgourou_cookie_jar_XXXXXX"); #endif - + int fd = mkstemp(cookiejar); if (fd >= 0) - close(fd); + close(fd); else { - EXCEPTION(gourou::CLIENT_FILE_ERROR, "mkstemp error"); + EXCEPTION(gourou::CLIENT_FILE_ERROR, "mkstemp error"); } } @@ -91,10 +91,10 @@ DRMProcessorClientImpl::~DRMProcessorClientImpl() { #if OPENSSL_VERSION_MAJOR >= 3 if (legacy) - OSSL_PROVIDER_unload(legacy); + OSSL_PROVIDER_unload(legacy); if (deflt) - OSSL_PROVIDER_unload(deflt); + OSSL_PROVIDER_unload(deflt); #endif unlink(cookiejar); @@ -108,8 +108,8 @@ void* DRMProcessorClientImpl::createDigest(const std::string& digestName) if (EVP_DigestInit(md_ctx, md) != 1) { - EVP_MD_CTX_free(md_ctx); - EXCEPTION(gourou::CLIENT_DIGEST_ERROR, ERR_error_string(ERR_get_error(), NULL)); + EVP_MD_CTX_free(md_ctx); + EXCEPTION(gourou::CLIENT_DIGEST_ERROR, ERR_error_string(ERR_get_error(), NULL)); } return md_ctx; @@ -118,7 +118,7 @@ void* DRMProcessorClientImpl::createDigest(const std::string& digestName) void DRMProcessorClientImpl::digestUpdate(void* handler, unsigned char* data, unsigned int length) { if (EVP_DigestUpdate((EVP_MD_CTX *)handler, data, length) != 1) - EXCEPTION(gourou::CLIENT_DIGEST_ERROR, ERR_error_string(ERR_get_error(), NULL)); + EXCEPTION(gourou::CLIENT_DIGEST_ERROR, ERR_error_string(ERR_get_error(), NULL)); } void DRMProcessorClientImpl::digestFinalize(void* handler, unsigned char* digestOut) @@ -127,7 +127,7 @@ void DRMProcessorClientImpl::digestFinalize(void* handler, unsigned char* digest EVP_MD_CTX_free((EVP_MD_CTX *)handler); if (res <= 0) - EXCEPTION(gourou::CLIENT_DIGEST_ERROR, ERR_error_string(ERR_get_error(), NULL)); + EXCEPTION(gourou::CLIENT_DIGEST_ERROR, ERR_error_string(ERR_get_error(), NULL)); } void DRMProcessorClientImpl::digest(const std::string& digestName, unsigned char* data, unsigned int length, unsigned char* digestOut) @@ -149,16 +149,16 @@ void DRMProcessorClientImpl::randBytes(unsigned char* bytesOut, unsigned int len static unsigned downloadedBytes; static int downloadProgress(void *clientp, curl_off_t dltotal, curl_off_t dlnow, - curl_off_t ultotal, curl_off_t ulnow) + curl_off_t ultotal, curl_off_t ulnow) { // For "big" files only if (dltotal >= DISPLAY_THRESHOLD && gourou::logLevel >= gourou::LG_LOG_WARN) { - int percent = 0; - if (dltotal) - percent = (dlnow * 100) / dltotal; + int percent = 0; + if (dltotal) + percent = (dlnow * 100) / dltotal; - std::cout << "\rDownload " << percent << "%" << std::flush; + std::cout << "\rDownload " << percent << "%" << std::flush; } return 0; @@ -167,7 +167,7 @@ static int downloadProgress(void *clientp, curl_off_t dltotal, curl_off_t dlnow, static size_t curlRead(void *data, size_t size, size_t nmemb, void *userp) { gourou::ByteArray* replyData = (gourou::ByteArray*) userp; - + replyData->append((unsigned char*)data, size*nmemb); return size*nmemb; @@ -194,18 +194,18 @@ static size_t curlHeaders(char *buffer, size_t size, size_t nitems, void *userda if (pos != std::string::npos) { - std::string key = std::string(buffer, pos); - std::string value = std::string(&buffer[pos+1], (size*nitems)-(pos+1)); + std::string key = std::string(buffer, pos); + std::string value = std::string(&buffer[pos+1], (size*nitems)-(pos+1)); - key = gourou::trim(key); - value = gourou::trim(value); + key = gourou::trim(key); + value = gourou::trim(value); - (*responseHeaders)[key] = value; - - if (gourou::logLevel >= gourou::LG_LOG_DEBUG) - std::cout << key << " : " << value << std::endl; + (*responseHeaders)[key] = value; + + if (gourou::logLevel >= gourou::LG_LOG_DEBUG) + std::cout << key << " : " << value << std::endl; } - + return size*nitems; } @@ -215,42 +215,42 @@ std::string DRMProcessorClientImpl::sendHTTPRequest(const std::string& URL, cons std::map localHeaders; if (!responseHeaders) - responseHeaders = &localHeaders; - + responseHeaders = &localHeaders; + GOUROU_LOG(INFO, "Send request to " << URL); if (POSTData.size()) { - GOUROU_LOG(DEBUG, "<<< " << std::endl << POSTData); + GOUROU_LOG(DEBUG, "<<< " << std::endl << POSTData); } unsigned prevDownloadedBytes; downloadedBytes = 0; if (fd && resume) { - struct stat _stat; - if (!fstat(fd, &_stat)) - { - GOUROU_LOG(WARN, "Resume download @ " << _stat.st_size << " bytes"); - downloadedBytes = _stat.st_size; - } - else - GOUROU_LOG(WARN, "Want to resume, but fstat failed"); + struct stat _stat; + if (!fstat(fd, &_stat)) + { + GOUROU_LOG(WARN, "Resume download @ " << _stat.st_size << " bytes"); + downloadedBytes = _stat.st_size; + } + else + GOUROU_LOG(WARN, "Want to resume, but fstat failed"); } - + CURL *curl = curl_easy_init(); CURLcode res; curl_easy_setopt(curl, CURLOPT_URL, URL.c_str()); curl_easy_setopt(curl, CURLOPT_USERAGENT, "book2png"); curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1); - + struct curl_slist *list = NULL; list = curl_slist_append(list, "Accept: */*"); std::string _contentType; if (contentType.size()) { - _contentType = "Content-Type: " + contentType; - list = curl_slist_append(list, _contentType.c_str()); + _contentType = "Content-Type: " + contentType; + list = curl_slist_append(list, _contentType.c_str()); } curl_easy_setopt(curl, CURLOPT_HTTPHEADER, list); @@ -258,138 +258,138 @@ std::string DRMProcessorClientImpl::sendHTTPRequest(const std::string& URL, cons if (POSTData.size()) { - curl_easy_setopt(curl, CURLOPT_POST, 1L); - curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, POSTData.size()); - curl_easy_setopt(curl, CURLOPT_POSTFIELDS, POSTData.data()); + curl_easy_setopt(curl, CURLOPT_POST, 1L); + curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, POSTData.size()); + curl_easy_setopt(curl, CURLOPT_POSTFIELDS, POSTData.data()); } if (fd) { - curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, curlReadFd); - curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void*)&fd); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, curlReadFd); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void*)&fd); } else { - curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, curlRead); - curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void*)&replyData); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, curlRead); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void*)&replyData); } - + curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, curlHeaders); curl_easy_setopt(curl, CURLOPT_HEADERDATA, (void*)responseHeaders); - + curl_easy_setopt(curl, CURLOPT_XFERINFOFUNCTION, downloadProgress); curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 0); for (int i=0; i try again without incrementing tries - else if (res == CURLE_RECV_ERROR) - { - if (prevDownloadedBytes != downloadedBytes) - { - GOUROU_LOG(WARN, "\nConnection broken, but data received, try again"); - i--; - } - else - GOUROU_LOG(WARN, "\nConnection broken and no data received, attempt " << (i+1) << "/" << HTTP_REQ_MAX_RETRY); - } - // Other error --> fail - else - break; - - // Wait a little bit (250ms * i) - usleep((250 * 1000) * (i+1)); - } + prevDownloadedBytes = downloadedBytes; + if (downloadedBytes) + curl_easy_setopt(curl, CURLOPT_RESUME_FROM, downloadedBytes); + res = curl_easy_perform(curl); + + // Connexion failed, wait & retry + if (res == CURLE_COULDNT_CONNECT) + { + GOUROU_LOG(WARN, "\nConnection failed, attempt " << (i+1) << "/" << HTTP_REQ_MAX_RETRY); + } + // Transfer failed but some data has been received + // --> try again without incrementing tries + else if (res == CURLE_RECV_ERROR) + { + if (prevDownloadedBytes != downloadedBytes) + { + GOUROU_LOG(WARN, "\nConnection broken, but data received, try again"); + i--; + } + else + GOUROU_LOG(WARN, "\nConnection broken and no data received, attempt " << (i+1) << "/" << HTTP_REQ_MAX_RETRY); + } + // Other error --> fail + else + break; + + // Wait a little bit (250ms * i) + usleep((250 * 1000) * (i+1)); + } + curl_slist_free_all(list); long http_code = 400; curl_easy_getinfo (curl, CURLINFO_RESPONSE_CODE, &http_code); curl_easy_cleanup(curl); - + if (res != CURLE_OK) - EXCEPTION(gourou::CLIENT_NETWORK_ERROR, "Error " << curl_easy_strerror(res)); + EXCEPTION(gourou::CLIENT_NETWORK_ERROR, "Error " << curl_easy_strerror(res)); if (http_code >= 400) - EXCEPTION(gourou::CLIENT_HTTP_ERROR, "HTTP Error code " << http_code); + EXCEPTION(gourou::CLIENT_HTTP_ERROR, "HTTP Error code " << http_code); if ((downloadedBytes >= DISPLAY_THRESHOLD || replyData.size() >= DISPLAY_THRESHOLD) && - gourou::logLevel >= gourou::LG_LOG_WARN) - std::cout << std::endl; + gourou::logLevel >= gourou::LG_LOG_WARN) + std::cout << std::endl; if ((*responseHeaders)["Content-Type"] == "application/vnd.adobe.adept+xml") { - GOUROU_LOG(DEBUG, ">>> " << std::endl << replyData.data()); + GOUROU_LOG(DEBUG, ">>> " << std::endl << replyData.data()); } - + return std::string((char*)replyData.data(), replyData.length()); } void DRMProcessorClientImpl::padWithPKCS1(unsigned char* out, unsigned int outLength, - const unsigned char* in, unsigned int inLength) + const unsigned char* in, unsigned int inLength) { if (outLength < (inLength + 3)) - EXCEPTION(gourou::CLIENT_RSA_ERROR, "Not enough space for PKCS1 padding"); - + EXCEPTION(gourou::CLIENT_RSA_ERROR, "Not enough space for PKCS1 padding"); + /* PKCS1v5 Padding is : - 0x00 0x01 0xff * n 0x00 dataIn + 0x00 0x01 0xff * n 0x00 dataIn */ - + memset(out, 0xFF, outLength - inLength - 1); - + out[0] = 0x0; out[1] = 0x1; out[outLength - inLength - 1] = 0x00; - + memcpy(&out[outLength - inLength], in, inLength); } void DRMProcessorClientImpl::padWithPKCS1Type2(unsigned char* out, unsigned int outLength, - const unsigned char* in, unsigned int inLength) + const unsigned char* in, unsigned int inLength) { if (outLength < (inLength + 3)) - EXCEPTION(gourou::CLIENT_RSA_ERROR, "Not enough space for PKCS1 padding"); - + EXCEPTION(gourou::CLIENT_RSA_ERROR, "Not enough space for PKCS1 padding"); + /* PKCS1v5 type 2 Padding is : - 0x00 0x02 0xXX * n 0x00 dataIn - XX is random non zero data + 0x00 0x02 0xXX * n 0x00 dataIn + XX is random non zero data */ RAND_bytes(&out[2], outLength - inLength - 1); for(unsigned int i=2; i* responseHeaders=0, int fd=0, bool resume=false); 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); - + const RSA_KEY_TYPE keyType, const std::string& password, + const unsigned char* data, unsigned dataLength, + unsigned char* res); + virtual void RSAPrivateDecrypt(const unsigned char* RSAKey, unsigned int RSAKeyLength, - const RSA_KEY_TYPE keyType, const std::string& password, - const unsigned char* data, unsigned dataLength, - unsigned char* res); + const RSA_KEY_TYPE keyType, const std::string& password, + const unsigned char* data, unsigned dataLength, + unsigned char* res); virtual void RSAPublicEncrypt(const unsigned char* RSAKey, unsigned int RSAKeyLength, - const RSA_KEY_TYPE keyType, - const unsigned char* data, unsigned dataLength, - unsigned char* res); + const RSA_KEY_TYPE keyType, + const unsigned char* data, unsigned dataLength, + unsigned char* res); virtual void* generateRSAKey(int keyLengthBits); virtual void destroyRSAHandler(void* handler); - + virtual void extractRSAPublicKey(void* RSAKeyHandler, unsigned char** keyOut, unsigned int* keyOutLength); virtual void extractRSAPrivateKey(void* RSAKeyHandler, unsigned char** keyOut, unsigned int* keyOutLength); virtual void extractCertificate(const unsigned char* RSAKey, unsigned int RSAKeyLength, - const RSA_KEY_TYPE keyType, const std::string& password, - unsigned char** certOut, unsigned int* certOutLength); - + const RSA_KEY_TYPE keyType, const std::string& password, + unsigned char** certOut, unsigned int* certOutLength); + /* Crypto interface */ virtual void encrypt(CRYPTO_ALGO algo, CHAINING_MODE chaining, - const unsigned char* key, unsigned int keyLength, - const unsigned char* iv, unsigned int ivLength, - const unsigned char* dataIn, unsigned int dataInLength, - unsigned char* dataOut, unsigned int* dataOutLength); + const unsigned char* key, unsigned int keyLength, + const unsigned char* iv, unsigned int ivLength, + const unsigned char* dataIn, unsigned int dataInLength, + unsigned char* dataOut, unsigned int* dataOutLength); virtual void* encryptInit(CRYPTO_ALGO algo, CHAINING_MODE chaining, - const unsigned char* key, unsigned int keyLength, - const unsigned char* iv=0, unsigned int ivLength=0); + const unsigned char* key, unsigned int keyLength, + const unsigned char* iv=0, unsigned int ivLength=0); virtual void encryptUpdate(void* handler, const unsigned char* dataIn, unsigned int dataInLength, - unsigned char* dataOut, unsigned int* dataOutLength); + unsigned char* dataOut, unsigned int* dataOutLength); virtual void encryptFinalize(void* handler, unsigned char* dataOut, unsigned int* dataOutLength); virtual void decrypt(CRYPTO_ALGO algo, CHAINING_MODE chaining, - 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); + const unsigned char* key, unsigned int keyLength, + const unsigned char* iv, unsigned int ivLength, + const unsigned char* dataIn, unsigned int dataInLength, + unsigned char* dataOut, unsigned int* dataOutLength); virtual void* decryptInit(CRYPTO_ALGO algo, CHAINING_MODE chaining, - const unsigned char* key, unsigned int keyLength, - const unsigned char* iv=0, unsigned int ivLength=0); + const unsigned char* key, unsigned int keyLength, + const unsigned char* iv=0, unsigned int ivLength=0); virtual void decryptUpdate(void* handler, const unsigned char* dataIn, unsigned int dataInLength, - unsigned char* dataOut, unsigned int* dataOutLength); + unsigned char* dataOut, unsigned int* dataOutLength); virtual void decryptFinalize(void* handler, unsigned char* dataOut, unsigned int* dataOutLength); /* ZIP Interface */ virtual void* zipOpen(const std::string& path); - + virtual void zipReadFile(void* handler, const std::string& path, gourou::ByteArray& result, bool decompress=true); - + virtual void zipWriteFile(void* handler, const std::string& path, gourou::ByteArray& content); - + virtual void zipDeleteFile(void* handler, const std::string& path); - + virtual void zipClose(void* handler); - + virtual void inflate(gourou::ByteArray& data, gourou::ByteArray& result, - int wbits=-15); - + int wbits=-15); + virtual void deflate(gourou::ByteArray& data, gourou::ByteArray& result, - int wbits=-15, int compressionLevel=8); + int wbits=-15, int compressionLevel=8); private: void padWithPKCS1(unsigned char* out, unsigned int outLength, - const unsigned char* in, unsigned int inLength); + const unsigned char* in, unsigned int inLength); void padWithPKCS1Type2(unsigned char* out, unsigned int outLength, - const unsigned char* in, unsigned int inLength); - + const unsigned char* in, unsigned int inLength); + #if OPENSSL_VERSION_MAJOR >= 3 OSSL_PROVIDER *legacy, *deflt; #else diff --git a/utils/launcher.cpp b/utils/launcher.cpp index 99f8495..6eb2bcf 100644 --- a/utils/launcher.cpp +++ b/utils/launcher.cpp @@ -24,17 +24,17 @@ int main(int argc, char** argv) fullPath = std::string(mountPoint) + util; if (std::string(util) == "launcher" || !fileExists(fullPath.c_str())) - fullPath = std::string(mountPoint) + DEFAULT_UTIL; + fullPath = std::string(mountPoint) + DEFAULT_UTIL; free(argv0); - + argv[0] = strdup(fullPath.c_str()); if (execvp(argv[0], argv)) - std::cout << "Unable to launch '" << argv[0] << "'" << std::endl; + std::cout << "Unable to launch '" << argv[0] << "'" << std::endl; /* Should not happens */ free(argv[0]); - + return 0; } diff --git a/utils/utils_common.cpp b/utils/utils_common.cpp index a686b40..383af5f 100644 --- a/utils/utils_common.cpp +++ b/utils/utils_common.cpp @@ -4,16 +4,16 @@ All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - + * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. + notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. * Neither the name of the copyright holder nor the - names of its contributors may be used to endorse or promote products - derived from this software without specific prior written permission. - + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE @@ -62,31 +62,31 @@ bool fileExists(const char* filename) const char* findFile(const char* filename, bool inDefaultDirs) { std::string path; - + const char* adeptDir = getenv("ADEPT_DIR"); if (adeptDir && adeptDir[0]) { - path = adeptDir + std::string("/") + filename; - if (fileExists(path.c_str())) - return strdup(path.c_str()); + path = adeptDir + std::string("/") + filename; + if (fileExists(path.c_str())) + return strdup(path.c_str()); } path = gourou::DRMProcessor::getDefaultAdeptDir() + filename; if (fileExists(path.c_str())) - return strdup(path.c_str()); + return strdup(path.c_str()); if (fileExists(filename)) - return strdup(filename); + return strdup(filename); if (!inDefaultDirs) return 0; - + for (int i=0; i<(int)ARRAY_SIZE(defaultDirs); i++) { - path = std::string(defaultDirs[i]) + filename; - if (fileExists(path.c_str())) - return strdup(path.c_str()); + path = std::string(defaultDirs[i]) + filename; + if (fileExists(path.c_str())) + return strdup(path.c_str()); } - + return 0; } @@ -118,35 +118,35 @@ void fileCopy(const char* in, const char* out) fdIn = open(in, O_RDONLY); if (!fdIn) - EXCEPTION(gourou::CLIENT_FILE_ERROR, "Unable to open " << in); - + EXCEPTION(gourou::CLIENT_FILE_ERROR, "Unable to open " << in); + fdOut = gourou::createNewFile(out); - + if (!fdOut) { - close (fdIn); - EXCEPTION(gourou::CLIENT_FILE_ERROR, "Unable to open " << out); + close (fdIn); + EXCEPTION(gourou::CLIENT_FILE_ERROR, "Unable to open " << out); } while (true) { - ret = ::read(fdIn, buffer, sizeof(buffer)); - if (ret <= 0) - break; - do - { - _buffer = buffer; - ret2 = ::write(fdOut, _buffer, ret); - if (ret2 >= 0) - { - ret -= ret2; - _buffer += ret2; - } - else - { - EXCEPTION(gourou::CLIENT_FILE_ERROR, "Error writing " << out); - } - } while (ret); + ret = ::read(fdIn, buffer, sizeof(buffer)); + if (ret <= 0) + break; + do + { + _buffer = buffer; + ret2 = ::write(fdOut, _buffer, ret); + if (ret2 >= 0) + { + ret -= ret2; + _buffer += ret2; + } + else + { + EXCEPTION(gourou::CLIENT_FILE_ERROR, "Error writing " << out); + } + } while (ret); } close (fdIn); diff --git a/utils/utils_common.h b/utils/utils_common.h index 5bad2a9..9289209 100644 --- a/utils/utils_common.h +++ b/utils/utils_common.h @@ -4,16 +4,16 @@ All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - + * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. + notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. * Neither the name of the copyright holder nor the - names of its contributors may be used to endorse or promote products - derived from this software without specific prior written permission. - + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE From 1605656c734dded67b4228f033519e7ba9b80268 Mon Sep 17 00:00:00 2001 From: Jake Waksbaum Date: Mon, 19 Jan 2026 14:41:32 -0500 Subject: [PATCH 87/94] Exception: fix constructor --- include/libgourou_common.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/include/libgourou_common.h b/include/libgourou_common.h index 6c57a4e..7c3d46b 100644 --- a/include/libgourou_common.h +++ b/include/libgourou_common.h @@ -165,8 +165,8 @@ namespace gourou Exception(const Exception& other) { this->code = other.code; - this->line = line; - this->file = file; + this->line = other.line; + this->file = other.file; this->fullmessage = strdup(other.fullmessage); } From 772abdd2f9c170d4c87826c2f6ea70f924521d15 Mon Sep 17 00:00:00 2001 From: Jake Waksbaum Date: Mon, 19 Jan 2026 14:41:32 -0500 Subject: [PATCH 88/94] Remove unused message field --- include/libgourou_common.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/libgourou_common.h b/include/libgourou_common.h index 7c3d46b..db03653 100644 --- a/include/libgourou_common.h +++ b/include/libgourou_common.h @@ -181,7 +181,7 @@ namespace gourou private: int code, line; - const char* message, *file; + const char* file; char* fullmessage; }; From a7cfd3ef89d8a869ffdc526a1e6fc53d0c3f5f29 Mon Sep 17 00:00:00 2001 From: Jake Waksbaum Date: Mon, 19 Jan 2026 14:52:38 -0500 Subject: [PATCH 89/94] Replace sprintf with snprintf --- src/bytearray.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bytearray.cpp b/src/bytearray.cpp index fb8fd54..f84230a 100644 --- a/src/bytearray.cpp +++ b/src/bytearray.cpp @@ -202,7 +202,7 @@ namespace gourou char* tmp = new char[_length*2+1]; for(int i=0; i<(int)_length; i++) - sprintf(&tmp[i*2], "%02x", _data[i]); + snprintf(&tmp[i*2], (_length-i)*2+1, "%02x", _data[i]); tmp[_length*2] = 0; From 2387dff2cb94ba57db1913784464eeaac99de751 Mon Sep 17 00:00:00 2001 From: Jake Waksbaum Date: Mon, 19 Jan 2026 15:58:08 -0500 Subject: [PATCH 90/94] Add darwin support to Makefile --- Makefile | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 31e9b6b..d39caa9 100644 --- a/Makefile +++ b/Makefile @@ -12,6 +12,8 @@ LDFLAGS = -lpugixml VERSION := $(shell cat include/libgourou.h |grep LIBGOUROU_VERSION|cut -d '"' -f2) +UNAME := $(shell uname -s) + BUILD_STATIC ?= 0 BUILD_SHARED ?= 1 BUILD_UTILS ?= 1 @@ -27,8 +29,13 @@ ifneq ($(BUILD_STATIC), 0) STATIC_UTILS=1 endif ifneq ($(BUILD_SHARED), 0) - TARGETS += libgourou.so - TARGET_LIBRARIES += libgourou.so libgourou.so.$(VERSION) + ifeq ($(UNAME), Darwin) + TARGETS += libgourou.dylib + TARGET_LIBRARIES += libgourou.dylib libgourou.dylib.$(VERSION) + else + TARGETS += libgourou.so + TARGET_LIBRARIES += libgourou.so libgourou.so.$(VERSION) + endif endif ifneq ($(BUILD_UTILS), 0) TARGETS += build_utils @@ -82,6 +89,12 @@ libgourou.so.$(VERSION): $(OBJECTS) $(UPDFPARSERLIB) libgourou.so: libgourou.so.$(VERSION) ln -f -s $^ $@ +libgourou.dylib.$(VERSION): $(OBJECTS) $(UPDFPARSERLIB) + $(CXX) $^ $(LDFLAGS) -o $@ -shared + +libgourou.dylib: libgourou.dylib.$(VERSION) + ln -f -s $^ $@ + build_utils: $(TARGET_LIBRARIES) $(MAKE) -C utils ROOT=$(PWD) CXX=$(CXX) AR=$(AR) DEBUG=$(DEBUG) STATIC_UTILS=$(STATIC_UTILS) DESTDIR=$(DESTDIR) PREFIX=$(PREFIX) From 76cab1866712105bf03f47cebdf568c0cc674a09 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9gory=20Soutad=C3=A9?= Date: Sat, 31 Jan 2026 20:31:44 +0100 Subject: [PATCH 91/94] Delete all referenced objects deleted by libgourou during PDF DRM removal --- src/libgourou.cpp | 28 ++++++++++++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/src/libgourou.cpp b/src/libgourou.cpp index d9b15ce..6607afd 100644 --- a/src/libgourou.cpp +++ b/src/libgourou.cpp @@ -1342,7 +1342,7 @@ namespace gourou uPDFParser::Integer* ebxVersion; std::vector objects = parser.objects(); - std::vector::iterator it; + std::vector::iterator it, ebxIt; std::vector::reverse_iterator rIt; std::vector ebxObjects; unsigned char decryptedKey[16]; @@ -1513,10 +1513,34 @@ namespace gourou } } + /* Delete objects that reference EBX objects, except in trailer */ + for(it = objects.begin(); it != objects.end(); it++) + { + uPDFParser::Object* object = *it; + + if (object->hasKey("Encrypt") && (*object)["Encrypt"]->type() == uPDFParser::DataType::REFERENCE) + { + uPDFParser::Reference* encrypt = (uPDFParser::Reference*)(*object)["Encrypt"]; + + /* Delete EBX objects */ + for(ebxIt = ebxObjects.begin(); ebxIt != ebxObjects.end(); ebxIt++) + { + if (encrypt->value() == (*ebxIt)->objectId()) + { + GOUROU_LOG(ERROR, "Delete stream id " << object->objectId()); + + parser.removeObject(object); + break; + } + } + } + } + + /* Delete EBX objects */ for(it = ebxObjects.begin(); it != ebxObjects.end(); it++) parser.removeObject(*it); - uPDFParser::Object& trailer = parser.getTrailer(); + uPDFParser::Object& trailer = parser.getTrailer(); trailer.deleteKey("Encrypt"); parser.write(filenameOut); From b44b9889664ca5a9aaa7c5da9a726eaa02714108 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9gory=20Soutad=C3=A9?= Date: Sun, 1 Feb 2026 08:56:27 +0100 Subject: [PATCH 92/94] Fix bug in utils with -o and -O options --- src/libgourou.cpp | 2 +- utils/acsmdownloader.cpp | 46 +++++++++++++++++++++------------------- utils/adept_activate.cpp | 4 ++-- utils/adept_loan_mgt.cpp | 2 +- utils/adept_remove.cpp | 29 ++++++++++++++----------- utils/launcher.cpp | 2 +- utils/utils_common.cpp | 22 +++++++++++++------ utils/utils_common.h | 9 ++++++-- 8 files changed, 69 insertions(+), 47 deletions(-) diff --git a/src/libgourou.cpp b/src/libgourou.cpp index 6607afd..e34b676 100644 --- a/src/libgourou.cpp +++ b/src/libgourou.cpp @@ -525,7 +525,7 @@ namespace gourou time_t expirationTime = parseDateTime(expiration.c_str(), "%Y-%m-%dT%H:%M:%S"); if (time(NULL) > expirationTime) - GOUROU_LOG(WARN, "WARNING: ACSM file expired (" << expiration << "). It may not work"); + GOUROU_LOG(WARN, "WARNING: ACSM file expired (" << expiration << "), It may not work."); } // Build req file diff --git a/utils/acsmdownloader.cpp b/utils/acsmdownloader.cpp index 554a1a9..cc9a5d9 100644 --- a/utils/acsmdownloader.cpp +++ b/utils/acsmdownloader.cpp @@ -64,19 +64,18 @@ public: if (exportPrivateKey) { std::string filename; - if (!outputFile) - filename = std::string("Adobe_PrivateLicenseKey--") + user->getUsername() + ".der"; - else + if (outputFile) filename = outputFile; - - if (outputDir) + else { - if (!fileExists(outputDir)) - mkpath(outputDir); - - filename = std::string(outputDir) + "/" + filename; + filename = std::string("Adobe_PrivateLicenseKey--") + user->getUsername() + ".der"; + + if (outputDir) + filename = std::string(outputDir) + "/" + filename; } + createPath(filename.c_str()); + processor.exportPrivateLicenseKey(filename); std::cout << "Private license key exported to " << filename << std::endl; @@ -86,7 +85,9 @@ public: gourou::FulfillmentItem* item = processor.fulfill(acsmFile, notify); std::string filename; - if (!outputFile) + if (outputFile) + filename = outputFile; + else { filename = item->getMetadata("title"); if (filename == "") @@ -96,18 +97,13 @@ public: // Remove invalid characters std::replace(filename.begin(), filename.end(), '/', '_'); } - } - else - filename = outputFile; - - if (outputDir) - { - if (!fileExists(outputDir)) - mkpath(outputDir); - filename = std::string(outputDir) + "/" + filename; + if (outputDir) + filename = std::string(outputDir) + "/" + filename; } + createPath(filename.c_str()); + gourou::DRMProcessor::ITEM_TYPE type = processor.download(item, filename, resume); if (!outputFile) @@ -186,8 +182,8 @@ static void usage(const char* cmd) std::cout << basename((char*)cmd) << " download EPUB file from ACSM request file" << std::endl << std::endl; std::cout << "Usage: " << basename((char*)cmd) << " [OPTIONS] file.acsm" << std::endl << std::endl; std::cout << "Global Options:" << std::endl; - std::cout << " " << "-O|--output-dir" << "\t" << "Optional output directory were to put result (default ./)" << std::endl; - std::cout << " " << "-o|--output-file" << "\t" << "Optional output filename (default )" << std::endl; + std::cout << " " << "-O|--output-dir" << "\t" << "Optional output directory were to put result (default ./) (not compatible with -o)" << std::endl; + std::cout << " " << "-o|--output-file" << "\t" << "Optional output filename (default ) (not compatible with -O)" << std::endl; std::cout << " " << "-f|--acsm-file" << "\t" << "Backward compatibility: ACSM request file for epub download" << std::endl; std::cout << " " << "-e|--export-private-key"<< "\t" << "Export private key in DER format" << std::endl; std::cout << " " << "-r|--resume" << "\t\t" << "Try to resume download (in case of previous failure)" << std::endl; @@ -309,6 +305,12 @@ int main(int argc, char** argv) return -1; } + if (outputDir && outputFile) + { + std::cout << "Error : you cannot use both -o and -O" << std::endl; + return -1; + } + ACSMDownloader downloader; int i; @@ -339,7 +341,7 @@ int main(int argc, char** argv) } else { - if (!fileExists(acsmFile)) + if (!pathExists(acsmFile)) { std::cout << "Error : " << acsmFile << " doesn't exists" << std::endl; ret = -1; diff --git a/utils/adept_activate.cpp b/utils/adept_activate.cpp index b224de5..b12b617 100644 --- a/utils/adept_activate.cpp +++ b/utils/adept_activate.cpp @@ -240,7 +240,7 @@ int main(int argc, char** argv) if (_outputDir[0] == '.' || _outputDir[0] != '/') { // realpath doesn't works if file/dir doesn't exists - if (fileExists(_outputDir)) + if (pathExists(_outputDir)) outputDir = strdup(realpath(_outputDir, 0)); else outputDir = strdup(abspath(_outputDir)); @@ -250,7 +250,7 @@ int main(int argc, char** argv) } std::string pass; - if (fileExists(outputDir)) + if (pathExists(outputDir)) { int key; diff --git a/utils/adept_loan_mgt.cpp b/utils/adept_loan_mgt.cpp index fd060ab..ab46d98 100644 --- a/utils/adept_loan_mgt.cpp +++ b/utils/adept_loan_mgt.cpp @@ -109,7 +109,7 @@ private: std::string loanDir = std::string(adeptDir) + std::string("/") + LOANS_DIR; - if (!fileExists(loanDir.c_str())) + if (!pathExists(loanDir.c_str())) return; dp = opendir (loanDir.c_str()); diff --git a/utils/adept_remove.cpp b/utils/adept_remove.cpp index c678f3d..47112ab 100644 --- a/utils/adept_remove.cpp +++ b/utils/adept_remove.cpp @@ -81,19 +81,16 @@ public: gourou::DRMProcessor processor(&client, deviceFile, activationFile, devicekeyFile); std::string filename; - if (!outputFile) - filename = std::string(inputFile); - else + if (outputFile) filename = outputFile; - - if (outputDir) + else { - if (!fileExists(outputDir)) - mkpath(outputDir); + filename = std::string(inputFile); - filename = std::string(outputDir) + "/" + filename; + if (outputDir) + filename = std::string(outputDir) + "/" + filename; } - + if (endsWith(filename, ".epub")) type = gourou::DRMProcessor::ITEM_TYPE::EPUB; else if (endsWith(filename, ".pdf")) @@ -102,7 +99,9 @@ public: { EXCEPTION(gourou::DRM_FORMAT_NOT_SUPPORTED, "Unsupported file format of " << filename); } - + + createPath(filename.c_str()); + if (inputFile != filename) { unlink(filename.c_str()); @@ -147,8 +146,8 @@ static void usage(const char* cmd) std::cout << "Usage: " << basename((char*)cmd) << " [OPTIONS] file(.epub|pdf)" << std::endl << std::endl; std::cout << "Global Options:" << std::endl; - std::cout << " " << "-O|--output-dir" << "\t" << "Optional output directory were to put result (default ./)" << std::endl; - std::cout << " " << "-o|--output-file" << "\t" << "Optional output filename (default inplace DRM removal>)" << std::endl; + std::cout << " " << "-O|--output-dir" << "\t" << "Optional output directory were to put result (default ./) (not compatible with -o)" << std::endl; + std::cout << " " << "-o|--output-file" << "\t" << "Optional output filename (default inplace DRM removal>) (not compatible with -O)" << std::endl; std::cout << " " << "-f|--input-file" << "\t" << "Backward compatibility: EPUB/PDF file to process" << std::endl; std::cout << " " << "-v|--verbose" << "\t\t" << "Increase verbosity, can be set multiple times" << std::endl; std::cout << " " << "-V|--version" << "\t\t" << "Display libgourou version" << std::endl; @@ -259,6 +258,12 @@ int main(int argc, char** argv) return -1; } + if (outputDir && outputFile) + { + std::cout << "Error : you cannot use both -o and -O" << std::endl; + return -1; + } + ADEPTRemove remover; int i; diff --git a/utils/launcher.cpp b/utils/launcher.cpp index 6eb2bcf..6b29f78 100644 --- a/utils/launcher.cpp +++ b/utils/launcher.cpp @@ -23,7 +23,7 @@ int main(int argc, char** argv) fullPath = std::string(mountPoint) + util; - if (std::string(util) == "launcher" || !fileExists(fullPath.c_str())) + if (std::string(util) == "launcher" || !pathExists(fullPath.c_str())) fullPath = std::string(mountPoint) + DEFAULT_UTIL; free(argv0); diff --git a/utils/utils_common.cpp b/utils/utils_common.cpp index 383af5f..61fce6d 100644 --- a/utils/utils_common.cpp +++ b/utils/utils_common.cpp @@ -33,6 +33,7 @@ #include #include #include +#include #include @@ -51,10 +52,10 @@ void version(void) std::cout << "Current libgourou version : " << gourou::DRMProcessor::VERSION << std::endl ; } -bool fileExists(const char* filename) +bool pathExists(const char* path) { struct stat _stat; - int ret = stat(filename, &_stat); + int ret = stat(path, &_stat); return (ret == 0); } @@ -67,15 +68,15 @@ const char* findFile(const char* filename, bool inDefaultDirs) if (adeptDir && adeptDir[0]) { path = adeptDir + std::string("/") + filename; - if (fileExists(path.c_str())) + if (pathExists(path.c_str())) return strdup(path.c_str()); } path = gourou::DRMProcessor::getDefaultAdeptDir() + filename; - if (fileExists(path.c_str())) + if (pathExists(path.c_str())) return strdup(path.c_str()); - if (fileExists(filename)) + if (pathExists(filename)) return strdup(filename); if (!inDefaultDirs) return 0; @@ -83,7 +84,7 @@ const char* findFile(const char* filename, bool inDefaultDirs) for (int i=0; i<(int)ARRAY_SIZE(defaultDirs); i++) { path = std::string(defaultDirs[i]) + filename; - if (fileExists(path.c_str())) + if (pathExists(path.c_str())) return strdup(path.c_str()); } @@ -152,3 +153,12 @@ void fileCopy(const char* in, const char* out) close (fdIn); close (fdOut); } + +void createPath(const char* filename) +{ + char* basepath = strdup(filename); + char* outputDir = dirname(basepath); + if (outputDir && !pathExists(outputDir)) + mkpath(outputDir); + free(basepath); +} diff --git a/utils/utils_common.h b/utils/utils_common.h index 9289209..519b8aa 100644 --- a/utils/utils_common.h +++ b/utils/utils_common.h @@ -50,9 +50,9 @@ void version(void); const char* findFile(const char* filename, bool inDefaultDirs=true); /** - * @brief Does the file (or directory exists) + * @brief Does the file (or directory) exists */ -bool fileExists(const char* filename); +bool pathExists(const char* path); /** * @brief Recursively created dir @@ -64,4 +64,9 @@ void mkpath(const char *dir); */ void fileCopy(const char* in, const char* out); +/** + * @brief Create intermediate directories if it does not exists + */ +void createPath(const char* filename); + #endif From 72cb22ad2af0e8636858f72e826f80a3fb1808f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9gory=20Soutad=C3=A9?= Date: Tue, 17 Feb 2026 20:01:58 +0100 Subject: [PATCH 93/94] Refresh curl download percent only when updated --- utils/drmprocessorclientimpl.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/utils/drmprocessorclientimpl.cpp b/utils/drmprocessorclientimpl.cpp index 02ef555..2512fb8 100644 --- a/utils/drmprocessorclientimpl.cpp +++ b/utils/drmprocessorclientimpl.cpp @@ -147,6 +147,7 @@ void DRMProcessorClientImpl::randBytes(unsigned char* bytesOut, unsigned int len #define HTTP_REQ_MAX_RETRY 5 #define DISPLAY_THRESHOLD 10*1024 // Threshold to display download progression static unsigned downloadedBytes; +static int lastPercent = -1; static int downloadProgress(void *clientp, curl_off_t dltotal, curl_off_t dlnow, curl_off_t ultotal, curl_off_t ulnow) @@ -158,7 +159,11 @@ static int downloadProgress(void *clientp, curl_off_t dltotal, curl_off_t dlnow, if (dltotal) percent = (dlnow * 100) / dltotal; - std::cout << "\rDownload " << percent << "%" << std::flush; + if (lastPercent != percent) + { + std::cout << "\rDownload " << percent << "%" << std::flush; + lastPercent = percent; + } } return 0; @@ -279,6 +284,7 @@ std::string DRMProcessorClientImpl::sendHTTPRequest(const std::string& URL, cons curl_easy_setopt(curl, CURLOPT_XFERINFOFUNCTION, downloadProgress); curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 0); + lastPercent = -1; for (int i=0; i Date: Sun, 8 Mar 2026 14:09:41 -0700 Subject: [PATCH 94/94] Use -fmacro-prefix-map for utils The __FILE__ macro is used in the EXCEPTION macro to report the file name alongside error messages. This macro reports the file name exactly as it is passed to the compiler. For most source files in libgourou this is a nice relative path such as "src/libgourou.cpp". However, for EXCEPTION instances in libgourou_common.h, the compiler flag is "-I$(ROOT)/include", where $(ROOT) is an absolute path passed from the higher Makefile. This results in an absolute path to the build directory being hardcoded into the utils shared library and binaries, and reported in error messages. Besides being less readable than the more common relative paths, this triggers warnings from packaging tools that detect inadvertent references to temporary build directories that end up in compiled binaries and might indicate a bug. There is a GCC feature -fmacro-prefix-map which allows to perform substition on the values that are used for __FILE__. Use that option to strip out the absolute path component, without changing any functionality. The feature was added to GCC on 2018-01-18 and released in GCC 8.1.0. https://github.com/gcc-mirror/gcc/commit/7365279fca30371b07e49bfa83a23ddc44cc3860 --- utils/Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utils/Makefile b/utils/Makefile index ba85377..2b4bfda 100644 --- a/utils/Makefile +++ b/utils/Makefile @@ -6,7 +6,7 @@ TARGETS=$(TARGET_BINARIES) launcher MAN_PAGES=acsmdownloader adept_activate adept_remove adept_loan_mgt -CXXFLAGS=-Wall -fPIC -I$(ROOT)/include -fdata-sections -ffunction-sections +CXXFLAGS=-Wall -fPIC -I$(ROOT)/include -fmacro-prefix-map=$(ROOT)/= -fdata-sections -ffunction-sections STATIC_DEP= # LDFLAGS += -Wl,--gc-sections