forked from soutade/libgourou
Initial commit
This commit is contained in:
173
src/bytearray.cpp
Normal file
173
src/bytearray.cpp
Normal file
@@ -0,0 +1,173 @@
|
||||
/*
|
||||
Copyright 2021 Grégory Soutadé
|
||||
|
||||
This file is part of libgourou.
|
||||
|
||||
libgourou is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Lesser General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
libgourou is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public License
|
||||
along with libgourou. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include <string.h>
|
||||
|
||||
#include <base64/Base64.h>
|
||||
|
||||
#include <bytearray.h>
|
||||
|
||||
namespace gourou
|
||||
{
|
||||
std::map<const unsigned char*, int> ByteArray::refCounter;
|
||||
|
||||
ByteArray::ByteArray():_data(0), _length(0)
|
||||
{}
|
||||
|
||||
ByteArray::ByteArray(const unsigned char* data, unsigned int length)
|
||||
{
|
||||
initData(data, length);
|
||||
}
|
||||
|
||||
ByteArray::ByteArray(const char* data, int length)
|
||||
{
|
||||
if (length == -1)
|
||||
length = strlen(data) + 1;
|
||||
|
||||
initData((const unsigned char*)data, (unsigned int) length);
|
||||
}
|
||||
|
||||
ByteArray::ByteArray(const std::string& str)
|
||||
{
|
||||
initData((unsigned char*)str.c_str(), (unsigned int)str.length() + 1);
|
||||
}
|
||||
|
||||
void ByteArray::initData(const unsigned char* data, unsigned int length)
|
||||
{
|
||||
_data = new unsigned char[length];
|
||||
memcpy((void*)_data, data, length);
|
||||
_length = length;
|
||||
|
||||
addRef();
|
||||
}
|
||||
|
||||
ByteArray::ByteArray(const ByteArray& other)
|
||||
{
|
||||
this->_data = other._data;
|
||||
this->_length = other._length;
|
||||
|
||||
addRef();
|
||||
}
|
||||
|
||||
ByteArray& ByteArray::operator=(const ByteArray& other)
|
||||
{
|
||||
delRef();
|
||||
|
||||
this->_data = other._data;
|
||||
this->_length = other._length;
|
||||
|
||||
addRef();
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
ByteArray::~ByteArray()
|
||||
{
|
||||
delRef();
|
||||
}
|
||||
|
||||
void ByteArray::addRef()
|
||||
{
|
||||
if (!_data) return;
|
||||
|
||||
if (refCounter.count(_data) == 0)
|
||||
refCounter[_data] = 1;
|
||||
else
|
||||
refCounter[_data]++;
|
||||
}
|
||||
|
||||
void ByteArray::delRef()
|
||||
{
|
||||
if (!_data) return;
|
||||
|
||||
if (refCounter[_data] == 1)
|
||||
{
|
||||
delete[] _data;
|
||||
refCounter.erase(_data);
|
||||
}
|
||||
else
|
||||
refCounter[_data]--;
|
||||
}
|
||||
|
||||
ByteArray ByteArray::fromBase64(const ByteArray& other)
|
||||
{
|
||||
std::string b64;
|
||||
|
||||
macaron::Base64::Decode(std::string((char*)other._data, other._length), b64);
|
||||
|
||||
return ByteArray(b64);
|
||||
}
|
||||
|
||||
ByteArray ByteArray::fromBase64(const char* data, int length)
|
||||
{
|
||||
std::string b64;
|
||||
|
||||
if (length == -1)
|
||||
length = strlen(data);
|
||||
|
||||
macaron::Base64::Decode(std::string(data, length), b64);
|
||||
|
||||
return ByteArray(b64);
|
||||
}
|
||||
|
||||
ByteArray ByteArray::fromBase64(const std::string& str)
|
||||
{
|
||||
return ByteArray::fromBase64(str.c_str(), str.length());
|
||||
}
|
||||
|
||||
std::string ByteArray::toBase64()
|
||||
{
|
||||
return macaron::Base64::Encode(std::string((char*)_data, _length));
|
||||
}
|
||||
|
||||
std::string ByteArray::toHex()
|
||||
{
|
||||
char* tmp = new char[_length*2+1];
|
||||
|
||||
for(int i=0; i<(int)_length; i++)
|
||||
sprintf(&tmp[i*2], "%02x", _data[i]);
|
||||
|
||||
tmp[_length*2] = 0;
|
||||
|
||||
std::string res = tmp;
|
||||
delete[] tmp;
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
void ByteArray::append(const unsigned char* data, unsigned int length)
|
||||
{
|
||||
const unsigned char* oldData = _data;
|
||||
unsigned char* newData = new unsigned char[_length+length];
|
||||
|
||||
memcpy(newData, oldData, _length);
|
||||
|
||||
delRef();
|
||||
|
||||
memcpy(&newData[_length], data, length);
|
||||
_length += length;
|
||||
|
||||
_data = newData;
|
||||
|
||||
addRef();
|
||||
}
|
||||
|
||||
void ByteArray::append(unsigned char c) { append(&c, 1);}
|
||||
void ByteArray::append(const char* str) { append((const unsigned char*)str, strlen(str));}
|
||||
void ByteArray::append(const std::string& str) { append((const unsigned char*)str.c_str(), str.length()); }
|
||||
}
|
305
src/device.cpp
Normal file
305
src/device.cpp
Normal file
@@ -0,0 +1,305 @@
|
||||
/*
|
||||
Copyright 2021 Grégory Soutadé
|
||||
|
||||
This file is part of libgourou.
|
||||
|
||||
libgourou is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Lesser General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
libgourou is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public License
|
||||
along with libgourou. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/utsname.h>
|
||||
#include <pwd.h>
|
||||
#include <locale.h>
|
||||
|
||||
#include <libgourou.h>
|
||||
#include <libgourou_common.h>
|
||||
#include <libgourou_log.h>
|
||||
#include <device.h>
|
||||
|
||||
// From https://stackoverflow.com/questions/1779715/how-to-get-mac-address-of-your-machine-using-a-c-program/35242525
|
||||
#include <sys/ioctl.h>
|
||||
#include <net/if.h>
|
||||
#include <unistd.h>
|
||||
#include <netinet/in.h>
|
||||
#include <string.h>
|
||||
|
||||
int get_mac_address(unsigned char* mac_address)
|
||||
{
|
||||
struct ifreq ifr;
|
||||
struct ifconf ifc;
|
||||
char buf[1024];
|
||||
int success = 0;
|
||||
|
||||
int sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_IP);
|
||||
if (sock == -1) { EXCEPTION(gourou::DEV_MAC_ERROR, "Unable to create socket"); };
|
||||
|
||||
ifc.ifc_len = sizeof(buf);
|
||||
ifc.ifc_buf = buf;
|
||||
if (ioctl(sock, SIOCGIFCONF, &ifc) == -1) { EXCEPTION(gourou::DEV_MAC_ERROR, "SIOCGIFCONF ioctl failed"); }
|
||||
|
||||
struct ifreq* it = ifc.ifc_req;
|
||||
const struct ifreq* const end = it + (ifc.ifc_len / sizeof(struct ifreq));
|
||||
|
||||
for (; it != end; ++it) {
|
||||
strcpy(ifr.ifr_name, it->ifr_name);
|
||||
if (ioctl(sock, SIOCGIFFLAGS, &ifr) == 0) {
|
||||
if (! (ifr.ifr_flags & IFF_LOOPBACK)) { // don't count loopback
|
||||
if (ioctl(sock, SIOCGIFHWADDR, &ifr) == 0) {
|
||||
success = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
else { EXCEPTION(gourou::DEV_MAC_ERROR, "SIOCGIFFLAGS ioctl failed"); }
|
||||
}
|
||||
|
||||
if (success)
|
||||
{
|
||||
memcpy(mac_address, ifr.ifr_hwaddr.sa_data, 6);
|
||||
return 0;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
namespace gourou
|
||||
{
|
||||
Device::Device(DRMProcessor* processor):
|
||||
processor(processor)
|
||||
{}
|
||||
|
||||
Device::Device(DRMProcessor* processor, const std::string& deviceFile, const std::string& deviceKeyFile):
|
||||
processor(processor), deviceFile(deviceFile), deviceKeyFile(deviceKeyFile)
|
||||
{
|
||||
parseDeviceKeyFile();
|
||||
parseDeviceFile();
|
||||
}
|
||||
|
||||
/* SHA1(uid ":" username ":" macaddress ":" */
|
||||
std::string Device::makeSerial(bool random)
|
||||
{
|
||||
unsigned char sha_out[SHA1_LEN];
|
||||
DRMProcessorClient* client = processor->getClient();
|
||||
|
||||
if (!random)
|
||||
{
|
||||
uid_t uid = getuid();
|
||||
struct passwd * passwd = getpwuid(uid);
|
||||
// Default mac address in case of failure
|
||||
unsigned char mac_address[6] = {0x01, 0x02, 0x03, 0x04, 0x05};
|
||||
|
||||
get_mac_address(mac_address);
|
||||
|
||||
int dataToHashLen = 10 /* UID */ + strlen(passwd->pw_name) + sizeof(mac_address)*2 /*mac address*/ + 1 /* \0 */;
|
||||
dataToHashLen += 8; /* Separators */
|
||||
unsigned char* dataToHash = new unsigned char[dataToHashLen];
|
||||
dataToHashLen = snprintf((char*)dataToHash, dataToHashLen, "%d:%s:%02x:%02x:%02x:%02x:%02x:%02x:",
|
||||
uid, passwd->pw_name,
|
||||
mac_address[0], mac_address[1], mac_address[2],
|
||||
mac_address[3], mac_address[4], mac_address[5]);
|
||||
|
||||
client->digest("SHA1", dataToHash, dataToHashLen+1, sha_out);
|
||||
|
||||
delete[] dataToHash;
|
||||
}
|
||||
else
|
||||
{
|
||||
client->randBytes(sha_out, sizeof(sha_out));
|
||||
}
|
||||
|
||||
std::string res = ByteArray((const char*)sha_out, DEVICE_SERIAL_LEN).toHex();
|
||||
GOUROU_LOG(DEBUG, "Serial : " << res);
|
||||
return res;
|
||||
}
|
||||
|
||||
/* base64(SHA1 (serial + privateKey)) */
|
||||
std::string Device::makeFingerprint(const std::string& serial)
|
||||
{
|
||||
DRMProcessorClient* client = processor->getClient();
|
||||
unsigned char sha_out[SHA1_LEN];
|
||||
|
||||
void* handler = client->createDigest("SHA1");
|
||||
client->digestUpdate(handler, (unsigned char*) serial.c_str(), serial.length());
|
||||
client->digestUpdate(handler, deviceKey, sizeof(deviceKey));
|
||||
client->digestFinalize(handler, sha_out);
|
||||
|
||||
std::string res = ByteArray(sha_out, sizeof(sha_out)).toBase64();
|
||||
GOUROU_LOG(DEBUG, "Fingerprint : " << res);
|
||||
return res;
|
||||
}
|
||||
|
||||
void Device::createDeviceFile(const std::string& hobbes, bool randomSerial)
|
||||
{
|
||||
struct utsname sysname;
|
||||
uname(&sysname);
|
||||
|
||||
std::string serial = makeSerial(randomSerial);
|
||||
std::string fingerprint = makeFingerprint(serial);
|
||||
|
||||
pugi::xml_document deviceDoc;
|
||||
pugi::xml_node decl = deviceDoc.append_child(pugi::node_declaration);
|
||||
decl.append_attribute("version") = "1.0";
|
||||
|
||||
pugi::xml_node root = deviceDoc.append_child("adept:deviceInfo");
|
||||
root.append_attribute("xmlns:adept") = ADOBE_ADEPT_NS;
|
||||
|
||||
appendTextElem(root, "adept:deviceClass", "Desktop");
|
||||
appendTextElem(root, "adept:deviceSerial", serial);
|
||||
appendTextElem(root, "adept:deviceName", sysname.nodename);
|
||||
appendTextElem(root, "adept:deviceType", "standalone");
|
||||
|
||||
pugi::xml_node version = root.append_child("adept:version");
|
||||
version.append_attribute("name") = "hobbes";
|
||||
version.append_attribute("value") = hobbes.c_str();
|
||||
|
||||
version = root.append_child("adept:version");
|
||||
version.append_attribute("name") = "clientOS";
|
||||
std::string os = std::string(sysname.sysname) + " " + std::string(sysname.release);
|
||||
version.append_attribute("value") = os.c_str();
|
||||
|
||||
version = root.append_child("adept:version");
|
||||
version.append_attribute("name") = "clientLocale";
|
||||
version.append_attribute("value") = setlocale(LC_ALL, NULL);
|
||||
|
||||
appendTextElem(root, "adept:fingerprint", fingerprint);
|
||||
|
||||
StringXMLWriter xmlWriter;
|
||||
deviceDoc.save(xmlWriter, " ");
|
||||
|
||||
GOUROU_LOG(DEBUG, "Create device file " << deviceFile);
|
||||
|
||||
writeFile(deviceFile, xmlWriter.getResult());
|
||||
}
|
||||
|
||||
void Device::createDeviceKeyFile()
|
||||
{
|
||||
unsigned char key[DEVICE_KEY_SIZE];
|
||||
|
||||
GOUROU_LOG(DEBUG, "Create device key file " << deviceKeyFile);
|
||||
|
||||
processor->getClient()->randBytes(key, sizeof(key));
|
||||
|
||||
writeFile(deviceKeyFile, key, sizeof(key));
|
||||
}
|
||||
|
||||
Device* Device::createDevice(DRMProcessor* processor, const std::string& dirName, const std::string& hobbes, bool randomSerial)
|
||||
{
|
||||
struct stat _stat;
|
||||
|
||||
if (stat(dirName.c_str(), &_stat) != 0)
|
||||
{
|
||||
if (mkdir_p(dirName.c_str(), S_IRWXU))
|
||||
EXCEPTION(DEV_MKPATH, "Unable to create " << dirName)
|
||||
}
|
||||
|
||||
Device* device = new Device(processor);
|
||||
|
||||
device->deviceFile = dirName + "/device.xml";
|
||||
device->deviceKeyFile = dirName + "/devicesalt";
|
||||
|
||||
try
|
||||
{
|
||||
device->parseDeviceKeyFile();
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
device->createDeviceKeyFile();
|
||||
device->parseDeviceKeyFile();
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
device->parseDeviceFile();
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
device->createDeviceFile(hobbes, randomSerial);
|
||||
device->parseDeviceFile();
|
||||
}
|
||||
|
||||
return device;
|
||||
}
|
||||
|
||||
const unsigned char* Device::getDeviceKey()
|
||||
{
|
||||
return deviceKey;
|
||||
}
|
||||
|
||||
void Device::parseDeviceFile()
|
||||
{
|
||||
pugi::xml_document doc;
|
||||
|
||||
if (!doc.load_file(deviceFile.c_str()))
|
||||
EXCEPTION(DEV_INVALID_DEVICE_FILE, "Invalid device file");
|
||||
|
||||
try
|
||||
{
|
||||
properties["deviceClass"] = gourou::extractTextElem(doc, "/adept:deviceInfo/adept:deviceClass");
|
||||
properties["deviceSerial"] = gourou::extractTextElem(doc, "/adept:deviceInfo/adept:deviceSerial");
|
||||
properties["deviceName"] = gourou::extractTextElem(doc, "/adept:deviceInfo/adept:deviceName");
|
||||
properties["deviceType"] = gourou::extractTextElem(doc, "/adept:deviceInfo/adept:deviceType");
|
||||
properties["fingerprint"] = gourou::extractTextElem(doc, "/adept:deviceInfo/adept:fingerprint");
|
||||
|
||||
pugi::xpath_node_set nodeSet = doc.select_nodes("/adept:deviceInfo/adept:version");
|
||||
|
||||
for (pugi::xpath_node_set::const_iterator it = nodeSet.begin();
|
||||
it != nodeSet.end(); ++it)
|
||||
{
|
||||
pugi::xml_node node = it->node();
|
||||
pugi::xml_attribute name = node.attribute("name");
|
||||
pugi::xml_attribute value = node.attribute("value");
|
||||
|
||||
properties[name.value()] = value.value();
|
||||
}
|
||||
}
|
||||
catch (gourou::Exception& e)
|
||||
{
|
||||
EXCEPTION(DEV_INVALID_DEVICE_FILE, "Invalid device file");
|
||||
}
|
||||
}
|
||||
|
||||
void Device::parseDeviceKeyFile()
|
||||
{
|
||||
struct stat _stat;
|
||||
|
||||
if (stat(deviceKeyFile.c_str(), &_stat) == 0 &&
|
||||
_stat.st_size == DEVICE_KEY_SIZE)
|
||||
{
|
||||
readFile(deviceKeyFile, deviceKey, sizeof(deviceKey));
|
||||
}
|
||||
else
|
||||
EXCEPTION(DEV_INVALID_DEVICE_KEY_FILE, "Invalid device key file");
|
||||
}
|
||||
|
||||
std::string Device::getProperty(const std::string& property, const std::string& _default)
|
||||
{
|
||||
if (properties.find(property) == properties.end())
|
||||
{
|
||||
if (_default == "")
|
||||
EXCEPTION(DEV_INVALID_DEV_PROPERTY, "Invalid property " << property);
|
||||
|
||||
return _default;
|
||||
}
|
||||
|
||||
return properties[property];
|
||||
}
|
||||
|
||||
std::string Device::operator[](const std::string& property)
|
||||
{
|
||||
return getProperty(property);
|
||||
}
|
||||
}
|
91
src/fulfillment_item.cpp
Normal file
91
src/fulfillment_item.cpp
Normal file
@@ -0,0 +1,91 @@
|
||||
/*
|
||||
Copyright 2021 Grégory Soutadé
|
||||
|
||||
This file is part of libgourou.
|
||||
|
||||
libgourou is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Lesser General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
libgourou is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public License
|
||||
along with libgourou. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <fulfillment_item.h>
|
||||
#include <libgourou_common.h>
|
||||
#include "user.h"
|
||||
|
||||
namespace gourou
|
||||
{
|
||||
FulfillmentItem::FulfillmentItem(pugi::xml_document& doc, User* user)
|
||||
{
|
||||
metadatas = doc.select_node("//metadata").node();
|
||||
|
||||
if (!metadatas)
|
||||
EXCEPTION(FFI_INVALID_FULFILLMENT_DATA, "No metadata tag in document");
|
||||
|
||||
pugi::xml_node node = doc.select_node("/envelope/fulfillmentResult/resourceItemInfo/src").node();
|
||||
downloadURL = node.first_child().value();
|
||||
|
||||
if (downloadURL == "")
|
||||
EXCEPTION(FFI_INVALID_FULFILLMENT_DATA, "No download URL in document");
|
||||
|
||||
pugi::xml_node licenseToken = doc.select_node("/envelope/fulfillmentResult/resourceItemInfo/licenseToken").node();
|
||||
|
||||
if (!licenseToken)
|
||||
EXCEPTION(FFI_INVALID_FULFILLMENT_DATA, "Any license token in document");
|
||||
|
||||
buildRights(licenseToken, user);
|
||||
}
|
||||
|
||||
void FulfillmentItem::buildRights(const pugi::xml_node& licenseToken, User* user)
|
||||
{
|
||||
pugi::xml_node decl = rights.append_child(pugi::node_declaration);
|
||||
decl.append_attribute("version") = "1.0";
|
||||
|
||||
pugi::xml_node root = rights.append_child("adept:rights");
|
||||
root.append_attribute("xmlns:adept") = ADOBE_ADEPT_NS;
|
||||
|
||||
pugi::xml_node newLicenseToken = root.append_copy(licenseToken);
|
||||
if (!newLicenseToken.attribute("xmlns"))
|
||||
newLicenseToken.append_attribute("xmlns") = ADOBE_ADEPT_NS;
|
||||
|
||||
pugi::xml_node licenseServiceInfo = root.append_child("licenseServiceInfo");
|
||||
licenseServiceInfo.append_attribute("xmlns") = ADOBE_ADEPT_NS;
|
||||
licenseServiceInfo.append_copy(licenseToken.select_node("licenseURL").node());
|
||||
pugi::xml_node certificate = licenseServiceInfo.append_child("certificate");
|
||||
certificate.append_child(pugi::node_pcdata).set_value(user->getCertificate().c_str());
|
||||
}
|
||||
|
||||
std::string FulfillmentItem::getMetadata(std::string name)
|
||||
{
|
||||
// https://stackoverflow.com/questions/313970/how-to-convert-an-instance-of-stdstring-to-lower-case
|
||||
std::transform(name.begin(), name.end(), name.begin(),
|
||||
[](unsigned char c){ return std::tolower(c); });
|
||||
name = std::string("dc:") + name;
|
||||
pugi::xpath_node path = metadatas.select_node(name.c_str());
|
||||
|
||||
if (!path)
|
||||
return "";
|
||||
|
||||
return path.node().first_child().value();
|
||||
}
|
||||
|
||||
std::string FulfillmentItem::getRights()
|
||||
{
|
||||
StringXMLWriter xmlWriter;
|
||||
rights.save(xmlWriter, " ");
|
||||
return xmlWriter.getResult();
|
||||
}
|
||||
|
||||
std::string FulfillmentItem::getDownloadURL()
|
||||
{
|
||||
return downloadURL;
|
||||
}
|
||||
}
|
662
src/libgourou.cpp
Normal file
662
src/libgourou.cpp
Normal file
@@ -0,0 +1,662 @@
|
||||
/*
|
||||
Copyright 2021 Grégory Soutadé
|
||||
|
||||
This file is part of libgourou.
|
||||
|
||||
libgourou is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Lesser General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
libgourou is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public License
|
||||
along with libgourou. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <arpa/inet.h>
|
||||
#include <sys/time.h>
|
||||
#include <time.h>
|
||||
|
||||
#include <libgourou.h>
|
||||
#include <libgourou_common.h>
|
||||
#include <libgourou_log.h>
|
||||
|
||||
#define ASN_NONE 0x00
|
||||
#define ASN_NS_TAG 0x01
|
||||
#define ASN_CHILD 0x02
|
||||
#define ASN_END_TAG 0x03
|
||||
#define ASN_TEXT 0x04
|
||||
#define ASN_ATTRIBUTE 0x05
|
||||
|
||||
namespace gourou
|
||||
{
|
||||
GOUROU_LOG_LEVEL logLevel = WARN;
|
||||
|
||||
DRMProcessor::DRMProcessor(DRMProcessorClient* client):client(client), device(0), user(0)
|
||||
{
|
||||
if (!client)
|
||||
EXCEPTION(GOUROU_INVALID_CLIENT, "DRMProcessorClient is NULL");
|
||||
}
|
||||
|
||||
DRMProcessor::DRMProcessor(DRMProcessorClient* client,
|
||||
const std::string& deviceFile, const std::string& activationFile,
|
||||
const std::string& deviceKeyFile):
|
||||
client(client), device(0), user(0)
|
||||
{
|
||||
if (!client)
|
||||
EXCEPTION(GOUROU_INVALID_CLIENT, "DRMProcessorClient is NULL");
|
||||
|
||||
device = new Device(this, deviceFile, deviceKeyFile);
|
||||
user = new User(this, activationFile);
|
||||
|
||||
if (user->getDeviceFingerprint() != "" &&
|
||||
(*device)["fingerprint"] != user->getDeviceFingerprint())
|
||||
EXCEPTION(GOUROU_DEVICE_DOES_NOT_MATCH, "User and device fingerprint does not match");
|
||||
}
|
||||
|
||||
DRMProcessor::~DRMProcessor()
|
||||
{
|
||||
if (device) delete device;
|
||||
if (user) delete user;
|
||||
}
|
||||
|
||||
DRMProcessor* DRMProcessor::createDRMProcessor(DRMProcessorClient* client, bool randomSerial, const std::string& dirName,
|
||||
const std::string& hobbes, const std::string& ACSServer)
|
||||
{
|
||||
DRMProcessor* processor = new DRMProcessor(client);
|
||||
|
||||
Device* device = Device::createDevice(processor, dirName, hobbes, randomSerial);
|
||||
processor->device = device;
|
||||
|
||||
User* user = User::createUser(processor, dirName, ACSServer);
|
||||
processor->user = user;
|
||||
|
||||
return processor;
|
||||
}
|
||||
|
||||
|
||||
void DRMProcessor::pushString(void* sha_ctx, const std::string& string)
|
||||
{
|
||||
int length = string.length();
|
||||
uint16_t nlength = htons(length);
|
||||
char c;
|
||||
|
||||
if (logLevel >= TRACE)
|
||||
printf("%02x %02x ", ((uint8_t*)&nlength)[0], ((uint8_t*)&nlength)[1]);
|
||||
|
||||
client->digestUpdate(sha_ctx, (unsigned char*)&nlength, sizeof(nlength));
|
||||
|
||||
for(int i=0; i<length; i++)
|
||||
{
|
||||
c = string[i];
|
||||
client->digestUpdate(sha_ctx, (unsigned char*)&c, 1);
|
||||
if (logLevel >= TRACE)
|
||||
printf("%c", c);
|
||||
}
|
||||
if (logLevel >= TRACE)
|
||||
printf("\n");
|
||||
}
|
||||
|
||||
void DRMProcessor::pushTag(void* sha_ctx, uint8_t tag)
|
||||
{
|
||||
client->digestUpdate(sha_ctx, &tag, sizeof(tag));
|
||||
if (logLevel >= TRACE)
|
||||
printf("%02x ", tag);
|
||||
}
|
||||
|
||||
void DRMProcessor::hashNode(const pugi::xml_node& root, void *sha_ctx, std::map<std::string,std::string> nsHash)
|
||||
{
|
||||
switch(root.type())
|
||||
{
|
||||
case pugi::node_element:
|
||||
{
|
||||
std::string name = root.name();
|
||||
|
||||
// Look for "xmlns[:]" attribute
|
||||
for (pugi::xml_attribute_iterator ait = root.attributes_begin();
|
||||
ait != root.attributes_end(); ++ait)
|
||||
{
|
||||
std::string attrName(ait->name());
|
||||
|
||||
if (attrName.find("xmlns") == 0)
|
||||
{
|
||||
std::string ns("GENERICNS");
|
||||
// Compound xmlns:Name attribute
|
||||
if (attrName.find(':') != std::string::npos)
|
||||
ns = attrName.substr(attrName.find(':')+1);
|
||||
|
||||
nsHash[ns] = ait->value();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Remove namespace from tag
|
||||
// If we have a namespace for the first time, put it to hash
|
||||
if (name.find(':') != std::string::npos)
|
||||
{
|
||||
size_t nsIndex = name.find(':');
|
||||
std::string nodeNS = name.substr(0, nsIndex);
|
||||
|
||||
pushTag(sha_ctx, ASN_NS_TAG);
|
||||
pushString(sha_ctx, nsHash[nodeNS]);
|
||||
|
||||
name = name.substr(nsIndex+1);
|
||||
}
|
||||
// Global xmlns, always send to hash
|
||||
else if (nsHash.find("GENERICNS") != nsHash.end())
|
||||
{
|
||||
pushTag(sha_ctx, ASN_NS_TAG);
|
||||
pushString(sha_ctx, nsHash["GENERICNS"]);
|
||||
}
|
||||
|
||||
pushString(sha_ctx, name);
|
||||
|
||||
// Must be parsed in reverse order
|
||||
for (pugi::xml_attribute attr = root.last_attribute();
|
||||
attr; attr = attr.previous_attribute())
|
||||
{
|
||||
if (std::string(attr.name()).find("xmlns") != std::string::npos)
|
||||
continue;
|
||||
|
||||
pushTag(sha_ctx, ASN_ATTRIBUTE);
|
||||
pushString(sha_ctx, "");
|
||||
|
||||
pushString(sha_ctx, attr.name());
|
||||
pushString(sha_ctx, attr.value());
|
||||
}
|
||||
|
||||
pushTag(sha_ctx, ASN_CHILD);
|
||||
|
||||
for (pugi::xml_node child : root.children())
|
||||
hashNode(child, sha_ctx, nsHash);
|
||||
|
||||
pushTag(sha_ctx, ASN_END_TAG);
|
||||
|
||||
break;
|
||||
}
|
||||
case pugi::node_pcdata:
|
||||
{
|
||||
std::string trimmed = root.value();
|
||||
trimmed = trim(trimmed);
|
||||
|
||||
if (trimmed.length())
|
||||
{
|
||||
pushTag(sha_ctx, ASN_TEXT);
|
||||
pushString(sha_ctx, trimmed);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void DRMProcessor::hashNode(const pugi::xml_node& root, unsigned char* sha_out)
|
||||
{
|
||||
void* sha_ctx = client->createDigest("SHA1");
|
||||
|
||||
std::map<std::string, std::string> nsHash;
|
||||
|
||||
hashNode(root, sha_ctx, nsHash);
|
||||
|
||||
client->digestFinalize(sha_ctx, sha_out);
|
||||
|
||||
if (logLevel >= DEBUG)
|
||||
{
|
||||
printf("\nSHA OUT : ");
|
||||
for(int i=0; i<(int)SHA1_LEN; i++)
|
||||
printf("%02x ", sha_out[i]);
|
||||
printf("\n");
|
||||
}
|
||||
}
|
||||
|
||||
ByteArray DRMProcessor::sendRequest(const std::string& URL, const std::string& POSTdata, const char* contentType)
|
||||
{
|
||||
if (contentType == 0)
|
||||
contentType = "";
|
||||
std::string reply = client->sendHTTPRequest(URL, POSTdata, contentType);
|
||||
|
||||
pugi::xml_document replyDoc;
|
||||
replyDoc.load_buffer(reply.c_str(), reply.length());
|
||||
|
||||
pugi::xml_node root = replyDoc.first_child();
|
||||
if (std::string(root.name()) == "error")
|
||||
{
|
||||
EXCEPTION(GOUROU_ADEPT_ERROR, root.attribute("data").value());
|
||||
}
|
||||
|
||||
return ByteArray(reply);
|
||||
}
|
||||
|
||||
ByteArray DRMProcessor::sendRequest(const pugi::xml_document& document, const std::string& url)
|
||||
{
|
||||
StringXMLWriter xmlWriter;
|
||||
document.save(xmlWriter, " ");
|
||||
std::string xmlStr = xmlWriter.getResult();
|
||||
|
||||
return sendRequest(url, xmlStr, (const char*)"application/vnd.adobe.adept+xml");
|
||||
}
|
||||
|
||||
void DRMProcessor::buildFulfillRequest(pugi::xml_document& acsmDoc, pugi::xml_document& fulfillReq)
|
||||
{
|
||||
pugi::xml_node decl = fulfillReq.append_child(pugi::node_declaration);
|
||||
decl.append_attribute("version") = "1.0";
|
||||
|
||||
pugi::xml_node root = fulfillReq.append_child("adept:fulfill");
|
||||
root.append_attribute("xmlns:adept") = ADOBE_ADEPT_NS;
|
||||
|
||||
appendTextElem(root, "adept:user", user->getUUID());
|
||||
appendTextElem(root, "adept:device", user->getDeviceUUID());
|
||||
appendTextElem(root, "adept:deviceType", (*device)["deviceType"]);
|
||||
|
||||
root.append_copy(acsmDoc.first_child());
|
||||
|
||||
pugi::xml_node targetDevice = root.append_child("adept:targetDevice");
|
||||
appendTextElem(targetDevice, "adept:softwareVersion", (*device)["hobbes"]);
|
||||
appendTextElem(targetDevice, "adept:clientOS", (*device)["clientOS"]);
|
||||
appendTextElem(targetDevice, "adept:clientLocale", (*device)["clientLocale"]);
|
||||
appendTextElem(targetDevice, "adept:clientVersion", (*device)["deviceClass"]);
|
||||
appendTextElem(targetDevice, "adept:deviceType", (*device)["deviceType"]);
|
||||
appendTextElem(targetDevice, "adept:fingerprint", (*device)["fingerprint"]);
|
||||
|
||||
pugi::xml_node activationToken = targetDevice.append_child("adept:activationToken");
|
||||
appendTextElem(activationToken, "adept:user", user->getUUID());
|
||||
appendTextElem(activationToken, "adept:device", user->getDeviceUUID());
|
||||
}
|
||||
|
||||
FulfillmentItem* DRMProcessor::fulfill(const std::string& ACSMFile)
|
||||
{
|
||||
if (!user->getPKCS12().length())
|
||||
EXCEPTION(FF_NOT_ACTIVATED, "Device not activated");
|
||||
|
||||
pugi::xml_document acsmDoc;
|
||||
|
||||
if (!acsmDoc.load_file(ACSMFile.c_str(), pugi::parse_ws_pcdata_single))
|
||||
EXCEPTION(FF_INVALID_ACSM_FILE, "Invalid ACSM file " << ACSMFile);
|
||||
|
||||
GOUROU_LOG(INFO, "Fulfill " << ACSMFile);
|
||||
|
||||
// Build req file
|
||||
pugi::xml_document fulfillReq;
|
||||
|
||||
buildFulfillRequest(acsmDoc, fulfillReq);
|
||||
pugi::xpath_node root = fulfillReq.select_node("//adept:fulfill");
|
||||
pugi::xml_node rootNode = root.node();
|
||||
|
||||
// Remove HMAC
|
||||
pugi::xpath_node xpathRes = fulfillReq.select_node("//hmac");
|
||||
|
||||
if (!xpathRes)
|
||||
EXCEPTION(FF_NO_HMAC_IN_ACSM_FILE, "hmac tag not found in ACSM file");
|
||||
|
||||
pugi::xml_node hmacNode = xpathRes.node();
|
||||
pugi::xml_node hmacParentNode = hmacNode.parent();
|
||||
|
||||
hmacParentNode.remove_child(hmacNode);
|
||||
|
||||
// Compute hash
|
||||
unsigned char sha_out[SHA1_LEN];
|
||||
|
||||
hashNode(rootNode, sha_out);
|
||||
|
||||
// Sign with private key
|
||||
unsigned char res[RSA_KEY_SIZE];
|
||||
ByteArray deviceKey(device->getDeviceKey(), Device::DEVICE_KEY_SIZE);
|
||||
std::string pkcs12 = user->getPKCS12();
|
||||
ByteArray privateRSAKey = ByteArray::fromBase64(pkcs12);
|
||||
|
||||
client->RSAPrivateEncrypt(privateRSAKey.data(), privateRSAKey.length(),
|
||||
RSAInterface::RSA_KEY_PKCS12, deviceKey.toBase64().data(),
|
||||
sha_out, sizeof(sha_out), res);
|
||||
if (logLevel >= DEBUG)
|
||||
{
|
||||
printf("Sig : ");
|
||||
for(int i=0; i<(int)sizeof(res); i++)
|
||||
printf("%02x ", res[i]);
|
||||
printf("\n");
|
||||
}
|
||||
|
||||
// Add removed HMAC
|
||||
appendTextElem(hmacParentNode, hmacNode.name(), hmacNode.first_child().value());
|
||||
|
||||
// Add base64 encoded signature
|
||||
ByteArray signature(res, sizeof(res));
|
||||
std::string b64Signature = signature.toBase64();
|
||||
|
||||
appendTextElem(rootNode, "adept:signature", b64Signature);
|
||||
|
||||
pugi::xpath_node node = acsmDoc.select_node("//operatorURL");
|
||||
if (!node)
|
||||
EXCEPTION(FF_NO_OPERATOR_URL, "OperatorURL not found in ACSM document");
|
||||
|
||||
std::string operatorURL = node.node().first_child().value();
|
||||
operatorURL = trim(operatorURL) + "/Fulfill";
|
||||
|
||||
ByteArray replyData = sendRequest(fulfillReq, operatorURL);
|
||||
|
||||
pugi::xml_document fulfillReply;
|
||||
|
||||
fulfillReply.load_string((const char*)replyData.data());
|
||||
|
||||
return new FulfillmentItem(fulfillReply, user);
|
||||
}
|
||||
|
||||
void DRMProcessor::download(FulfillmentItem* item, std::string path)
|
||||
{
|
||||
if (!item)
|
||||
EXCEPTION(DW_NO_ITEM, "No item");
|
||||
|
||||
ByteArray replyData = sendRequest(item->getDownloadURL());
|
||||
|
||||
writeFile(path, replyData);
|
||||
|
||||
GOUROU_LOG(INFO, "Download into " << path);
|
||||
|
||||
std::string rightsStr = item->getRights();
|
||||
|
||||
void* handler = client->zipOpen(path);
|
||||
client->zipWriteFile(handler, "META-INF/rights.xml", rightsStr);
|
||||
client->zipClose(handler);
|
||||
}
|
||||
|
||||
void DRMProcessor::buildSignInRequest(pugi::xml_document& signInRequest,
|
||||
const std::string& adobeID, const std::string& adobePassword,
|
||||
const std::string& authenticationCertificate)
|
||||
{
|
||||
pugi::xml_node decl = signInRequest.append_child(pugi::node_declaration);
|
||||
decl.append_attribute("version") = "1.0";
|
||||
pugi::xml_node signIn = signInRequest.append_child("adept:signIn");
|
||||
signIn.append_attribute("xmlns:adept") = ADOBE_ADEPT_NS;
|
||||
signIn.append_attribute("method") = user->getLoginMethod().c_str();
|
||||
|
||||
unsigned char encryptedSignInData[RSA_KEY_SIZE];
|
||||
const unsigned char* deviceKey = device->getDeviceKey();
|
||||
|
||||
ByteArray _authenticationCertificate = ByteArray::fromBase64(authenticationCertificate);
|
||||
|
||||
// Build buffer <deviceKey> <len username> <username> <len password> <password>
|
||||
ByteArray ar(deviceKey, Device::DEVICE_KEY_SIZE);
|
||||
ar.append((unsigned char)adobeID.length());
|
||||
ar.append(adobeID);
|
||||
ar.append((unsigned char)adobePassword.length());
|
||||
ar.append(adobePassword);
|
||||
|
||||
// Encrypt with authentication certificate (public part)
|
||||
client->RSAPublicEncrypt(_authenticationCertificate.data(),
|
||||
_authenticationCertificate.length(),
|
||||
RSAInterface::RSA_KEY_X509,
|
||||
ar.data(), ar.length(), encryptedSignInData);
|
||||
|
||||
ar = ByteArray(encryptedSignInData, sizeof(encryptedSignInData));
|
||||
appendTextElem(signIn, "adept:signInData", ar.toBase64());
|
||||
|
||||
// Generate Auth key and License Key
|
||||
void* rsaAuth = client->generateRSAKey(RSA_KEY_SIZE_BITS);
|
||||
void* rsaLicense = client->generateRSAKey(RSA_KEY_SIZE_BITS);
|
||||
|
||||
std::string serializedData = serializeRSAPublicKey(rsaAuth);
|
||||
appendTextElem(signIn, "adept:publicAuthKey", serializedData);
|
||||
serializedData = serializeRSAPrivateKey(rsaAuth);
|
||||
appendTextElem(signIn, "adept:encryptedPrivateAuthKey", serializedData.data());
|
||||
|
||||
serializedData = serializeRSAPublicKey(rsaLicense);
|
||||
appendTextElem(signIn, "adept:publicLicenseKey", serializedData.data());
|
||||
serializedData = serializeRSAPrivateKey(rsaLicense);
|
||||
appendTextElem(signIn, "adept:encryptedPrivateLicenseKey", serializedData.data());
|
||||
|
||||
client->destroyRSAHandler(rsaAuth);
|
||||
client->destroyRSAHandler(rsaLicense);
|
||||
}
|
||||
|
||||
void DRMProcessor::signIn(const std::string& adobeID, const std::string& adobePassword)
|
||||
{
|
||||
pugi::xml_document signInRequest;
|
||||
std::string authenticationCertificate = user->getAuthenticationCertificate();
|
||||
|
||||
buildSignInRequest(signInRequest, adobeID, adobePassword, authenticationCertificate);
|
||||
|
||||
GOUROU_LOG(INFO, "SignIn " << adobeID);
|
||||
|
||||
std::string signInURL = user->getProperty("//adept:authURL");
|
||||
signInURL += "/SignInDirect";
|
||||
|
||||
ByteArray credentials = sendRequest(signInRequest, signInURL);
|
||||
|
||||
pugi::xml_document credentialsDoc;
|
||||
if (!credentialsDoc.load_buffer(credentials.data(), credentials.length()))
|
||||
EXCEPTION(SIGN_INVALID_CREDENTIALS, "Invalid credentials reply");
|
||||
|
||||
struct adeptWalker: pugi::xml_tree_walker
|
||||
{
|
||||
void changeName(pugi::xml_node& node)
|
||||
{
|
||||
std::string name = std::string("adept:") + node.name();
|
||||
node.set_name(name.c_str());
|
||||
}
|
||||
|
||||
bool begin(pugi::xml_node& node)
|
||||
{
|
||||
changeName(node);
|
||||
return true;
|
||||
}
|
||||
|
||||
virtual bool for_each(pugi::xml_node& node)
|
||||
{
|
||||
if (node.type() == pugi::node_element)
|
||||
changeName(node);
|
||||
return true; // continue traversal
|
||||
}
|
||||
} adeptWalker;
|
||||
|
||||
pugi::xml_node credentialsNode = credentialsDoc.first_child();
|
||||
|
||||
if (std::string(credentialsNode.name()) != "credentials")
|
||||
EXCEPTION(SIGN_INVALID_CREDENTIALS, "Invalid credentials reply");
|
||||
|
||||
pugi::xpath_node encryptedPrivateLicenseKey = credentialsNode.select_node("encryptedPrivateLicenseKey");
|
||||
const char* privateKeyData = encryptedPrivateLicenseKey.node().first_child().value();
|
||||
ByteArray privateKeyDataStr = ByteArray::fromBase64(privateKeyData);
|
||||
ByteArray privateKey = decryptWithDeviceKey(privateKeyDataStr.data(), privateKeyDataStr.length());
|
||||
credentialsNode.remove_child(encryptedPrivateLicenseKey.node());
|
||||
appendTextElem(credentialsNode, "privateLicenseKey", privateKey.toBase64().data());
|
||||
|
||||
// Add "adept:" prefix to all nodes
|
||||
credentialsNode.remove_attribute("xmlns");
|
||||
credentialsNode.append_attribute("xmlns:adept") = ADOBE_ADEPT_NS;
|
||||
credentialsNode.traverse(adeptWalker);
|
||||
|
||||
appendTextElem(credentialsNode, "adept:authenticationCertificate", authenticationCertificate.data());
|
||||
|
||||
pugi::xml_document activationDoc;
|
||||
user->readActivation(activationDoc);
|
||||
pugi::xml_node activationInfo = activationDoc.select_node("activationInfo").node();
|
||||
activationInfo.append_copy(credentialsNode);
|
||||
|
||||
user->updateActivationFile(activationDoc);
|
||||
}
|
||||
|
||||
void DRMProcessor::buildActivateReq(pugi::xml_document& activateReq)
|
||||
{
|
||||
pugi::xml_node decl = activateReq.append_child(pugi::node_declaration);
|
||||
decl.append_attribute("version") = "1.0";
|
||||
|
||||
pugi::xml_node root = activateReq.append_child("adept:activate");
|
||||
root.append_attribute("xmlns:adept") = ADOBE_ADEPT_NS;
|
||||
root.append_attribute("requestType") = "initial";
|
||||
|
||||
appendTextElem(root, "adept:fingerprint", (*device)["fingerprint"]);
|
||||
appendTextElem(root, "adept:deviceType", (*device)["deviceType"]);
|
||||
appendTextElem(root, "adept:clientOS", (*device)["clientOS"]);
|
||||
appendTextElem(root, "adept:clientLocale", (*device)["clientLocale"]);
|
||||
appendTextElem(root, "adept:clientVersion", (*device)["deviceClass"]);
|
||||
|
||||
pugi::xml_node targetDevice = root.append_child("adept:targetDevice");
|
||||
appendTextElem(targetDevice, "adept:softwareVersion", (*device)["hobbes"]);
|
||||
appendTextElem(targetDevice, "adept:clientOS", (*device)["clientOS"]);
|
||||
appendTextElem(targetDevice, "adept:clientLocale", (*device)["clientLocale"]);
|
||||
appendTextElem(targetDevice, "adept:clientVersion", (*device)["deviceClass"]);
|
||||
appendTextElem(targetDevice, "adept:deviceType", (*device)["deviceType"]);
|
||||
appendTextElem(targetDevice, "adept:fingerprint", (*device)["fingerprint"]);
|
||||
|
||||
/*
|
||||
r4 = tp->time
|
||||
r3 = 0
|
||||
r2 = tm->militime
|
||||
r0 = 0x6f046000
|
||||
r1 = 0x388a
|
||||
|
||||
r3 += high(r4*1000)
|
||||
r2 += low(r4*1000)
|
||||
|
||||
r0 += r2
|
||||
r1 += r3
|
||||
*/
|
||||
struct timeval tv;
|
||||
gettimeofday(&tv, 0);
|
||||
uint32_t nonce32[2] = {0x6f046000, 0x388a};
|
||||
uint64_t bigtime = tv.tv_sec*1000;
|
||||
nonce32[0] += (bigtime & 0xFFFFFFFF) + (tv.tv_usec/1000);
|
||||
nonce32[1] += ((bigtime >> 32) & 0xFFFFFFFF);
|
||||
|
||||
ByteArray nonce((const unsigned char*)&nonce32, sizeof(nonce32));
|
||||
uint32_t tmp = 0;
|
||||
nonce.append((const unsigned char*)&tmp, sizeof(tmp));
|
||||
appendTextElem(root, "adept:nonce", nonce.toBase64().data());
|
||||
|
||||
time_t _time = time(0) + 10*60; // Cur time + 10 minutes
|
||||
struct tm* tm_info = localtime(&_time);
|
||||
char buffer[32];
|
||||
|
||||
strftime(buffer, sizeof(buffer), "%Y-%m-%dT%H:%M:%SZ", tm_info);
|
||||
appendTextElem(root, "adept:expiration", buffer);
|
||||
|
||||
appendTextElem(root, "adept:user", user->getUUID());
|
||||
}
|
||||
|
||||
void DRMProcessor::activateDevice()
|
||||
{
|
||||
pugi::xml_document activateReq;
|
||||
|
||||
GOUROU_LOG(INFO, "Activate device");
|
||||
|
||||
buildActivateReq(activateReq);
|
||||
|
||||
// Compute hash
|
||||
unsigned char sha_out[SHA1_LEN];
|
||||
|
||||
pugi::xml_node root = activateReq.select_node("adept:activate").node();
|
||||
hashNode(root, sha_out);
|
||||
|
||||
// Sign with private key
|
||||
ByteArray RSAKey = ByteArray::fromBase64(user->getPKCS12());
|
||||
unsigned char res[RSA_KEY_SIZE];
|
||||
ByteArray deviceKey(device->getDeviceKey(), Device::DEVICE_KEY_SIZE);
|
||||
|
||||
client->RSAPrivateEncrypt(RSAKey.data(), RSAKey.length(), RSAInterface::RSA_KEY_PKCS12,
|
||||
deviceKey.toBase64().c_str(),
|
||||
sha_out, sizeof(sha_out),
|
||||
res);
|
||||
|
||||
// Add base64 encoded signature
|
||||
ByteArray signature(res, sizeof(res));
|
||||
std::string b64Signature = signature.toBase64();
|
||||
|
||||
root = activateReq.select_node("adept:activate").node();
|
||||
appendTextElem(root, "adept:signature", b64Signature);
|
||||
|
||||
pugi::xml_document activationDoc;
|
||||
user->readActivation(activationDoc);
|
||||
|
||||
std::string activationURL = user->getProperty("//adept:activationURL");
|
||||
activationURL += "/Activate";
|
||||
|
||||
ByteArray reply = sendRequest(activateReq, activationURL);
|
||||
|
||||
pugi::xml_document activationToken;
|
||||
activationToken.load_buffer(reply.data(), reply.length());
|
||||
|
||||
root = activationDoc.select_node("activationInfo").node();
|
||||
root.append_copy(activationToken.first_child());
|
||||
user->updateActivationFile(activationDoc);
|
||||
}
|
||||
|
||||
ByteArray DRMProcessor::encryptWithDeviceKey(const unsigned char* data, unsigned int len)
|
||||
{
|
||||
const unsigned char* deviceKey = device->getDeviceKey();
|
||||
unsigned int outLen;
|
||||
int remain = 0;
|
||||
if ((len % 16))
|
||||
remain = 16 - (len%16);
|
||||
int encrypted_data_len = 16 + len + remain; // IV + data + pad
|
||||
unsigned char* encrypted_data = new unsigned char[encrypted_data_len];
|
||||
|
||||
// Generate IV in front
|
||||
client->randBytes(encrypted_data, 16);
|
||||
|
||||
client->AESEncrypt(CryptoInterface::CHAIN_CBC,
|
||||
deviceKey, 16, encrypted_data, 16,
|
||||
data, len,
|
||||
encrypted_data+16, &outLen);
|
||||
|
||||
ByteArray res(encrypted_data, outLen+16);
|
||||
|
||||
delete[] encrypted_data;
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
/* First 16 bytes of data is IV for CBC chaining */
|
||||
ByteArray DRMProcessor::decryptWithDeviceKey(const unsigned char* data, unsigned int len)
|
||||
{
|
||||
unsigned int outLen;
|
||||
const unsigned char* deviceKey = device->getDeviceKey();
|
||||
unsigned char* decrypted_data = new unsigned char[len-16];
|
||||
|
||||
client->AESDecrypt(CryptoInterface::CHAIN_CBC,
|
||||
deviceKey, 16, data, 16,
|
||||
data+16, len-16,
|
||||
decrypted_data, &outLen);
|
||||
|
||||
ByteArray res(decrypted_data, outLen);
|
||||
|
||||
delete[] decrypted_data;
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
std::string DRMProcessor::serializeRSAPublicKey(void* rsa)
|
||||
{
|
||||
unsigned char* data = 0;
|
||||
unsigned int len;
|
||||
|
||||
client->extractRSAPublicKey(rsa, &data, &len);
|
||||
|
||||
ByteArray res(data, len);
|
||||
|
||||
free(data);
|
||||
|
||||
return res.toBase64();
|
||||
}
|
||||
|
||||
std::string DRMProcessor::serializeRSAPrivateKey(void* rsa)
|
||||
{
|
||||
unsigned char* data = 0;
|
||||
unsigned int len;
|
||||
|
||||
client->extractRSAPrivateKey(rsa, &data, &len);
|
||||
|
||||
ByteArray res = encryptWithDeviceKey(data, len);
|
||||
|
||||
free(data);
|
||||
|
||||
return res.toBase64();
|
||||
}
|
||||
|
||||
int DRMProcessor::getLogLevel() {return (int)gourou::logLevel;}
|
||||
void DRMProcessor::setLogLevel(int logLevel) {gourou::logLevel = (GOUROU_LOG_LEVEL)logLevel;}
|
||||
}
|
1
src/pugixml.cpp
Symbolic link
1
src/pugixml.cpp
Symbolic link
@@ -0,0 +1 @@
|
||||
../lib/pugixml/src/pugixml.cpp
|
198
src/user.cpp
Normal file
198
src/user.cpp
Normal file
@@ -0,0 +1,198 @@
|
||||
/*
|
||||
Copyright 2021 Grégory Soutadé
|
||||
|
||||
This file is part of libgourou.
|
||||
|
||||
libgourou is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Lesser General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
libgourou is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public License
|
||||
along with libgourou. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <libgourou.h>
|
||||
#include <libgourou_common.h>
|
||||
#include <libgourou_log.h>
|
||||
#include <user.h>
|
||||
|
||||
namespace gourou {
|
||||
User::User(DRMProcessor* processor):processor(processor) {}
|
||||
|
||||
User::User(DRMProcessor* processor, const std::string& activationFile):
|
||||
processor(processor), activationFile(activationFile)
|
||||
{
|
||||
parseActivationFile();
|
||||
}
|
||||
|
||||
void User::parseActivationFile(bool throwOnNull)
|
||||
{
|
||||
GOUROU_LOG(DEBUG, "Parse activation file " << activationFile);
|
||||
|
||||
if (!activationDoc.load_file(activationFile.c_str()))
|
||||
{
|
||||
if (throwOnNull)
|
||||
EXCEPTION(USER_INVALID_ACTIVATION_FILE, "Invalid activation file");
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
pkcs12 = gourou::extractTextElem(activationDoc, "//adept:pkcs12", throwOnNull);
|
||||
uuid = gourou::extractTextElem(activationDoc, "//adept:user", throwOnNull);
|
||||
deviceUUID = gourou::extractTextElem(activationDoc, "//device", throwOnNull);
|
||||
deviceFingerprint = gourou::extractTextElem(activationDoc, "//fingerprint", throwOnNull);
|
||||
certificate = gourou::extractTextElem(activationDoc, "//adept:certificate", throwOnNull);
|
||||
authenticationCertificate = gourou::extractTextElem(activationDoc, "//adept:authenticationCertificate", throwOnNull);
|
||||
privateLicenseKey = gourou::extractTextElem(activationDoc, "//adept:privateLicenseKey", throwOnNull);
|
||||
username = gourou::extractTextElem(activationDoc, "//adept:username", throwOnNull);
|
||||
|
||||
pugi::xpath_node xpath_node = activationDoc.select_node("//adept:username");
|
||||
if (xpath_node)
|
||||
loginMethod = xpath_node.node().attribute("method").value();
|
||||
else
|
||||
{
|
||||
if (throwOnNull)
|
||||
EXCEPTION(USER_INVALID_ACTIVATION_FILE, "Invalid activation file");
|
||||
}
|
||||
}
|
||||
catch(gourou::Exception& e)
|
||||
{
|
||||
EXCEPTION(USER_INVALID_ACTIVATION_FILE, "Invalid activation file");
|
||||
}
|
||||
}
|
||||
|
||||
std::string& User::getUUID() { return uuid; }
|
||||
std::string& User::getPKCS12() { return pkcs12; }
|
||||
std::string& User::getDeviceUUID() { return deviceUUID; }
|
||||
std::string& User::getDeviceFingerprint() { return deviceFingerprint; }
|
||||
std::string& User::getUsername() { return username; }
|
||||
std::string& User::getLoginMethod() { return loginMethod; }
|
||||
std::string& User::getCertificate() { return certificate; }
|
||||
std::string& User::getAuthenticationCertificate() { return authenticationCertificate; }
|
||||
std::string& User::getPrivateLicenseKey() { return privateLicenseKey; }
|
||||
|
||||
void User::readActivation(pugi::xml_document& doc)
|
||||
{
|
||||
if (!doc.load_file(activationFile.c_str()))
|
||||
EXCEPTION(USER_INVALID_ACTIVATION_FILE, "Invalid activation file");
|
||||
}
|
||||
|
||||
void User::updateActivationFile(const char* data)
|
||||
{
|
||||
GOUROU_LOG(INFO, "Update Activation file : " << std::endl << data);
|
||||
|
||||
writeFile(activationFile, (unsigned char*)data, strlen(data));
|
||||
|
||||
parseActivationFile(false);
|
||||
}
|
||||
|
||||
void User::updateActivationFile(const pugi::xml_document& doc)
|
||||
{
|
||||
StringXMLWriter xmlWriter;
|
||||
doc.save(xmlWriter, " ");
|
||||
updateActivationFile(xmlWriter.getResult().c_str());
|
||||
}
|
||||
|
||||
std::string User::getProperty(const std::string property)
|
||||
{
|
||||
pugi::xpath_node xpathRes = activationDoc.select_node(property.c_str());
|
||||
if (!xpathRes)
|
||||
EXCEPTION(USER_NO_PROPERTY, "Property " << property << " not found in activation.xml");
|
||||
|
||||
std::string res = xpathRes.node().first_child().value();
|
||||
return trim(res);
|
||||
}
|
||||
|
||||
User* User::createUser(DRMProcessor* processor, const std::string& dirName, const std::string& ACSServer)
|
||||
{
|
||||
struct stat _stat;
|
||||
|
||||
if (stat(dirName.c_str(), &_stat) != 0)
|
||||
{
|
||||
if (mkdir_p(dirName.c_str(), S_IRWXU))
|
||||
EXCEPTION(USER_MKPATH, "Unable to create " << dirName)
|
||||
}
|
||||
|
||||
User* user = new User(processor);
|
||||
bool doUpdate = false;
|
||||
|
||||
user->activationFile = dirName + "/activation.xml";
|
||||
user->parseActivationFile(false);
|
||||
|
||||
pugi::xpath_node nodeActivationInfo = user->activationDoc.select_node("activation_info");
|
||||
pugi::xpath_node nodeActivationServiceInfo = nodeActivationInfo.node().select_node("adept:activationServiceInfo");
|
||||
pugi::xml_node activationInfo;
|
||||
pugi::xml_node activationServiceInfo;
|
||||
|
||||
if (nodeActivationInfo && nodeActivationServiceInfo)
|
||||
{
|
||||
GOUROU_LOG(DEBUG, "Read previous activation configuration");
|
||||
activationInfo = nodeActivationInfo.node();
|
||||
activationServiceInfo = nodeActivationServiceInfo.node();
|
||||
}
|
||||
else
|
||||
{
|
||||
GOUROU_LOG(DEBUG, "Create new activation");
|
||||
|
||||
user->activationDoc.reset();
|
||||
|
||||
pugi::xml_node decl = user->activationDoc.append_child(pugi::node_declaration);
|
||||
decl.append_attribute("version") = "1.0";
|
||||
activationInfo = user->activationDoc.append_child("activationInfo");
|
||||
activationInfo.append_attribute("xmlns") = ADOBE_ADEPT_NS;
|
||||
activationServiceInfo = activationInfo.append_child("adept:activationServiceInfo");
|
||||
activationServiceInfo.append_attribute("xmlns:adept") = ADOBE_ADEPT_NS;
|
||||
|
||||
// Go to activation Service Info
|
||||
std::string activationURL = ACSServer + "/ActivationServiceInfo";
|
||||
ByteArray activationServiceInfoReply = processor->sendRequest(activationURL);
|
||||
pugi::xml_document docActivationServiceInfo;
|
||||
docActivationServiceInfo.load_buffer(activationServiceInfoReply.data(),
|
||||
activationServiceInfoReply.length());
|
||||
|
||||
pugi::xpath_node path = docActivationServiceInfo.select_node("//authURL");
|
||||
appendTextElem(activationServiceInfo, "adept:authURL", path.node().first_child().value());
|
||||
path = docActivationServiceInfo.select_node("//userInfoURL");
|
||||
appendTextElem(activationServiceInfo, "adept:userInfoURL", path.node().first_child().value());
|
||||
appendTextElem(activationServiceInfo, "adept:activationURL", ACSServer);
|
||||
path = docActivationServiceInfo.select_node("//certificate");
|
||||
appendTextElem(activationServiceInfo, "adept:certificate", path.node().first_child().value());
|
||||
doUpdate = true;
|
||||
}
|
||||
|
||||
pugi::xpath_node nodeAuthenticationCertificate = activationServiceInfo.select_node("adept:authenticationCertificate");
|
||||
|
||||
if (!nodeAuthenticationCertificate)
|
||||
{
|
||||
GOUROU_LOG(DEBUG, "Create new activation, authentication part");
|
||||
|
||||
pugi::xpath_node xpathRes = activationServiceInfo.select_node("adept:authURL");
|
||||
if (!xpathRes)
|
||||
EXCEPTION(USER_NO_AUTHENTICATION_URL, "No authentication URL");
|
||||
|
||||
std::string authenticationURL = xpathRes.node().first_child().value();
|
||||
authenticationURL = trim(authenticationURL) + "/AuthenticationServiceInfo";
|
||||
|
||||
// Go to authentication Service Info
|
||||
ByteArray authenticationServiceInfo = processor->sendRequest(authenticationURL);
|
||||
pugi::xml_document docAuthenticationServiceInfo;
|
||||
docAuthenticationServiceInfo.load_buffer(authenticationServiceInfo.data(), authenticationServiceInfo.length());
|
||||
pugi::xpath_node path = docAuthenticationServiceInfo.select_node("//certificate");
|
||||
appendTextElem(activationServiceInfo, "adept:authenticationCertificate", path.node().first_child().value());
|
||||
doUpdate = true;
|
||||
}
|
||||
|
||||
if (doUpdate)
|
||||
user->updateActivationFile(user->activationDoc);
|
||||
|
||||
|
||||
return user;
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user