From 0e90b89382fdaac3df34dd5d7b635b6e1e967a84 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9gory=20Soutad=C3=A9?= Date: Fri, 9 Jul 2021 21:55:39 +0200 Subject: [PATCH] v0.2 Fix a lot of things : * add common method signNode() and addNonde() * Try to auth to operator if not already set * Fix an error in ADEPT protocol : attributes must be hashed in alphabetical order, not in reverse one * Update DRMProcessorClient --- include/drmprocessorclient.h | 14 ++ include/libgourou.h | 7 +- include/user.h | 5 + src/libgourou.cpp | 269 ++++++++++++++++++++++--------- src/user.cpp | 5 + utils/drmprocessorclientimpl.cpp | 19 +++ utils/drmprocessorclientimpl.h | 3 + 7 files changed, 241 insertions(+), 81 deletions(-) diff --git a/include/drmprocessorclient.h b/include/drmprocessorclient.h index 5563332..5cbd1ee 100644 --- a/include/drmprocessorclient.h +++ b/include/drmprocessorclient.h @@ -173,6 +173,20 @@ namespace gourou * @param keyOutLength Length of result */ virtual void extractRSAPrivateKey(void* handler, unsigned char** keyOut, unsigned int* keyOutLength) = 0; + + /** + * @brief Extract certificate from PKCS12 blob + * + * @param RSAKey RSA key in binary form + * @param RSAKeyLength RSA key length + * @param keyType Key type + * @param password Optional password for RSA PKCS12 certificate + * @param certOut Result certificate + * @param certOutLength Result certificate length + */ + virtual void extractCertificate(const unsigned char* RSAKey, unsigned int RSAKeyLength, + const RSA_KEY_TYPE keyType, const std::string& password, + unsigned char** certOut, unsigned int* certOutLength) = 0; }; class CryptoInterface diff --git a/include/libgourou.h b/include/libgourou.h index f50f3ed..ae7613b 100644 --- a/include/libgourou.h +++ b/include/libgourou.h @@ -40,7 +40,7 @@ #define ACS_SERVER "http://adeactivate.adobe.com/adept" #endif -#define LIBGOUROU_VERSION "0.1.1" +#define LIBGOUROU_VERSION "0.2" namespace gourou { @@ -184,6 +184,11 @@ namespace gourou void pushTag(void* sha_ctx, uint8_t tag); void hashNode(const pugi::xml_node& root, void *sha_ctx, std::map nsHash); void hashNode(const pugi::xml_node& root, unsigned char* sha_out); + std::string signNode(const pugi::xml_node& rootNode); + void addNonce(pugi::xml_node& root); + void buildAuthRequest(pugi::xml_document& authReq); + void buildInitLicenseServiceRequest(pugi::xml_document& initLicReq, std::string operatorURL); + void operatorAuth(std::string operatorURL); void buildFulfillRequest(pugi::xml_document& acsmDoc, pugi::xml_document& fulfillReq); void buildActivateReq(pugi::xml_document& activateReq); ByteArray sendFulfillRequest(const pugi::xml_document& document, const std::string& url); diff --git a/include/user.h b/include/user.h index f605df0..625862e 100644 --- a/include/user.h +++ b/include/user.h @@ -70,6 +70,11 @@ namespace gourou */ std::string getProperty(const std::string property); + /** + * @brief Get all nodes with property name + */ + pugi::xpath_node_set getProperties(const std::string property); + /** * @brief Create activation.xml and devicesalt files if they did not exists * diff --git a/src/libgourou.cpp b/src/libgourou.cpp index 04d330f..2b069c0 100644 --- a/src/libgourou.cpp +++ b/src/libgourou.cpp @@ -20,6 +20,7 @@ #include #include #include +#include #include #include @@ -156,13 +157,28 @@ namespace gourou pushString(sha_ctx, name); - // Must be parsed in reverse order - for (pugi::xml_attribute attr = root.last_attribute(); - attr; attr = attr.previous_attribute()) + 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, ""); @@ -213,8 +229,73 @@ namespace gourou for(int i=0; i<(int)SHA1_LEN; i++) printf("%02x ", sha_out[i]); printf("\n"); + } } + + std::string DRMProcessor::signNode(const 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); + if (logLevel >= DEBUG) + { + printf("Sig : "); + for(int i=0; i<(int)sizeof(res); i++) + printf("%02x ", res[i]); + printf("\n"); + } + + ByteArray signature(res, sizeof(res)); + + return signature.toBase64(); + } + + 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}; + 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); + } ByteArray DRMProcessor::sendRequest(const std::string& URL, const std::string& POSTdata, const char* contentType) { @@ -243,6 +324,102 @@ namespace gourou 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()); + + std::string signature = signNode(root); + appendTextElem(root, "adept:signature", signature); + } + + 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; + } + } + + pugi::xml_document authReq; + buildAuthRequest(authReq); + std::string authURL = operatorURL; + int fulfillPos = authURL.rfind("Fulfill"); + if (fulfillPos == ((int)authURL.size() - 7)) + 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"); + + // Add new operatorURL to list + pugi::xml_document activationDoc; + user->readActivation(activationDoc); + + pugi::xml_node root; + pugi::xpath_node 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); + } + void DRMProcessor::buildFulfillRequest(pugi::xml_document& acsmDoc, pugi::xml_document& fulfillReq) { pugi::xml_node decl = fulfillReq.append_child(pugi::node_declaration); @@ -281,7 +458,7 @@ namespace gourou EXCEPTION(FF_INVALID_ACSM_FILE, "Invalid ACSM file " << ACSMFile); GOUROU_LOG(INFO, "Fulfill " << ACSMFile); - + // Build req file pugi::xml_document fulfillReq; @@ -300,36 +477,12 @@ namespace gourou 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"); - } + std::string signature = signNode(rootNode); // 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); + appendTextElem(rootNode, "adept:signature", signature); pugi::xpath_node node = acsmDoc.select_node("//operatorURL"); if (!node) @@ -338,6 +491,8 @@ namespace gourou std::string operatorURL = node.node().first_child().value(); operatorURL = trim(operatorURL) + "/Fulfill"; + operatorAuth(operatorURL); + ByteArray replyData = sendRequest(fulfillReq, operatorURL); pugi::xml_document fulfillReply; @@ -508,37 +663,7 @@ namespace gourou 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); + addNonce(root); appendTextElem(root, "adept:user", user->getUUID()); } @@ -550,29 +675,13 @@ namespace gourou 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(); + std::string signature = signNode(root); root = activateReq.select_node("adept:activate").node(); - appendTextElem(root, "adept:signature", b64Signature); + appendTextElem(root, "adept:signature", signature); pugi::xml_document activationDoc; user->readActivation(activationDoc); diff --git a/src/user.cpp b/src/user.cpp index ccc5019..c464386 100644 --- a/src/user.cpp +++ b/src/user.cpp @@ -110,6 +110,11 @@ namespace gourou { return trim(res); } + pugi::xpath_node_set User::getProperties(const std::string property) + { + return activationDoc.select_nodes(property.c_str()); + } + User* User::createUser(DRMProcessor* processor, const std::string& dirName, const std::string& ACSServer) { struct stat _stat; diff --git a/utils/drmprocessorclientimpl.cpp b/utils/drmprocessorclientimpl.cpp index 716d5d8..d98474d 100644 --- a/utils/drmprocessorclientimpl.cpp +++ b/utils/drmprocessorclientimpl.cpp @@ -213,6 +213,25 @@ void DRMProcessorClientImpl::extractRSAPrivateKey(void* handler, unsigned char** EVP_PKEY_free(evpKey); } +void DRMProcessorClientImpl::extractCertificate(const unsigned char* RSAKey, unsigned int RSAKeyLength, + const RSA_KEY_TYPE keyType, const std::string& password, + unsigned char** certOut, unsigned int* certOutLength) +{ + PKCS12 * pkcs12; + EVP_PKEY* pkey = 0; + X509* cert = 0; + STACK_OF(X509)* ca; + + pkcs12 = d2i_PKCS12(NULL, &RSAKey, RSAKeyLength); + if (!pkcs12) + EXCEPTION(gourou::CLIENT_INVALID_PKCS12, ERR_error_string(ERR_get_error(), NULL)); + PKCS12_parse(pkcs12, password.c_str(), &pkey, &cert, &ca); + + *certOutLength = i2d_X509(cert, certOut); + + EVP_PKEY_free(pkey); +} + /* Crypto interface */ void DRMProcessorClientImpl::AESEncrypt(CHAINING_MODE chaining, const unsigned char* key, unsigned int keyLength, diff --git a/utils/drmprocessorclientimpl.h b/utils/drmprocessorclientimpl.h index 3eca8bc..f475d96 100644 --- a/utils/drmprocessorclientimpl.h +++ b/utils/drmprocessorclientimpl.h @@ -63,6 +63,9 @@ class DRMProcessorClientImpl : public gourou::DRMProcessorClient virtual void extractRSAPublicKey(void* RSAKeyHandler, unsigned char** keyOut, unsigned int* keyOutLength); virtual void extractRSAPrivateKey(void* RSAKeyHandler, unsigned char** keyOut, unsigned int* keyOutLength); + virtual void extractCertificate(const unsigned char* RSAKey, unsigned int RSAKeyLength, + const RSA_KEY_TYPE keyType, const std::string& password, + unsigned char** certOut, unsigned int* certOutLength); /* Crypto interface */ virtual void AESEncrypt(CHAINING_MODE chaining,