/* 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 . */ #include #include #include #include #include #include #include #include #define LOCAL_ADEPT_DIR "./.adept" #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 = LG_LOG_WARN; const std::string DRMProcessor::VERSION = LIBGOUROU_VERSION; DRMProcessor::DRMProcessor(DRMProcessorClient* client):client(client), device(0), user(0) { if (!client) EXCEPTION(GOUROU_INVALID_CLIENT, "DRMProcessorClient is NULL"); } 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, std::string dirName, const std::string& hobbes, const std::string& ACSServer) { DRMProcessor* processor = new DRMProcessor(client); if (dirName == "") dirName = getDefaultAdeptDir(); Device* device = Device::createDevice(processor, dirName, hobbes, randomSerial); processor->device = device; 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 >= LG_LOG_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; idigestUpdate(sha_ctx, (unsigned char*)&c, 1); if (logLevel >= LG_LOG_TRACE) printf("%c", c); } if (logLevel >= LG_LOG_TRACE) printf("\n"); } void DRMProcessor::pushTag(void* sha_ctx, uint8_t tag) { client->digestUpdate(sha_ctx, &tag, sizeof(tag)); if (logLevel >= LG_LOG_TRACE) printf("%02x ", tag); } void DRMProcessor::hashNode(const pugi::xml_node& root, void *sha_ctx, std::map nsHash) { switch(root.type()) { case pugi::node_element: { std::string name = root.name(); // 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(); // Don't break here because we may multiple xmlns definitions // break; } } // Remove namespace from tag // If we have a namespace for the first time, put it to hash if (name.find(':') != std::string::npos) { size_t nsIndex = name.find(':'); std::string nodeNS = name.substr(0, nsIndex); 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); std::vector attributes; pugi::xml_attribute attr; for (attr = root.first_attribute(); attr; attr = attr.next_attribute()) { if (std::string(attr.name()).find("xmlns") != std::string::npos) continue; attributes.push_back(attr.name()); } // Attributes must be handled in alphabetical order std::sort(attributes.begin(), attributes.end()); std::vector::iterator attributesIt; for(attributesIt = attributes.begin(); attributesIt != attributes.end(); attributesIt++) { attr = root.attribute(attributesIt->c_str()); pushTag(sha_ctx, ASN_ATTRIBUTE); pushString(sha_ctx, ""); pushString(sha_ctx, attr.name()); pushString(sha_ctx, attr.value()); } pushTag(sha_ctx, ASN_CHILD); for (pugi::xml_node child : root.children()) hashNode(child, sha_ctx, nsHash); pushTag(sha_ctx, ASN_END_TAG); break; } case pugi::node_pcdata: { std::string trimmed = root.value(); trimmed = trim(trimmed); if (trimmed.length()) { pushTag(sha_ctx, ASN_TEXT); pushString(sha_ctx, trimmed); } break; } default: break; } } void DRMProcessor::hashNode(const pugi::xml_node& root, unsigned char* sha_out) { void* sha_ctx = client->createDigest("SHA1"); std::map nsHash; hashNode(root, sha_ctx, nsHash); client->digestFinalize(sha_ctx, sha_out); dumpBuffer(gourou::LG_LOG_DEBUG, "\nSHA OUT : ", sha_out, SHA1_LEN); } void DRMProcessor::signNode(pugi::xml_node& rootNode) { // Compute hash unsigned char sha_out[SHA1_LEN]; hashNode(rootNode, sha_out); // Sign with private key unsigned char res[RSA_KEY_SIZE]; ByteArray deviceKey(device->getDeviceKey(), Device::DEVICE_KEY_SIZE); std::string pkcs12 = user->getPKCS12(); ByteArray privateRSAKey = ByteArray::fromBase64(pkcs12); client->RSAPrivateEncrypt(privateRSAKey.data(), privateRSAKey.length(), RSAInterface::RSA_KEY_PKCS12, deviceKey.toBase64().data(), sha_out, sizeof(sha_out), res); dumpBuffer(gourou::LG_LOG_DEBUG, "Sig : ", res, sizeof(res)); std::string signature = ByteArray(res, sizeof(res)).toBase64(); appendTextElem(rootNode, "adept:signature", signature); } void DRMProcessor::addNonce(pugi::xml_node& root) { /* r4 = tp->time r3 = 0 r2 = tm->militime r0 = 0x6f046000 r1 = 0x388a r3 += high(r4*1000) r2 += low(r4*1000) r0 += r2 r1 += r3 */ struct timeval tv; gettimeofday(&tv, 0); uint32_t nonce32[2] = {0x6f046000, 0x388a}; #ifdef STATIC_NONCE uint64_t bigtime = 0xAA001122BBCCAAULL; #else uint64_t bigtime = tv.tv_sec*1000; #endif nonce32[0] += (bigtime & 0xFFFFFFFF) + (tv.tv_usec/1000); nonce32[1] += ((bigtime >> 32) & 0xFFFFFFFF); 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 = gmtime(&_time); char buffer[32]; strftime(buffer, sizeof(buffer), "%Y-%m-%dT%H:%M:%SZ", tm_info); appendTextElem(root, "adept:expiration", buffer); } ByteArray DRMProcessor::sendRequest(const std::string& URL, const std::string& POSTdata, const char* contentType, std::map* responseHeaders, int fd, bool resume) { if (contentType == 0) contentType = ""; std::string reply = client->sendHTTPRequest(URL, POSTdata, contentType, responseHeaders, fd, resume); if (fd) return ByteArray(); 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::buildAuthRequest(pugi::xml_document& authReq) { pugi::xml_node decl = authReq.append_child(pugi::node_declaration); decl.append_attribute("version") = "1.0"; pugi::xml_node root = authReq.append_child("adept:credentials"); root.append_attribute("xmlns:adept") = ADOBE_ADEPT_NS; appendTextElem(root, "adept:user", user->getUUID()); ByteArray deviceKey(device->getDeviceKey(), Device::DEVICE_KEY_SIZE); unsigned char* pkcs12 = 0; unsigned int pkcs12Length; ByteArray pkcs12Cert = ByteArray::fromBase64(user->getPKCS12()); client->extractCertificate(pkcs12Cert.data(), pkcs12Cert.length(), RSAInterface::RSA_KEY_PKCS12, deviceKey.toBase64().data(), &pkcs12, &pkcs12Length); ByteArray privateCertificate(pkcs12, pkcs12Length); free(pkcs12); appendTextElem(root, "adept:certificate", privateCertificate.toBase64()); appendTextElem(root, "adept:licenseCertificate", user->getProperty("//adept:licenseCertificate")); appendTextElem(root, "adept:authenticationCertificate", user->getProperty("//adept:authenticationCertificate")); } void DRMProcessor::buildInitLicenseServiceRequest(pugi::xml_document& initLicReq, std::string operatorURL) { pugi::xml_node decl = initLicReq.append_child(pugi::node_declaration); decl.append_attribute("version") = "1.0"; pugi::xml_node root = initLicReq.append_child("adept:licenseServiceRequest"); root.append_attribute("xmlns:adept") = ADOBE_ADEPT_NS; root.append_attribute("identity") = "user"; appendTextElem(root, "adept:operatorURL", operatorURL); addNonce(root); appendTextElem(root, "adept:user", user->getUUID()); signNode(root); } void DRMProcessor::doOperatorAuth(std::string operatorURL) { pugi::xml_document authReq; buildAuthRequest(authReq); std::string authURL = operatorURL; unsigned int fulfillPos = authURL.rfind("Fulfill"); if (fulfillPos == (authURL.size() - (sizeof("Fulfill")-1))) authURL = authURL.substr(0, fulfillPos-1); ByteArray replyData = sendRequest(authReq, authURL + "/Auth"); pugi::xml_document initLicReq; std::string activationURL = user->getProperty("//adept:activationURL"); buildInitLicenseServiceRequest(initLicReq, authURL); sendRequest(initLicReq, activationURL + "/InitLicenseService"); } void DRMProcessor::operatorAuth(std::string operatorURL) { pugi::xpath_node_set operatorList = user->getProperties("//adept:operatorURL"); for (pugi::xpath_node_set::const_iterator operatorIt = operatorList.begin(); operatorIt != operatorList.end(); ++operatorIt) { std::string value = operatorIt->node().first_child().value(); if (trim(value) == operatorURL) { GOUROU_LOG(DEBUG, "Already authenticated to operator " << operatorURL); return; } } doOperatorAuth(operatorURL); } 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()); } void DRMProcessor::fetchLicenseServiceCertificate(const std::string& licenseURL, const std::string& operatorURL) { if (user->getLicenseServiceCertificate(licenseURL) != "") return; std::string licenseServiceInfoReq = operatorURL + "/LicenseServiceInfo?licenseURL=" + licenseURL; ByteArray replyData; replyData = sendRequest(licenseServiceInfoReq); pugi::xml_document licenseServicesDoc; licenseServicesDoc.load_buffer(replyData.data(), replyData.length()); // Add new license certificate pugi::xml_document activationDoc; user->readActivation(activationDoc); pugi::xml_node root; pugi::xpath_node xpathRes = activationDoc.select_node("//adept:licenseServices"); // Create adept:licenseServices if it doesn't exists if (!xpathRes) { xpathRes = activationDoc.select_node("/activationInfo"); root = xpathRes.node(); root = root.append_child("adept:licenseServices"); root.append_attribute("xmlns:adept") = ADOBE_ADEPT_NS; } else root = xpathRes.node(); root = root.append_child("adept:licenseServiceInfo"); std::string certificate = extractTextElem(licenseServicesDoc, "/licenseServiceInfo/certificate"); appendTextElem(root, "adept:licenseURL", licenseURL); appendTextElem(root, "adept:certificate", certificate); // Add new operatorURL to list xpathRes = activationDoc.select_node("//adept:operatorURLList"); // Create adept:operatorURLList if it doesn't exists if (!xpathRes) { xpathRes = activationDoc.select_node("/activationInfo"); root = xpathRes.node(); root = root.append_child("adept:operatorURLList"); root.append_attribute("xmlns:adept") = ADOBE_ADEPT_NS; appendTextElem(root, "adept:user", user->getUUID()); } else root = xpathRes.node(); appendTextElem(root, "adept:operatorURL", operatorURL); user->updateActivationFile(activationDoc); } FulfillmentItem* DRMProcessor::fulfill(const std::string& ACSMFile, bool notify) { if (!user->getPKCS12().length()) EXCEPTION(FF_NOT_ACTIVATED, "Device not activated"); pugi::xml_document acsmDoc; if (!acsmDoc.load_file(ACSMFile.c_str(), pugi::parse_ws_pcdata_single|pugi::parse_escapes, pugi::encoding_utf8)) EXCEPTION(FF_INVALID_ACSM_FILE, "Invalid ACSM file " << ACSMFile); // Could be an server internal error pugi::xml_node rootNode = acsmDoc.first_child(); if (std::string(rootNode.name()) == "error") { EXCEPTION(FF_SERVER_INTERNAL_ERROR, rootNode.attribute("data").value()); } GOUROU_LOG(INFO, "Fulfill " << ACSMFile); // Build req file pugi::xml_document fulfillReq; buildFulfillRequest(acsmDoc, fulfillReq); pugi::xpath_node root = fulfillReq.select_node("//adept:fulfill"); 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); signNode(rootNode); // Add removed HMAC appendTextElem(hmacParentNode, hmacNode.name(), hmacNode.first_child().value()); pugi::xpath_node node = acsmDoc.select_node("//operatorURL"); if (!node) EXCEPTION(FF_NO_OPERATOR_URL, "OperatorURL not found in ACSM document"); std::string operatorURL = node.node().first_child().value(); operatorURL = trim(operatorURL); std::string fulfillURL = operatorURL + "/Fulfill"; operatorAuth(fulfillURL); ByteArray replyData; try { replyData = sendRequest(fulfillReq, fulfillURL); } catch (gourou::Exception& e) { /* Operator requires authentication even if it's already in our operator list */ std::string errorMsg(e.what()); if (e.getErrorCode() == GOUROU_ADEPT_ERROR && errorMsg.find("E_ADEPT_DISTRIBUTOR_AUTH") != std::string::npos) { doOperatorAuth(fulfillURL); replyData = sendRequest(fulfillReq, fulfillURL); } else { throw e; } } pugi::xml_document fulfillReply; fulfillReply.load_string((const char*)replyData.data()); std::string licenseURL = extractTextElem(fulfillReply, "//licenseToken/licenseURL"); fetchLicenseServiceCertificate(licenseURL, operatorURL); FulfillmentItem* item = new FulfillmentItem(fulfillReply, user); if (notify) notifyServer(fulfillReply); return item; } DRMProcessor::ITEM_TYPE DRMProcessor::download(FulfillmentItem* item, std::string path, bool resume) { ITEM_TYPE res = EPUB; if (!item) EXCEPTION(DW_NO_ITEM, "No item"); std::map headers; int fd = createNewFile(path, !resume); sendRequest(item->getDownloadURL(), "", 0, &headers, fd, resume); close(fd); GOUROU_LOG(INFO, "Download into " << path); ByteArray rightsStr(item->getRights()); if (item->getMetadata("format").find("application/pdf") != std::string::npos) res = PDF; if (headers.count("Content-Type") && headers["Content-Type"].find("application/pdf") != std::string::npos) res = PDF; if (res == EPUB) { void* handler = client->zipOpen(path); client->zipWriteFile(handler, "META-INF/rights.xml", rightsStr); client->zipClose(handler); } else if (res == PDF) { uPDFParser::Parser parser; bool EBXHandlerFound = false; try { GOUROU_LOG(DEBUG, "Parse PDF"); parser.parse(path); } catch(std::invalid_argument& e) { GOUROU_LOG(ERROR, "Invalid PDF"); return res; } std::vector objects = parser.objects(); std::vector::reverse_iterator it; for(it = objects.rbegin(); it != objects.rend(); it++) { // Update EBX_HANDLER with rights if ((*it)->hasKey("Filter") && (**it)["Filter"]->str() == "/EBX_HANDLER") { EBXHandlerFound = true; uPDFParser::Object* ebx = (*it)->clone(); (*ebx)["ADEPT_ID"] = new uPDFParser::String(item->getResource()); (*ebx)["EBX_BOOKID"] = new uPDFParser::String(item->getResource()); ByteArray zipped; client->deflate(rightsStr, zipped); (*ebx)["ADEPT_LICENSE"] = new uPDFParser::String(zipped.toBase64()); parser.addObject(ebx); break; } } if (EBXHandlerFound) parser.write(path, true); else { EXCEPTION(DW_NO_EBX_HANDLER, "EBX_HANDLER not found"); } } return res; } void DRMProcessor::buildSignInRequest(pugi::xml_document& signInRequest, const std::string& adobeID, const std::string& adobePassword, const std::string& authenticationCertificate) { pugi::xml_node decl = signInRequest.append_child(pugi::node_declaration); decl.append_attribute("version") = "1.0"; pugi::xml_node signIn = signInRequest.append_child("adept:signIn"); signIn.append_attribute("xmlns:adept") = ADOBE_ADEPT_NS; std::string loginMethod = user->getLoginMethod(); if (adobeID == "anonymous") signIn.append_attribute("method") = "anonymous"; else if (loginMethod.size()) signIn.append_attribute("method") = loginMethod.c_str(); else signIn.append_attribute("method") = "AdobeID"; unsigned char encryptedSignInData[RSA_KEY_SIZE]; const unsigned char* deviceKey = device->getDeviceKey(); ByteArray _authenticationCertificate = ByteArray::fromBase64(authenticationCertificate); // Build buffer ByteArray ar(deviceKey, Device::DEVICE_KEY_SIZE); ar.append((unsigned char)adobeID.length()); ar.append(adobeID); ar.append((unsigned char)adobePassword.length()); ar.append(adobePassword); // 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"]); addNonce(root); appendTextElem(root, "adept:user", user->getUUID()); } void DRMProcessor::activateDevice() { pugi::xml_document activateReq; GOUROU_LOG(INFO, "Activate device"); buildActivateReq(activateReq); pugi::xml_node root = activateReq.select_node("adept:activate").node(); signNode(root); 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); } void DRMProcessor::buildReturnReq(pugi::xml_document& returnReq, const std::string& loanID, const std::string& operatorURL) { pugi::xml_node decl = returnReq.append_child(pugi::node_declaration); decl.append_attribute("version") = "1.0"; pugi::xml_node root = returnReq.append_child("adept:loanReturn"); root.append_attribute("xmlns:adept") = ADOBE_ADEPT_NS; appendTextElem(root, "adept:user", user->getUUID()); appendTextElem(root, "adept:device", user->getDeviceUUID()); appendTextElem(root, "adept:loan", loanID); addNonce(root); signNode(root); } std::string DRMProcessor::getDefaultAdeptDir(void) { #ifndef DEFAULT_ADEPT_DIR const char* home = getenv("HOME"); if (home) return home + std::string("/.config/adept/"); else { const char* user = getenv("USER"); if (user && user[0]) { return std::string("/home/") + user + std::string("/.config/adept/"); } else return LOCAL_ADEPT_DIR; } #else return DEFAULT_ADEPT_DIR "/"; #endif } void DRMProcessor::returnLoan(const std::string& loanID, const std::string& operatorURL, bool notify) { pugi::xml_document returnReq; GOUROU_LOG(INFO, "Return loan " << loanID); buildReturnReq(returnReq, loanID, operatorURL); ByteArray replyData = sendRequest(returnReq, operatorURL + "/LoanReturn"); pugi::xml_document fulfillReply; fulfillReply.load_string((const char*)replyData.data()); if (notify) notifyServer(fulfillReply); } void DRMProcessor::buildNotifyReq(pugi::xml_document& returnReq, pugi::xml_node& body) { pugi::xml_node decl = returnReq.append_child(pugi::node_declaration); decl.append_attribute("version") = "1.0"; pugi::xml_node root = returnReq.append_child("adept:notification"); root.append_attribute("xmlns:adept") = ADOBE_ADEPT_NS; appendTextElem(root, "adept:user", user->getUUID()); appendTextElem(root, "adept:device", user->getDeviceUUID()); body = root.append_copy(body); body.append_attribute("xmlns") = ADOBE_ADEPT_NS; addNonce(root); signNode(root); } void DRMProcessor::notifyServer(pugi::xml_node& notifyRoot) { std::string notifyUrl = extractTextElem(notifyRoot, "//notifyURL", false); pugi::xml_node notifyBody = getNode(notifyRoot, "//body", false); if (notifyUrl == "") { GOUROU_LOG(INFO, "No notify URL"); return; } if (!notifyBody) { GOUROU_LOG(INFO, "No notify body"); return; } pugi::xml_document notifyReq; buildNotifyReq(notifyReq, notifyBody); sendRequest(notifyReq, notifyUrl); } void DRMProcessor::notifyServer(pugi::xml_document& fulfillReply) { pugi::xpath_node_set notifySet = fulfillReply.select_nodes("//notify"); if (notifySet.empty()) { GOUROU_LOG(DEBUG, "No notify request"); return; } for (pugi::xpath_node_set::const_iterator it = notifySet.begin(); it != notifySet.end(); ++it) { pugi::xml_node notifyRoot = it->node(); notifyServer(notifyRoot); } } ByteArray DRMProcessor::encryptWithDeviceKey(const unsigned char* data, unsigned int len) { const unsigned char* deviceKey = device->getDeviceKey(); unsigned int outLen; int remain = 0; if ((len % 16)) remain = 16 - (len%16); int encrypted_data_len = 16 + len + remain; // IV + data + pad unsigned char* encrypted_data = new unsigned char[encrypted_data_len]; // Generate IV in front client->randBytes(encrypted_data, 16); client->encrypt(CryptoInterface::ALGO_AES, CryptoInterface::CHAIN_CBC, deviceKey, 16, encrypted_data, 16, data, len, encrypted_data+16, &outLen); 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->decrypt(CryptoInterface::ALGO_AES, 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(); } void DRMProcessor::exportPrivateLicenseKey(std::string path) { int fd = open(path.c_str(), O_CREAT|O_TRUNC|O_WRONLY, S_IRWXU); int ret; if (fd <= 0) EXCEPTION(GOUROU_FILE_ERROR, "Unable to open " << path); ByteArray privateLicenseKey = ByteArray::fromBase64(user->getPrivateLicenseKey()); /* In adobekey.py, we get base64 decoded data [26:] */ ret = write(fd, privateLicenseKey.data()+26, privateLicenseKey.length()-26); close(fd); if (ret != (int)(privateLicenseKey.length()-26)) { EXCEPTION(gourou::GOUROU_FILE_ERROR, "Error writing " << path); } } int DRMProcessor::getLogLevel() {return (int)gourou::logLevel;} void DRMProcessor::setLogLevel(int logLevel) {gourou::logLevel = (GOUROU_LOG_LEVEL)logLevel;} /** * RSA Key can be over encrypted with AES128-CBC if keyType attribute is set * remainder = keyType % 16 * Key = SHA256(keyType)[remainder*2:remainder*2+(16-remainder)] || SHA256(keyType)[16-remainder:16] * IV = DeviceID ^ FulfillmentId ^ VoucherId * * @return Base64 encoded decrypted key */ std::string DRMProcessor::encryptedKeyFirstPass(pugi::xml_document& rightsDoc, const std::string& encryptedKey, const std::string& keyType) { unsigned char digest[32], key[16], iv[16]; unsigned int dataOutLength; std::string id; client->digest("SHA256", (unsigned char*)keyType.c_str(), keyType.size(), digest); dumpBuffer(gourou::LG_LOG_DEBUG, "SHA of KeyType : ", digest, sizeof(digest)); long nonce = std::stol(keyType); int remainder = nonce % 16; memcpy(key, &digest[remainder*2], 16-remainder); memcpy(&key[16-remainder], &digest[remainder], remainder); id = extractTextElem(rightsDoc, "/adept:rights/licenseToken/device"); if (id == "") EXCEPTION(DRM_ERR_ENCRYPTION_KEY_FP, "Device id not found in rights.xml"); ByteArray deviceId = ByteArray::fromHex(extractIdFromUUID(id)); unsigned char* _deviceId = deviceId.data(); id = extractTextElem(rightsDoc, "/adept:rights/licenseToken/fulfillment"); if (id == "") EXCEPTION(DRM_ERR_ENCRYPTION_KEY_FP, "Fulfillment id not found in rights.xml"); ByteArray fulfillmentId = ByteArray::fromHex(extractIdFromUUID(id)); unsigned char* _fulfillmentId = fulfillmentId.data(); id = extractTextElem(rightsDoc, "/adept:rights/licenseToken/voucher"); if (id == "") EXCEPTION(DRM_ERR_ENCRYPTION_KEY_FP, "Voucher id not found in rights.xml"); ByteArray voucherId = ByteArray::fromHex(extractIdFromUUID(id)); unsigned char* _voucherId = voucherId.data(); if (deviceId.size() < sizeof(iv) || fulfillmentId.size() < sizeof(iv) || voucherId.size() < sizeof(iv)) EXCEPTION(DRM_ERR_ENCRYPTION_KEY_FP, "One id has a bad length"); for(unsigned int i=0; idecrypt(CryptoInterface::ALGO_AES, CryptoInterface::CHAIN_CBC, (const unsigned char*)key, (unsigned int)sizeof(key), (const unsigned char*)iv, (unsigned int)sizeof(iv), (const unsigned char*)arrayEncryptedKey.data(), arrayEncryptedKey.size(), (unsigned char*)clearRSAKey, &dataOutLength); dumpBuffer(gourou::LG_LOG_DEBUG, "\nDecrypted key : ", clearRSAKey, dataOutLength); /* Last block could be 0x10*16 which is OpenSSL padding, remove it if it's the case */ bool skipLastLine = true; for(unsigned int i=dataOutLength-16; iuser->getUUID() != user) { EXCEPTION(DRM_INVALID_USER, "This book has been downloaded for another user (" << user << ")"); } std::string encryptedKey = extractTextElem(rightsDoc, "/adept:rights/licenseToken/encryptedKey"); std::string keyType = extractTextAttribute(rightsDoc, "/adept:rights/licenseToken/encryptedKey", "keyType", false); if (keyType != "") encryptedKey = encryptedKeyFirstPass(rightsDoc, encryptedKey, keyType); if (encryptedKey.size() != 172) EXCEPTION(DRM_INVALID_KEY_SIZE, "Invalid encrypted key size (" << encryptedKey.size() << "). DRM version not supported"); ByteArray arrayEncryptedKey = ByteArray::fromBase64(encryptedKey); std::string privateKeyData = this->user->getPrivateLicenseKey(); ByteArray privateRSAKey = ByteArray::fromBase64(privateKeyData); dumpBuffer(gourou::LG_LOG_DEBUG, "To decrypt : ", arrayEncryptedKey.data(), arrayEncryptedKey.length()); client->RSAPrivateDecrypt(privateRSAKey.data(), privateRSAKey.length(), RSAInterface::RSA_KEY_PKCS8, "", arrayEncryptedKey.data(), arrayEncryptedKey.length(), rsaKey); dumpBuffer(gourou::LG_LOG_DEBUG, "Decrypted : ", rsaKey, sizeof(rsaKey)); if (rsaKey[0] != 0x00 || rsaKey[1] != 0x02 || rsaKey[RSA_KEY_SIZE-16-1] != 0x00) EXCEPTION(DRM_ERR_ENCRYPTION_KEY, "Unable to retrieve encryption key"); memcpy(decryptedKey, &rsaKey[sizeof(rsaKey)-16], 16); } else { GOUROU_LOG(DEBUG, "Use provided encryption key"); if (encryptionKeySize != 16) EXCEPTION(DRM_ERR_ENCRYPTION_KEY, "Provided encryption key must be 16 bytes"); memcpy(decryptedKey, encryptionKey, encryptionKeySize); } } void DRMProcessor::removeEPubDRM(const std::string& filenameIn, const std::string& filenameOut, const unsigned char* encryptionKey, unsigned encryptionKeySize) { ByteArray zipData; bool removeEncryptionXML = true; void* zipHandler = client->zipOpen(filenameOut); client->zipReadFile(zipHandler, "META-INF/rights.xml", zipData); pugi::xml_document rightsDoc; rightsDoc.load_string((const char*)zipData.data()); unsigned char decryptedKey[16]; decryptADEPTKey(rightsDoc, decryptedKey, encryptionKey, encryptionKeySize); client->zipReadFile(zipHandler, "META-INF/encryption.xml", zipData); pugi::xml_document encryptionDoc; encryptionDoc.load_string((const char*)zipData.data()); pugi::xpath_node_set nodeSet = encryptionDoc.select_nodes("//EncryptedData"); for (pugi::xpath_node_set::const_iterator it = nodeSet.begin(); it != nodeSet.end(); ++it) { pugi::xml_node encryptionMethod = it->node().child("EncryptionMethod"); pugi::xml_node cipherReference = it->node().child("CipherData").child("CipherReference"); std::string encryptionType = encryptionMethod.attribute("Algorithm").value(); std::string encryptedFile = cipherReference.attribute("URI").value(); if (encryptionType == "") { EXCEPTION(DRM_MISSING_PARAMETER, "Missing Algorithm attribute in encryption.xml"); } else if (encryptionType == "http://www.w3.org/2001/04/xmlenc#aes128-cbc") { if (encryptedFile == "") { EXCEPTION(DRM_MISSING_PARAMETER, "Missing URI attribute in encryption.xml"); } GOUROU_LOG(DEBUG, "Encrypted file " << encryptedFile); client->zipReadFile(zipHandler, encryptedFile, zipData, false); unsigned char* _data = zipData.data(); ByteArray clearData(zipData.length()-16+1, true); /* Reserve 1 byte for 'Z' */ unsigned char* _clearData = clearData.data(); gourou::ByteArray inflateData(true); unsigned int dataOutLength; client->decrypt(CryptoInterface::ALGO_AES, CryptoInterface::CHAIN_CBC, decryptedKey, sizeof(decryptedKey), /* Key */ _data, 16, /* IV */ &_data[16], zipData.length()-16, _clearData, &dataOutLength); // Add 'Z' at the end, done in ineptepub.py _clearData[dataOutLength] = 'Z'; clearData.resize(dataOutLength+1); try { client->inflate(clearData, inflateData); client->zipWriteFile(zipHandler, encryptedFile, inflateData); } catch(gourou::Exception& e) { if (e.getErrorCode() == CLIENT_ZIP_ERROR) { GOUROU_LOG(ERROR, e.what() << std::endl << "Skip file " << encryptedFile); } else throw e; } it->node().parent().remove_child(it->node()); } else { GOUROU_LOG(WARN, "Unsupported encryption algorithm " << encryptionType << ", for file " << encryptedFile); removeEncryptionXML = false; } } client->zipDeleteFile(zipHandler, "META-INF/rights.xml"); if (removeEncryptionXML) client->zipDeleteFile(zipHandler, "META-INF/encryption.xml"); else { StringXMLWriter xmlWriter; encryptionDoc.save(xmlWriter, " "); std::string xmlStr = xmlWriter.getResult(); ByteArray ba(xmlStr); client->zipWriteFile(zipHandler, "META-INF/encryption.xml", ba); } client->zipClose(zipHandler); } void DRMProcessor::generatePDFObjectKey(int version, const unsigned char* masterKey, unsigned int masterKeyLength, int objectId, int objectGenerationNumber, unsigned char* keyOut) { switch(version) { case 4: ByteArray toHash(masterKey, masterKeyLength); uint32_t _objectId = objectId; uint32_t _objectGenerationNumber = objectGenerationNumber; toHash.append((const unsigned char*)&_objectId, 3); // Fill 3 bytes toHash.append((const unsigned char*)&_objectGenerationNumber, 2); // Fill 2 bytes client->digest("md5", toHash.data(), toHash.length(), keyOut); break; } } void DRMProcessor::removePDFDRM(const std::string& filenameIn, const std::string& filenameOut, const unsigned char* encryptionKey, unsigned encryptionKeySize) { uPDFParser::Parser parser; bool EBXHandlerFound = false; if (filenameIn == filenameOut) { EXCEPTION(DRM_IN_OUT_EQUALS, "PDF IN must be different of PDF OUT"); } try { GOUROU_LOG(DEBUG, "Parse PDF"); parser.parse(filenameIn); } catch(std::invalid_argument& e) { GOUROU_LOG(ERROR, "Invalid PDF"); return; } uPDFParser::Integer* ebxVersion; std::vector objects = parser.objects(); std::vector::iterator it; std::vector::reverse_iterator rIt; std::vector ebxObjects; unsigned char decryptedKey[16]; int ebxId; for(rIt = objects.rbegin(); rIt != objects.rend(); rIt++) { // Update EBX_HANDLER with rights if ((*rIt)->hasKey("Filter") && (**rIt)["Filter"]->str() == "/EBX_HANDLER") { EBXHandlerFound = true; uPDFParser::Object* ebx = *rIt; ebxVersion = (uPDFParser::Integer*)(*ebx)["V"]; if (ebxVersion->value() != 4) { EXCEPTION(DRM_VERSION_NOT_SUPPORTED, "EBX encryption version not supported " << ebxVersion->value()); } if (!(ebx->hasKey("ADEPT_LICENSE"))) { EXCEPTION(DRM_ERR_ENCRYPTION_KEY, "No ADEPT_LICENSE found"); } uPDFParser::String* licenseObject = (uPDFParser::String*)(*ebx)["ADEPT_LICENSE"]; std::string value = licenseObject->value(); // Pad with '=' while ((value.size() % 4)) value += "="; ByteArray zippedData = ByteArray::fromBase64(value); if (zippedData.size() == 0) EXCEPTION(DRM_ERR_ENCRYPTION_KEY, "Invalid ADEPT_LICENSE"); ByteArray rightsStr; client->inflate(zippedData, rightsStr); pugi::xml_document rightsDoc; rightsDoc.load_string((const char*)rightsStr.data()); decryptADEPTKey(rightsDoc, decryptedKey, encryptionKey, encryptionKeySize); ebxId = ebx->objectId(); break; } } if (!EBXHandlerFound) { EXCEPTION(DRM_ERR_ENCRYPTION_KEY, "EBX_HANDLER not found"); } for(it = objects.begin(); it != objects.end(); it++) { uPDFParser::Object* object = *it; if (object->objectId() == ebxId) { ebxObjects.push_back(object); continue; } // Should not decrypt XRef stream if (object->hasKey("Type") && (*object)["Type"]->str() == "/XRef") { GOUROU_LOG(DEBUG, "XRef stream at " << object->offset()); continue; } GOUROU_LOG(DEBUG, "Obj " << object->objectId()); unsigned char tmpKey[sizeof(decryptedKey)]; generatePDFObjectKey(ebxVersion->value(), decryptedKey, sizeof(decryptedKey), object->objectId(), object->generationNumber(), tmpKey); uPDFParser::Dictionary& dictionary = object->dictionary(); std::map& dictValues = dictionary.value(); std::map::iterator dictIt; std::map decodedStrings; std::string string; /* Parse dictionary */ for (dictIt = dictValues.begin(); dictIt != dictValues.end(); dictIt++) { uPDFParser::DataType* dictData = dictIt->second; if (dictData->type() == uPDFParser::DataType::STRING) { string = ((uPDFParser::String*) dictData)->unescapedValue(); unsigned char* encryptedData = (unsigned char*)string.c_str(); unsigned int dataLength = string.size(); unsigned char* clearData = new unsigned char[dataLength]; unsigned int dataOutLength; GOUROU_LOG(DEBUG, "Decrypt string " << dictIt->first << " " << dataLength); client->decrypt(CryptoInterface::ALGO_RC4, CryptoInterface::CHAIN_ECB, tmpKey, sizeof(tmpKey), /* Key */ NULL, 0, /* IV */ encryptedData, dataLength, clearData, &dataOutLength); decodedStrings[dictIt->first] = new uPDFParser::String( std::string((const char*)clearData, dataOutLength)); delete[] clearData; } else if (dictData->type() == uPDFParser::DataType::HEXASTRING) { string = ((uPDFParser::HexaString*) dictData)->value(); ByteArray hexStr = ByteArray::fromHex(string); unsigned char* encryptedData = hexStr.data(); unsigned int dataLength = hexStr.size(); unsigned char* clearData = new unsigned char[dataLength]; unsigned int dataOutLength; GOUROU_LOG(DEBUG, "Decrypt hexa string " << dictIt->first << " " << dataLength); client->decrypt(CryptoInterface::ALGO_RC4, CryptoInterface::CHAIN_ECB, tmpKey, sizeof(tmpKey), /* Key */ NULL, 0, /* IV */ encryptedData, dataLength, clearData, &dataOutLength); ByteArray clearHexStr = ByteArray(clearData, dataOutLength); decodedStrings[dictIt->first] = new uPDFParser::HexaString( clearHexStr.toHex()); delete[] clearData; } } for (dictIt = decodedStrings.begin(); dictIt != decodedStrings.end(); dictIt++) dictionary.replace(dictIt->first, dictIt->second); std::vector::iterator datasIt; std::vector& datas = object->data(); uPDFParser::Stream* stream; for (datasIt = datas.begin(); datasIt != datas.end(); datasIt++) { if ((*datasIt)->type() != uPDFParser::DataType::STREAM) continue; stream = (uPDFParser::Stream*) (*datasIt); unsigned char* encryptedData = stream->data(); unsigned int dataLength = stream->dataLength(); unsigned char* clearData = new unsigned char[dataLength]; unsigned int dataOutLength; GOUROU_LOG(DEBUG, "Decrypt stream id " << object->objectId() << ", size " << stream->dataLength()); client->decrypt(CryptoInterface::ALGO_RC4, CryptoInterface::CHAIN_ECB, tmpKey, sizeof(tmpKey), /* Key */ NULL, 0, /* IV */ encryptedData, dataLength, clearData, &dataOutLength); stream->setData(clearData, dataOutLength, true); if (dataOutLength != dataLength) GOUROU_LOG(DEBUG, "New size " << dataOutLength); } } for(it = ebxObjects.begin(); it != ebxObjects.end(); it++) parser.removeObject(*it); uPDFParser::Object& trailer = parser.getTrailer(); trailer.deleteKey("Encrypt"); parser.write(filenameOut); } void DRMProcessor::removeDRM(const std::string& filenameIn, const std::string& filenameOut, ITEM_TYPE type, const unsigned char* encryptionKey, unsigned encryptionKeySize) { if (type == PDF) removePDFDRM(filenameIn, filenameOut, encryptionKey, encryptionKeySize); else removeEPubDRM(filenameIn, filenameOut, encryptionKey, encryptionKeySize); } }