forked from soutade/libgourou
Compute first pass for encryptedKey if keyType attribute is set
This commit is contained in:
parent
56b3231f92
commit
7b8c7acbad
|
@ -111,6 +111,7 @@ namespace gourou
|
||||||
public:
|
public:
|
||||||
enum RSA_KEY_TYPE {
|
enum RSA_KEY_TYPE {
|
||||||
RSA_KEY_PKCS12 = 0,
|
RSA_KEY_PKCS12 = 0,
|
||||||
|
RSA_KEY_PKCS8,
|
||||||
RSA_KEY_X509
|
RSA_KEY_X509
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -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 buildSignInRequest(pugi::xml_document& signInRequest, const std::string& adobeID, const std::string& adobePassword, const std::string& authenticationCertificate);
|
||||||
void fetchLicenseServiceCertificate(const std::string& licenseURL,
|
void fetchLicenseServiceCertificate(const std::string& licenseURL,
|
||||||
const std::string& operatorURL);
|
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 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 removeEPubDRM(const std::string& filenameIn, const std::string& filenameOut, const unsigned char* encryptionKey, unsigned encryptionKeySize);
|
||||||
void generatePDFObjectKey(int version,
|
void generatePDFObjectKey(int version,
|
||||||
|
|
|
@ -126,7 +126,8 @@ namespace gourou
|
||||||
DRM_FORMAT_NOT_SUPPORTED,
|
DRM_FORMAT_NOT_SUPPORTED,
|
||||||
DRM_IN_OUT_EQUALS,
|
DRM_IN_OUT_EQUALS,
|
||||||
DRM_MISSING_PARAMETER,
|
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
|
* It can throw an exception if tag does not exists
|
||||||
* or just return an empty value
|
* 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);
|
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_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);
|
|
||||||
|
|
||||||
if (!xpath_node)
|
if (!xpath_node)
|
||||||
{
|
{
|
||||||
|
@ -277,6 +252,37 @@ namespace gourou
|
||||||
return trim(res);
|
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
|
* @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());
|
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(; i<uuid.size(); i++)
|
||||||
|
{
|
||||||
|
if (uuid[i] != '-')
|
||||||
|
res += uuid[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Open a file descriptor on path. If it already exists and truncate == true, it's truncated
|
* @brief Open a file descriptor on path. If it already exists and truncate == true, it's truncated
|
||||||
*
|
*
|
||||||
|
|
|
@ -945,28 +945,95 @@ namespace gourou
|
||||||
void DRMProcessor::setLogLevel(int logLevel) {gourou::logLevel = (GOUROU_LOG_LEVEL)logLevel;}
|
void DRMProcessor::setLogLevel(int logLevel) {gourou::logLevel = (GOUROU_LOG_LEVEL)logLevel;}
|
||||||
|
|
||||||
void DRMProcessor::decryptADEPTKey(const std::string& encryptedKey, unsigned char* decryptedKey)
|
void DRMProcessor::decryptADEPTKey(const std::string& encryptedKey, unsigned char* decryptedKey)
|
||||||
{
|
{
|
||||||
if (encryptedKey.size() != 172)
|
if (encryptedKey.size() != 172)
|
||||||
EXCEPTION(DRM_INVALID_KEY_SIZE, "Invalid encrypted key size (" << encryptedKey.size() << "). DRM version not supported");
|
EXCEPTION(DRM_INVALID_KEY_SIZE, "Invalid encrypted key size (" << encryptedKey.size() << "). DRM version not supported");
|
||||||
|
|
||||||
ByteArray arrayEncryptedKey = ByteArray::fromBase64(encryptedKey);
|
ByteArray arrayEncryptedKey = ByteArray::fromBase64(encryptedKey);
|
||||||
|
|
||||||
|
|
||||||
std::string privateKeyData = user->getPrivateLicenseKey();
|
std::string privateKeyData = user->getPrivateLicenseKey();
|
||||||
ByteArray privateRSAKey = ByteArray::fromBase64(privateKeyData);
|
ByteArray privateRSAKey = ByteArray::fromBase64(privateKeyData);
|
||||||
|
|
||||||
ByteArray deviceKey(device->getDeviceKey(), Device::DEVICE_KEY_SIZE);
|
|
||||||
std::string pkcs12 = user->getPKCS12();
|
|
||||||
|
|
||||||
client->RSAPrivateDecrypt(privateRSAKey.data(), privateRSAKey.length(),
|
dumpBuffer(gourou::LG_LOG_DEBUG, "To decrypt : ", arrayEncryptedKey.data(), arrayEncryptedKey.length());
|
||||||
RSAInterface::RSA_KEY_PKCS12, deviceKey.toBase64().data(),
|
|
||||||
arrayEncryptedKey.data(), arrayEncryptedKey.length(), decryptedKey);
|
|
||||||
|
|
||||||
if (decryptedKey[0] != 0x00 || decryptedKey[1] != 0x02 ||
|
client->RSAPrivateDecrypt(privateRSAKey.data(), privateRSAKey.length(),
|
||||||
decryptedKey[RSA_KEY_SIZE-16-1] != 0x00)
|
RSAInterface::RSA_KEY_PKCS8, "",
|
||||||
EXCEPTION(DRM_ERR_ENCRYPTION_KEY, "Unable to retrieve encryption key");
|
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; i<sizeof(iv); i++)
|
||||||
|
iv[i] = _deviceId[i] ^ _fulfillmentId[i] ^ _voucherId[i];
|
||||||
|
|
||||||
|
ByteArray arrayEncryptedKey = ByteArray::fromBase64(encryptedKey);
|
||||||
|
|
||||||
|
dumpBuffer(gourou::LG_LOG_DEBUG, "First pass key : ", key, sizeof(key));
|
||||||
|
dumpBuffer(gourou::LG_LOG_DEBUG, "First pass IV : ", iv, sizeof(iv));
|
||||||
|
|
||||||
|
unsigned char* clearRSAKey = new unsigned char[arrayEncryptedKey.size()];
|
||||||
|
|
||||||
|
client->Decrypt(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<dataOutLength; i++)
|
||||||
|
{
|
||||||
|
if (clearRSAKey[i] != 0x10)
|
||||||
|
{
|
||||||
|
skipLastLine = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ByteArray res(clearRSAKey, (skipLastLine)?dataOutLength-16:dataOutLength);
|
||||||
|
|
||||||
|
delete[] clearRSAKey;
|
||||||
|
|
||||||
|
return res.toBase64();
|
||||||
|
}
|
||||||
|
|
||||||
void DRMProcessor::removeEPubDRM(const std::string& filenameIn, const std::string& filenameOut,
|
void DRMProcessor::removeEPubDRM(const std::string& filenameIn, const std::string& filenameOut,
|
||||||
const unsigned char* encryptionKey, unsigned encryptionKeySize)
|
const unsigned char* encryptionKey, unsigned encryptionKeySize)
|
||||||
|
@ -983,7 +1050,20 @@ namespace gourou
|
||||||
unsigned char decryptedKey[RSA_KEY_SIZE];
|
unsigned char decryptedKey[RSA_KEY_SIZE];
|
||||||
|
|
||||||
if (!encryptionKey)
|
if (!encryptionKey)
|
||||||
|
{
|
||||||
|
std::string keyType = extractTextAttribute(rightsDoc, "/adept:rights/licenseToken/encryptedKey", "keyType", false);
|
||||||
|
|
||||||
|
if (keyType != "")
|
||||||
|
encryptedKey = encryptedKeyFirstPass(rightsDoc, encryptedKey, keyType);
|
||||||
|
|
||||||
decryptADEPTKey(encryptedKey, decryptedKey);
|
decryptADEPTKey(encryptedKey, decryptedKey);
|
||||||
|
|
||||||
|
dumpBuffer(gourou::LG_LOG_DEBUG, "Decrypted : ", decryptedKey, RSA_KEY_SIZE);
|
||||||
|
|
||||||
|
if (decryptedKey[0] != 0x00 || decryptedKey[1] != 0x02 ||
|
||||||
|
decryptedKey[RSA_KEY_SIZE-16-1] != 0x00)
|
||||||
|
EXCEPTION(DRM_ERR_ENCRYPTION_KEY, "Unable to retrieve encryption key");
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
GOUROU_LOG(DEBUG, "Use provided encryption key");
|
GOUROU_LOG(DEBUG, "Use provided encryption key");
|
||||||
|
|
|
@ -33,6 +33,6 @@ ${COMMON_LIB}: ${COMMON_DEPS} ${STATIC_DEP}
|
||||||
$(CXX) $(CXXFLAGS) $^ $(LDFLAGS) -o $@
|
$(CXX) $(CXXFLAGS) $^ $(LDFLAGS) -o $@
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
rm -f $(TARGETS)
|
rm -f $(TARGETS) $(COMMON_LIB)
|
||||||
|
|
||||||
ultraclean: clean
|
ultraclean: clean
|
||||||
|
|
Loading…
Reference in New Issue
Block a user