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