2021-07-03 21:57:53 +02:00
|
|
|
/*
|
|
|
|
Copyright 2021 Grégory Soutadé
|
|
|
|
|
|
|
|
This file is part of libgourou.
|
|
|
|
|
|
|
|
libgourou is free software: you can redistribute it and/or modify
|
|
|
|
it under the terms of the GNU Lesser General Public License as published by
|
|
|
|
the Free Software Foundation, either version 3 of the License, or
|
|
|
|
(at your option) any later version.
|
|
|
|
|
|
|
|
libgourou is distributed in the hope that it will be useful,
|
|
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
GNU Lesser General Public License for more details.
|
|
|
|
|
|
|
|
You should have received a copy of the GNU Lesser General Public License
|
2021-08-21 20:37:07 +02:00
|
|
|
along with libgourou. If not, see <http://www.gnu.org/licenses/>.
|
2021-07-03 21:57:53 +02:00
|
|
|
*/
|
|
|
|
|
|
|
|
#ifndef _LIBGOUROU_COMMON_H_
|
|
|
|
#define _LIBGOUROU_COMMON_H_
|
|
|
|
|
|
|
|
#include <sys/types.h>
|
|
|
|
#include <sys/stat.h>
|
|
|
|
#include <fcntl.h>
|
|
|
|
#include <unistd.h>
|
|
|
|
|
|
|
|
#include <pugixml.hpp>
|
|
|
|
|
|
|
|
#include <exception>
|
|
|
|
#include <iostream>
|
|
|
|
#include <sstream>
|
|
|
|
#include <iomanip>
|
|
|
|
#include <algorithm>
|
|
|
|
|
|
|
|
#include <string.h>
|
|
|
|
|
|
|
|
#include <libgourou_log.h>
|
|
|
|
#include "bytearray.h"
|
|
|
|
|
|
|
|
namespace gourou
|
|
|
|
{
|
|
|
|
/**
|
|
|
|
* Some common utilities
|
|
|
|
*/
|
|
|
|
|
|
|
|
#define ADOBE_ADEPT_NS "http://ns.adobe.com/adept"
|
|
|
|
|
|
|
|
static const int SHA1_LEN = 20;
|
|
|
|
static const int RSA_KEY_SIZE = 128;
|
|
|
|
static const int RSA_KEY_SIZE_BITS = (RSA_KEY_SIZE*8);
|
|
|
|
|
|
|
|
enum GOUROU_ERROR {
|
|
|
|
GOUROU_DEVICE_DOES_NOT_MATCH = 0x1000,
|
|
|
|
GOUROU_INVALID_CLIENT,
|
|
|
|
GOUROU_TAG_NOT_FOUND,
|
|
|
|
GOUROU_ADEPT_ERROR,
|
2022-04-03 09:32:06 +02:00
|
|
|
GOUROU_FILE_ERROR,
|
|
|
|
GOUROU_INVALID_PROPERTY
|
2021-07-03 21:57:53 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
enum FULFILL_ERROR {
|
|
|
|
FF_ACSM_FILE_NOT_EXISTS = 0x1100,
|
|
|
|
FF_INVALID_ACSM_FILE,
|
|
|
|
FF_NO_HMAC_IN_ACSM_FILE,
|
|
|
|
FF_NOT_ACTIVATED,
|
|
|
|
FF_NO_OPERATOR_URL
|
|
|
|
};
|
|
|
|
|
|
|
|
enum DOWNLOAD_ERROR {
|
|
|
|
DW_NO_ITEM = 0x1200,
|
2021-09-28 15:01:04 +02:00
|
|
|
DW_NO_EBX_HANDLER,
|
2021-07-03 21:57:53 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
enum SIGNIN_ERROR {
|
|
|
|
SIGN_INVALID_CREDENTIALS = 0x1300,
|
|
|
|
};
|
|
|
|
|
|
|
|
enum ACTIVATE_ERROR {
|
|
|
|
ACTIVATE_NOT_SIGNEDIN = 0x1400
|
|
|
|
};
|
|
|
|
|
|
|
|
enum DEV_ERROR {
|
|
|
|
DEV_MKPATH = 0x2000,
|
|
|
|
DEV_MAC_ERROR,
|
|
|
|
DEV_INVALID_DEVICE_FILE,
|
|
|
|
DEV_INVALID_DEVICE_KEY_FILE,
|
|
|
|
DEV_INVALID_DEV_PROPERTY,
|
|
|
|
};
|
|
|
|
|
|
|
|
enum USER_ERROR {
|
|
|
|
USER_MKPATH = 0x3000,
|
|
|
|
USER_INVALID_ACTIVATION_FILE,
|
|
|
|
USER_NO_AUTHENTICATION_URL,
|
|
|
|
USER_NO_PROPERTY,
|
2022-03-12 23:04:16 +01:00
|
|
|
USER_INVALID_INPUT,
|
2021-07-03 21:57:53 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
enum FULFILL_ITEM_ERROR {
|
2022-04-03 09:32:06 +02:00
|
|
|
FFI_INVALID_FULFILLMENT_DATA = 0x4000,
|
|
|
|
FFI_INVALID_LOAN_TOKEN
|
2021-07-03 21:57:53 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
enum CLIENT_ERROR {
|
|
|
|
CLIENT_BAD_PARAM = 0x5000,
|
|
|
|
CLIENT_INVALID_PKCS12,
|
|
|
|
CLIENT_INVALID_CERTIFICATE,
|
|
|
|
CLIENT_NO_PRIV_KEY,
|
2022-08-10 21:37:43 +02:00
|
|
|
CLIENT_NO_PUB_KEY,
|
2021-07-03 21:57:53 +02:00
|
|
|
CLIENT_RSA_ERROR,
|
|
|
|
CLIENT_BAD_CHAINING,
|
|
|
|
CLIENT_BAD_KEY_SIZE,
|
|
|
|
CLIENT_BAD_ZIP_FILE,
|
|
|
|
CLIENT_ZIP_ERROR,
|
2021-07-10 12:51:36 +02:00
|
|
|
CLIENT_GENERIC_EXCEPTION,
|
|
|
|
CLIENT_NETWORK_ERROR,
|
2022-03-16 22:45:33 +01:00
|
|
|
CLIENT_INVALID_PKCS8,
|
2022-06-05 15:29:20 +02:00
|
|
|
CLIENT_FILE_ERROR,
|
|
|
|
CLIENT_OSSL_ERROR,
|
2022-08-29 12:18:29 +02:00
|
|
|
CLIENT_CRYPT_ERROR,
|
|
|
|
CLIENT_DIGEST_ERROR,
|
2021-07-03 21:57:53 +02:00
|
|
|
};
|
|
|
|
|
2021-11-26 15:01:26 +01:00
|
|
|
enum DRM_REMOVAL_ERROR {
|
2021-12-18 17:40:24 +01:00
|
|
|
DRM_ERR_ENCRYPTION_KEY = 0x6000,
|
|
|
|
DRM_VERSION_NOT_SUPPORTED,
|
|
|
|
DRM_FILE_ERROR,
|
|
|
|
DRM_FORMAT_NOT_SUPPORTED,
|
2021-12-23 21:12:03 +01:00
|
|
|
DRM_IN_OUT_EQUALS,
|
2022-02-22 20:58:26 +01:00
|
|
|
DRM_MISSING_PARAMETER,
|
2022-08-27 15:44:27 +02:00
|
|
|
DRM_INVALID_KEY_SIZE,
|
|
|
|
DRM_ERR_ENCRYPTION_KEY_FP
|
2021-11-26 15:01:26 +01:00
|
|
|
};
|
2022-11-21 17:56:29 +01:00
|
|
|
|
|
|
|
#ifndef _NOEXCEPT
|
|
|
|
#if __STDC_VERSION__ >= 201112L
|
|
|
|
# define _NOEXCEPT noexcept
|
|
|
|
# define _NOEXCEPT_(x) noexcept(x)
|
|
|
|
#else
|
|
|
|
# define _NOEXCEPT throw()
|
|
|
|
# define _NOEXCEPT_(x)
|
|
|
|
#endif
|
|
|
|
#endif /* !_NOEXCEPT */
|
|
|
|
|
2021-07-03 21:57:53 +02:00
|
|
|
/**
|
|
|
|
* 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;
|
2022-08-07 16:44:14 +02:00
|
|
|
if (logLevel >= LG_LOG_DEBUG)
|
2021-07-03 21:57:53 +02:00
|
|
|
msg << "File : " << file << ":" << std::setbase(10) << line << std::endl;
|
|
|
|
fullmessage = strdup(msg.str().c_str());
|
|
|
|
}
|
|
|
|
|
2021-07-16 20:38:32 +02:00
|
|
|
Exception(const Exception& other)
|
|
|
|
{
|
|
|
|
this->code = other.code;
|
|
|
|
this->line = line;
|
|
|
|
this->file = file;
|
|
|
|
this->fullmessage = strdup(other.fullmessage);
|
|
|
|
}
|
|
|
|
|
2022-11-21 17:56:29 +01:00
|
|
|
~Exception() _NOEXCEPT
|
2021-07-03 21:57:53 +02:00
|
|
|
{
|
|
|
|
free(fullmessage);
|
|
|
|
}
|
|
|
|
|
|
|
|
const char * what () const throw () { return fullmessage; }
|
|
|
|
|
|
|
|
int getErrorCode() {return code;}
|
|
|
|
|
|
|
|
private:
|
|
|
|
int code, line;
|
|
|
|
const char* message, *file;
|
|
|
|
char* fullmessage;
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @brief Throw an exception
|
|
|
|
*/
|
|
|
|
#define EXCEPTION(code, message) \
|
|
|
|
{std::stringstream __msg;__msg << message; throw gourou::Exception(code, __msg.str().c_str(), __FILE__, __LINE__);}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Stream writer for pugi::xml
|
|
|
|
*/
|
|
|
|
class StringXMLWriter : public pugi::xml_writer
|
|
|
|
{
|
|
|
|
public:
|
|
|
|
virtual void write(const void* data, size_t size)
|
|
|
|
{
|
|
|
|
result.append(static_cast<const char*>(data), size);
|
|
|
|
}
|
|
|
|
|
|
|
|
const std::string& getResult() {return result;}
|
|
|
|
|
|
|
|
private:
|
|
|
|
std::string result;
|
|
|
|
};
|
|
|
|
|
|
|
|
static const char* ws = " \t\n\r\f\v";
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @brief trim from end of string (right)
|
|
|
|
*/
|
|
|
|
inline std::string& rtrim(std::string& s, const char* t = ws)
|
|
|
|
{
|
|
|
|
s.erase(s.find_last_not_of(t) + 1);
|
|
|
|
return s;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @brief trim from beginning of string (left)
|
|
|
|
*/
|
|
|
|
inline std::string& ltrim(std::string& s, const char* t = ws)
|
|
|
|
{
|
|
|
|
s.erase(0, s.find_first_not_of(t));
|
|
|
|
return s;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @brief trim from both ends of string (right then left)
|
|
|
|
*/
|
|
|
|
inline std::string& trim(std::string& s, const char* t = ws)
|
|
|
|
{
|
|
|
|
return ltrim(rtrim(s, t), t);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @brief Extract text node from tag in document
|
|
|
|
* It can throw an exception if tag does not exists
|
|
|
|
* or just return an empty value
|
|
|
|
*/
|
2022-08-27 15:44:27 +02:00
|
|
|
static inline std::string extractTextElem(const pugi::xml_node& root, const char* tagName, bool throwOnNull=true)
|
2021-07-03 21:57:53 +02:00
|
|
|
{
|
2022-08-27 15:44:27 +02:00
|
|
|
pugi::xpath_node xpath_node = root.select_node(tagName);
|
2021-07-03 21:57:53 +02:00
|
|
|
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
2022-08-27 15:44:27 +02:00
|
|
|
/**
|
|
|
|
* @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)
|
2021-08-21 20:57:31 +02:00
|
|
|
{
|
2022-08-27 15:44:27 +02:00
|
|
|
pugi::xpath_node xpath_node = root.select_node(tagName);
|
2021-08-21 20:57:31 +02:00
|
|
|
|
|
|
|
if (!xpath_node)
|
|
|
|
{
|
|
|
|
if (throwOnNull)
|
|
|
|
EXCEPTION(GOUROU_TAG_NOT_FOUND, "Tag " << tagName << " not found");
|
|
|
|
|
|
|
|
return "";
|
|
|
|
}
|
|
|
|
|
2022-08-27 15:44:27 +02:00
|
|
|
pugi::xml_attribute attr = xpath_node.node().attribute(attributeName);
|
2021-08-21 20:57:31 +02:00
|
|
|
|
2022-08-27 15:44:27 +02:00
|
|
|
if (!attr)
|
2021-08-21 20:57:31 +02:00
|
|
|
{
|
|
|
|
if (throwOnNull)
|
2022-08-27 15:44:27 +02:00
|
|
|
EXCEPTION(GOUROU_TAG_NOT_FOUND, "Attribute element " << attributeName << " for tag " << tagName << " not found");
|
2021-08-21 20:57:31 +02:00
|
|
|
|
|
|
|
return "";
|
|
|
|
}
|
|
|
|
|
2022-08-27 15:44:27 +02:00
|
|
|
std::string res = attr.value();
|
|
|
|
return trim(res);
|
2021-08-21 20:57:31 +02:00
|
|
|
}
|
|
|
|
|
2021-07-03 21:57:53 +02:00
|
|
|
/**
|
|
|
|
* @brief Append an element to root with a sub text element
|
|
|
|
*
|
|
|
|
* @param root Root node where to put child
|
|
|
|
* @param name Tag name for child
|
|
|
|
* @param value Text child value of tag element
|
|
|
|
*/
|
|
|
|
static inline void appendTextElem(pugi::xml_node& root, const std::string& name, const std::string& value)
|
|
|
|
{
|
|
|
|
pugi::xml_node node = root.append_child(name.c_str());
|
|
|
|
node.append_child(pugi::node_pcdata).set_value(value.c_str());
|
|
|
|
}
|
|
|
|
|
2022-08-27 15:44:27 +02:00
|
|
|
/**
|
|
|
|
* Remove "urn:uuid:" prefix and all '-' from uuid
|
|
|
|
* urn:uuid:9cb786e8-586a-4950-8901-fff8d2ee6025
|
|
|
|
* ->
|
|
|
|
* 9cb786e8586a49508901fff8d2ee6025
|
|
|
|
*/
|
|
|
|
static inline std::string extractIdFromUUID(const std::string& uuid)
|
|
|
|
{
|
|
|
|
unsigned int i = 0;
|
|
|
|
std::string res;
|
|
|
|
|
|
|
|
if (uuid.find("urn:uuid:") == 0)
|
|
|
|
i = 9;
|
|
|
|
|
|
|
|
for(; i<uuid.size(); i++)
|
|
|
|
{
|
|
|
|
if (uuid[i] != '-')
|
|
|
|
res += uuid[i];
|
|
|
|
}
|
|
|
|
|
|
|
|
return res;
|
|
|
|
}
|
|
|
|
|
2021-07-03 21:57:53 +02:00
|
|
|
/**
|
2022-03-23 21:05:56 +01:00
|
|
|
* @brief Open a file descriptor on path. If it already exists and truncate == true, it's truncated
|
2022-03-17 21:55:02 +01:00
|
|
|
*
|
|
|
|
* @return Created fd, must be closed
|
2021-07-03 21:57:53 +02:00
|
|
|
*/
|
2022-03-23 21:05:56 +01:00
|
|
|
static inline int createNewFile(std::string path, bool truncate=true)
|
2021-07-03 21:57:53 +02:00
|
|
|
{
|
2022-03-23 21:05:56 +01:00
|
|
|
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);
|
2021-07-03 21:57:53 +02:00
|
|
|
|
|
|
|
if (fd <= 0)
|
|
|
|
EXCEPTION(GOUROU_FILE_ERROR, "Unable to create " << path);
|
|
|
|
|
2022-03-17 21:55:02 +01:00
|
|
|
return fd;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @brief Write data in a file. If it already exists, it's truncated
|
|
|
|
*/
|
|
|
|
static inline void writeFile(std::string path, const unsigned char* data, unsigned int length)
|
|
|
|
{
|
|
|
|
int fd = createNewFile(path);
|
|
|
|
|
2021-07-03 21:57:53 +02:00
|
|
|
if (write(fd, data, length) != length)
|
|
|
|
EXCEPTION(GOUROU_FILE_ERROR, "Write error for file " << path);
|
|
|
|
|
|
|
|
close (fd);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @brief Write data in a file. If it already exists, it's truncated
|
|
|
|
*/
|
|
|
|
static inline void writeFile(std::string path, ByteArray& data)
|
|
|
|
{
|
2021-09-28 14:58:41 +02:00
|
|
|
writeFile(path, data.data(), data.length());
|
2021-07-03 21:57:53 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @brief Write data in a file. If it already exists, it's truncated
|
|
|
|
*/
|
|
|
|
static inline void writeFile(std::string path, const std::string& data)
|
|
|
|
{
|
|
|
|
writeFile(path, (const unsigned char*)data.c_str(), data.length());
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Read data from file
|
|
|
|
*/
|
|
|
|
static inline void readFile(std::string path, const unsigned char* data, unsigned int length)
|
|
|
|
{
|
|
|
|
int fd = open(path.c_str(), O_RDONLY);
|
|
|
|
|
|
|
|
if (fd <= 0)
|
|
|
|
EXCEPTION(GOUROU_FILE_ERROR, "Unable to open " << path);
|
|
|
|
|
|
|
|
if (read(fd, (void*)data, length) != length)
|
|
|
|
EXCEPTION(GOUROU_FILE_ERROR, "Read error for file " << path);
|
|
|
|
|
|
|
|
close (fd);
|
|
|
|
}
|
|
|
|
|
|
|
|
#define PATH_MAX_STRING_SIZE 256
|
|
|
|
|
|
|
|
// https://gist.github.com/ChisholmKyle/0cbedcd3e64132243a39
|
|
|
|
/* recursive mkdir */
|
|
|
|
static inline int mkdir_p(const char *dir, const mode_t mode) {
|
|
|
|
char tmp[PATH_MAX_STRING_SIZE];
|
|
|
|
char *p = NULL;
|
|
|
|
struct stat sb;
|
|
|
|
size_t len;
|
|
|
|
|
|
|
|
/* copy path */
|
|
|
|
len = strnlen (dir, PATH_MAX_STRING_SIZE);
|
|
|
|
if (len == 0 || len == PATH_MAX_STRING_SIZE) {
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
memcpy (tmp, dir, len);
|
|
|
|
tmp[len] = '\0';
|
|
|
|
|
|
|
|
/* remove trailing slash */
|
|
|
|
if(tmp[len - 1] == '/') {
|
|
|
|
tmp[len - 1] = '\0';
|
|
|
|
}
|
|
|
|
|
|
|
|
/* check if path exists and is a directory */
|
|
|
|
if (stat (tmp, &sb) == 0) {
|
|
|
|
if (S_ISDIR (sb.st_mode)) {
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* recursive mkdir */
|
|
|
|
for(p = tmp + 1; *p; p++) {
|
|
|
|
if(*p == '/') {
|
|
|
|
*p = 0;
|
|
|
|
/* test path */
|
|
|
|
if (stat(tmp, &sb) != 0) {
|
|
|
|
/* path does not exist - create directory */
|
|
|
|
if (mkdir(tmp, mode) < 0) {
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
} else if (!S_ISDIR(sb.st_mode)) {
|
|
|
|
/* not a directory */
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
*p = '/';
|
|
|
|
}
|
|
|
|
}
|
|
|
|
/* test path */
|
|
|
|
if (stat(tmp, &sb) != 0) {
|
|
|
|
/* path does not exist - create directory */
|
|
|
|
if (mkdir(tmp, mode) < 0) {
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
} else if (!S_ISDIR(sb.st_mode)) {
|
|
|
|
/* not a directory */
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
2022-08-27 15:42:11 +02:00
|
|
|
|
|
|
|
static inline void dumpBuffer(GOUROU_LOG_LEVEL level, const char* title, const unsigned char* data, unsigned int len)
|
|
|
|
{
|
|
|
|
if (gourou::logLevel < level)
|
|
|
|
return;
|
|
|
|
|
|
|
|
printf("%s", title);
|
|
|
|
for(unsigned int i=0; i<len; i++)
|
|
|
|
{
|
|
|
|
if (i && !(i%16)) printf("\n");
|
|
|
|
printf("%02x ", data[i]);
|
|
|
|
}
|
|
|
|
printf("\n");
|
|
|
|
}
|
2021-07-03 21:57:53 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
#endif
|