From 7b8c7acbadf5dbdbba534899ea0ab55db1102a64 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9gory=20Soutad=C3=A9?= Date: Sat, 27 Aug 2022 15:44:27 +0200 Subject: [PATCH] Compute first pass for encryptedKey if keyType attribute is set --- include/drmprocessorclient.h | 1 + include/libgourou.h | 1 + include/libgourou_common.h | 87 +++++++++++++++++++---------- src/libgourou.cpp | 104 +++++++++++++++++++++++++++++++---- utils/Makefile | 2 +- 5 files changed, 153 insertions(+), 42 deletions(-) diff --git a/include/drmprocessorclient.h b/include/drmprocessorclient.h index 6b02b81..3926595 100644 --- a/include/drmprocessorclient.h +++ b/include/drmprocessorclient.h @@ -111,6 +111,7 @@ namespace gourou public: enum RSA_KEY_TYPE { RSA_KEY_PKCS12 = 0, + RSA_KEY_PKCS8, RSA_KEY_X509 }; diff --git a/include/libgourou.h b/include/libgourou.h index 1fe4988..60f7fe6 100644 --- a/include/libgourou.h +++ b/include/libgourou.h @@ -231,6 +231,7 @@ namespace gourou void buildSignInRequest(pugi::xml_document& signInRequest, const std::string& adobeID, const std::string& adobePassword, const std::string& authenticationCertificate); void fetchLicenseServiceCertificate(const std::string& licenseURL, const std::string& operatorURL); + std::string encryptedKeyFirstPass(pugi::xml_document& rightsDoc, const std::string& encryptedKey, const std::string& keyType); void decryptADEPTKey(const std::string& encryptedKey, unsigned char* decryptedKey); void removeEPubDRM(const std::string& filenameIn, const std::string& filenameOut, const unsigned char* encryptionKey, unsigned encryptionKeySize); void generatePDFObjectKey(int version, diff --git a/include/libgourou_common.h b/include/libgourou_common.h index bd4117b..4a71aa3 100644 --- a/include/libgourou_common.h +++ b/include/libgourou_common.h @@ -126,7 +126,8 @@ namespace gourou DRM_FORMAT_NOT_SUPPORTED, DRM_IN_OUT_EQUALS, DRM_MISSING_PARAMETER, - DRM_INVALID_KEY_SIZE + DRM_INVALID_KEY_SIZE, + DRM_ERR_ENCRYPTION_KEY_FP }; /** @@ -225,35 +226,9 @@ namespace gourou * It can throw an exception if tag does not exists * or just return an empty value */ - static inline std::string extractTextElem(const pugi::xml_document& doc, const char* tagName, bool throwOnNull=true) + static inline std::string extractTextElem(const pugi::xml_node& root, const char* tagName, bool throwOnNull=true) { - pugi::xpath_node xpath_node = doc.select_node(tagName); - - if (!xpath_node) - { - if (throwOnNull) - EXCEPTION(GOUROU_TAG_NOT_FOUND, "Tag " << tagName << " not found"); - - return ""; - } - - pugi::xml_node node = xpath_node.node().first_child(); - - if (!node) - { - if (throwOnNull) - EXCEPTION(GOUROU_TAG_NOT_FOUND, "Text element for tag " << tagName << " not found"); - - return ""; - } - - std::string res = node.value(); - return trim(res); - } - - static inline std::string extractTextElem(const pugi::xml_node& doc, const char* tagName, bool throwOnNull=true) - { - pugi::xpath_node xpath_node = doc.select_node(tagName); + pugi::xpath_node xpath_node = root.select_node(tagName); if (!xpath_node) { @@ -277,6 +252,37 @@ namespace gourou return trim(res); } + /** + * @brief Extract text attribute from tag in document + * It can throw an exception if attribute does not exists + * or just return an empty value + */ + static inline std::string extractTextAttribute(const pugi::xml_node& root, const char* tagName, const char* attributeName, bool throwOnNull=true) + { + pugi::xpath_node xpath_node = root.select_node(tagName); + + if (!xpath_node) + { + if (throwOnNull) + EXCEPTION(GOUROU_TAG_NOT_FOUND, "Tag " << tagName << " not found"); + + return ""; + } + + pugi::xml_attribute attr = xpath_node.node().attribute(attributeName); + + if (!attr) + { + if (throwOnNull) + EXCEPTION(GOUROU_TAG_NOT_FOUND, "Attribute element " << attributeName << " for tag " << tagName << " not found"); + + return ""; + } + + std::string res = attr.value(); + return trim(res); + } + /** * @brief Append an element to root with a sub text element * @@ -290,6 +296,29 @@ namespace gourou node.append_child(pugi::node_pcdata).set_value(value.c_str()); } + /** + * Remove "urn:uuid:" prefix and all '-' from uuid + * urn:uuid:9cb786e8-586a-4950-8901-fff8d2ee6025 + * -> + * 9cb786e8586a49508901fff8d2ee6025 + */ + static inline std::string extractIdFromUUID(const std::string& uuid) + { + unsigned int i = 0; + std::string res; + + if (uuid.find("urn:uuid:") == 0) + i = 9; + + for(; igetPrivateLicenseKey(); ByteArray privateRSAKey = ByteArray::fromBase64(privateKeyData); - - ByteArray deviceKey(device->getDeviceKey(), Device::DEVICE_KEY_SIZE); - std::string pkcs12 = user->getPKCS12(); - client->RSAPrivateDecrypt(privateRSAKey.data(), privateRSAKey.length(), - RSAInterface::RSA_KEY_PKCS12, deviceKey.toBase64().data(), - arrayEncryptedKey.data(), arrayEncryptedKey.length(), decryptedKey); + dumpBuffer(gourou::LG_LOG_DEBUG, "To decrypt : ", arrayEncryptedKey.data(), arrayEncryptedKey.length()); - if (decryptedKey[0] != 0x00 || decryptedKey[1] != 0x02 || - decryptedKey[RSA_KEY_SIZE-16-1] != 0x00) - EXCEPTION(DRM_ERR_ENCRYPTION_KEY, "Unable to retrieve encryption key"); + client->RSAPrivateDecrypt(privateRSAKey.data(), privateRSAKey.length(), + RSAInterface::RSA_KEY_PKCS8, "", + arrayEncryptedKey.data(), arrayEncryptedKey.length(), decryptedKey); } + /** + * RSA Key can be over encrypted with AES128-CBC if keyType attribute is set + * Key = SHA256(keyType)[14:22] || SHA256(keyType)[7:13] + * 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); + memcpy(key, &digest[14], 9); + memcpy(&key[9], &digest[7], 7); + + 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; i