Initial commit

This commit is contained in:
2021-07-03 21:57:53 +02:00
commit d5ce4d625e
25 changed files with 4153 additions and 0 deletions

173
src/bytearray.cpp Normal file
View 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
View 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
View 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
View 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
View File

@@ -0,0 +1 @@
../lib/pugixml/src/pugixml.cpp

198
src/user.cpp Normal file
View 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;
}
}