Skip to content

Commit

Permalink
Merge pull request #1641 from CastagnaIT/clearkey_improvs_omega
Browse files Browse the repository at this point in the history
[backport][DRM][ClearKey] Add license url by DRM config, CK as DRM replacement
  • Loading branch information
CastagnaIT authored Aug 19, 2024
2 parents f2cadb0 + 32eb73a commit 6058111
Show file tree
Hide file tree
Showing 14 changed files with 173 additions and 57 deletions.
53 changes: 40 additions & 13 deletions src/CompKodiProps.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,18 @@ ADP::KODI_PROPS::CCompKodiProps::CCompKodiProps(const std::map<std::string, std:
{
std::string licenseUrl;

if ((STRING::KeyExists(props, PROP_LICENSE_TYPE) || STRING::KeyExists(props, PROP_LICENSE_KEY)) &&
STRING::KeyExists(props, PROP_DRM_LEGACY))
{
LOG::Log(LOGERROR, "WRONG DRM CONFIGURATION. A mixed use of DRM properties are not supported.\n"
"Please fix your configuration by setting only one of these:\n"
" - Simple method: \"inputstream.adaptive.drm_legacy\"\n"
" - Advanced method: \"inputstream.adaptive.license_type\" with optional "
"\"inputstream.adaptive.license_key\"\n"
"For more details, see the Github Wiki Integration page.");
return;
}

for (const auto& prop : props)
{
bool logPropValRedacted{false};
Expand Down Expand Up @@ -374,7 +386,8 @@ bool ADP::KODI_PROPS::CCompKodiProps::ParseDrmConfig(const std::string& data)
* "streams_pssh_data" : str,
* "pre_init_data" : str,
* "priority": int,
* "keyids": list<dict>},
* "license": dict,
* ... },
* "keysystem_name_2" : { ... }}
*/
rapidjson::Document jDoc;
Expand All @@ -397,6 +410,9 @@ bool ADP::KODI_PROPS::CCompKodiProps::ParseDrmConfig(const std::string& data)
continue;
}

//! @todo: m_licenseType temporarily assigned, to remove with the DRM config rework
m_licenseType = keySystem;

DrmCfg& drmCfg = m_drmConfigs[keySystem];
auto& jDictVal = jChildObj.value;

Expand All @@ -416,10 +432,13 @@ bool ADP::KODI_PROPS::CCompKodiProps::ParseDrmConfig(const std::string& data)
for (auto const& keyid : jDictLic["keyids"].GetObject())
{
if (keyid.name.IsString() && keyid.value.IsString())
drmCfg.m_keys[keyid.name.GetString()] = (keyid.value.GetString());
drmCfg.license.keys[keyid.name.GetString()] = (keyid.value.GetString());
}
}
}

//! @todo: temporary support only one DRM config
break;
}

return true;
Expand Down Expand Up @@ -460,15 +479,13 @@ bool ADP::KODI_PROPS::CCompKodiProps::ParseDrmLegacyConfig(const std::string& da
}

m_licenseType = keySystem;

// Clear existing value to prevent possible mix with other similar properties
m_licenseKey.clear();
std::string licenseUrl;

if (!licenseStr.empty())
{
if (URL::IsValidUrl(licenseStr)) // License server URL
{
m_licenseKey = licenseStr;
licenseUrl = licenseStr;
}
else // Assume are keyid's for ClearKey DRM
{
Expand All @@ -484,17 +501,27 @@ bool ADP::KODI_PROPS::CCompKodiProps::ParseDrmLegacyConfig(const std::string& da
LOG::LogF(LOGERROR, "Ignored malformed ClearKey kid/key pair");
continue;
}
drmCfg.m_keys[STRING::Trim(keyPair[0])] = STRING::Trim(keyPair[1]);
drmCfg.license.keys[STRING::Trim(keyPair[0])] = STRING::Trim(keyPair[1]);
}
}
}

//! @todo: temporary stored default DRM values here just for convenience
//! since we need to construct the "license key" string
//! these values are stored also on DRM's implementation,
//! they must be placed in an appropriate place with the future DRM config rework
if (licenseHeaders.empty())
if (keySystem == DRM::KS_CLEARKEY)
{
DrmCfg& drmCfg = m_drmConfigs[keySystem];

drmCfg.license.serverUrl = licenseUrl;
ParseHeaderString(drmCfg.license.reqHeaders, licenseHeaders);
// Until the future DRM config rework only the ClearKey DRM use the new properties
// so return now to keep m_licenseKey empty
return true;
}
else if (licenseHeaders.empty())
{
//! @todo: temporary stored default DRM values here just for convenience
//! since we need to construct the "license key" string
//! these values are stored also on DRM's implementation,
//! they must be placed in an appropriate place with the future DRM config rework
if (keySystem == DRM::KS_WIDEVINE)
licenseHeaders = "Content-Type=application%2Foctet-stream";
else if (keySystem == DRM::KS_PLAYREADY)
Expand All @@ -504,6 +531,6 @@ bool ADP::KODI_PROPS::CCompKodiProps::ParseDrmLegacyConfig(const std::string& da
licenseHeaders = "Content-Type=application/json";
}

m_licenseKey += "|" + licenseHeaders + "|R{SSM}|R";
m_licenseKey = licenseUrl + "|" + licenseHeaders + "|R{SSM}|R";
return true;
}
10 changes: 9 additions & 1 deletion src/CompKodiProps.h
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,15 @@ struct ManifestConfig

struct DrmCfg
{
std::map<std::string, std::string> m_keys;
struct License
{
std::string serverUrl;
std::map<std::string, std::string> reqHeaders;

std::map<std::string, std::string> keys; // Clearkeys kid / key
};

License license; // The license configuration
};

class ATTR_DLL_LOCAL CCompKodiProps
Expand Down
3 changes: 2 additions & 1 deletion src/Session.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -454,9 +454,10 @@ bool CSession::InitializeDRM(bool addDefaultKID /* = false */)
// Use the init data provided by manifest (e.g. PSSH)
initData = sessionPsshset.pssh_;
}
else
else if (licenseType != DRM::KS_CLEARKEY)
{
// Try extract the PSSH/KID from the stream
// only if clearkeys are not used (use case e.g. Widevine manifest tested with ClearKey DRM)
if (!ExtractStreamProtectionData(sessionPsshset, initData, m_adaptiveTree->m_supportedKeySystems))
LOG::Log(LOGERROR, "License data: Cannot extract PSSH/KID data from the stream");
}
Expand Down
24 changes: 13 additions & 11 deletions src/decrypters/clearkey/ClearKeyCencSingleSampleDecrypter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,10 @@
using namespace UTILS;

CClearKeyCencSingleSampleDecrypter::CClearKeyCencSingleSampleDecrypter(
std::string_view licenseUrl, const std::vector<uint8_t>& defaultKeyId, CClearKeyDecrypter* host)
std::string_view licenseUrl,
const std::map<std::string, std::string>& licenseHeaders,
const std::vector<uint8_t>& defaultKeyId,
CClearKeyDecrypter* host)
: m_host(host)
{
if (licenseUrl.empty())
Expand All @@ -44,10 +47,10 @@ CClearKeyCencSingleSampleDecrypter::CClearKeyCencSingleSampleDecrypter(
FILESYS::SaveFile(debugFilePath, postData.c_str(), true);
}

CURL::CUrl curl{licenseUrl};
CURL::CUrl curl{licenseUrl, postData};
curl.AddHeader("Accept", "application/json");
curl.AddHeader("Content-Type", "application/json");
curl.AddHeader("postdata", UTILS::BASE64::Encode(postData));
curl.AddHeaders(licenseHeaders);

std::string response;
int statusCode = curl.Open();
Expand Down Expand Up @@ -83,10 +86,10 @@ CClearKeyCencSingleSampleDecrypter::CClearKeyCencSingleSampleDecrypter(
return;
}

std::vector<uint8_t> keyBytes = BASE64::Decode(m_keyPairs[b64DefaultKeyId]);
const std::vector<uint8_t> keyBytes = BASE64::Decode(m_keyPairs[b64DefaultKeyId]);
if (AP4_FAILED(AP4_CencSingleSampleDecrypter::Create(AP4_CENC_CIPHER_AES_128_CTR, keyBytes.data(),
16, 0, 0, nullptr, false,
m_singleSampleDecrypter)))
static_cast<AP4_Size>(keyBytes.size()), 0, 0,
nullptr, false, m_singleSampleDecrypter)))
{
LOG::LogF(LOGERROR, "Failed to create AP4_CencSingleSampleDecrypter");
}
Expand Down Expand Up @@ -117,9 +120,9 @@ CClearKeyCencSingleSampleDecrypter::CClearKeyCencSingleSampleDecrypter(
LOG::LogF(LOGERROR, "Missing KeyId \"%s\" on DRM configuration", defaultKeyId.data());
}

const AP4_UI08* ap4Key = reinterpret_cast<const AP4_UI08*>(hexKey.data());
AP4_CencSingleSampleDecrypter::Create(AP4_CENC_CIPHER_AES_128_CTR, ap4Key, 16, 0, 0, nullptr,
false, m_singleSampleDecrypter);
AP4_CencSingleSampleDecrypter::Create(AP4_CENC_CIPHER_AES_128_CTR, hexKey.data(),
static_cast<AP4_Size>(hexKey.size()), 0, 0, nullptr, false,
m_singleSampleDecrypter);
SetParentIsOwner(false);
AddSessionKey(defaultKeyId);
}
Expand Down Expand Up @@ -173,8 +176,7 @@ std::string CClearKeyCencSingleSampleDecrypter::CreateLicenseRequest(
* "type":"temporary" }
*/

std::string b64Kid = UTILS::BASE64::Encode(defaultKeyId);
UTILS::STRING::ReplaceAll(b64Kid, "=", "");
std::string b64Kid = UTILS::BASE64::Encode(defaultKeyId, false);

rapidjson::Document jDoc;
jDoc.SetObject();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ class CClearKeyCencSingleSampleDecrypter : public Adaptive_CencSingleSampleDecry
{
public:
CClearKeyCencSingleSampleDecrypter(std::string_view licenseUrl,
const std::map<std::string, std::string>& licenseHeaders,
const std::vector<uint8_t>& defaultKeyId,
CClearKeyDecrypter* host);
CClearKeyCencSingleSampleDecrypter(const std::vector<uint8_t>& initdata,
Expand Down
20 changes: 16 additions & 4 deletions src/decrypters/clearkey/ClearKeyDecrypter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
#include "CompKodiProps.h"
#include "SrvBroker.h"
#include "decrypters/Helpers.h"
#include "utils/log.h"

std::vector<std::string_view> CClearKeyDecrypter::SelectKeySystems(std::string_view keySystem)
{
Expand Down Expand Up @@ -39,16 +40,27 @@ Adaptive_CencSingleSampleDecrypter* CClearKeyDecrypter::CreateSingleSampleDecryp
bool skipSessionMessage,
CryptoMode cryptoMode)
{
if (cryptoMode != CryptoMode::AES_CTR)
{
LOG::LogF(LOGERROR, "Cannot initialize ClearKey DRM. Only \"cenc\" encryption supported.");
return nullptr;
}

CClearKeyCencSingleSampleDecrypter* decrypter = nullptr;
auto& keys = CSrvBroker::GetKodiProps().GetDrmConfig(std::string(DRM::KS_CLEARKEY)).m_keys;
auto& cfgLic = CSrvBroker::GetKodiProps().GetDrmConfig(std::string(DRM::KS_CLEARKEY)).license;

// If keys / license url are provided by Kodi property, those of the manifest will be overwritten

if (!cfgLic.serverUrl.empty())
licenseUrl = cfgLic.serverUrl;

if (!keys.empty() || !initData.empty()) // Keys provided from manifest or Kodi property
if ((!cfgLic.keys.empty() || !initData.empty()) && cfgLic.serverUrl.empty()) // Keys provided from manifest or Kodi property
{
decrypter = new CClearKeyCencSingleSampleDecrypter(initData, defaultkeyid, keys, this);
decrypter = new CClearKeyCencSingleSampleDecrypter(initData, defaultkeyid, cfgLic.keys, this);
}
else // Clearkey license server URL provided
{
decrypter = new CClearKeyCencSingleSampleDecrypter(licenseUrl, defaultkeyid, this);
decrypter = new CClearKeyCencSingleSampleDecrypter(licenseUrl, cfgLic.reqHeaders, defaultkeyid, this);
}

if (!decrypter->HasKeys())
Expand Down
1 change: 1 addition & 0 deletions src/decrypters/widevine/WVCencSingleSampleDecrypter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -459,6 +459,7 @@ bool CWVCencSingleSampleDecrypter::SendSessionMessage()
}

std::string encData{BASE64::Encode(blocks[2])};
//! @todo: inappropriate use of "postdata" header, use CURL::CUrl for post request
file.AddHeader("postdata", encData.c_str());
}

Expand Down
29 changes: 29 additions & 0 deletions src/parser/DASHTree.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
#include "PRProtectionParser.h"
#include "SrvBroker.h"
#include "common/Period.h"
#include "decrypters/Helpers.h"
#include "utils/Base64Utils.h"
#include "utils/CurlUtils.h"
#include "utils/StringUtils.h"
Expand Down Expand Up @@ -1336,6 +1337,34 @@ bool adaptive::CDashTree::GetProtectionData(
}
}

// Workaround for ClearKey:
// if license type ClearKey is set and a manifest dont contains ClearKey protection scheme
// in any case the KID is required to allow decryption (with clear keys or license URLs provided by Kodi props)
//! @todo: this should not be a task of parser, moreover missing an appropriate KID extraction from mp4 box
auto& kodiProps = CSrvBroker::GetKodiProps();
ProtectionScheme ckProtScheme;
if (!protSelected && !protCommon && kodiProps.GetLicenseType() == DRM::KS_CLEARKEY)
{
for (const ProtectionScheme& protScheme : reprProtSchemes)
{
if (!protScheme.kid.empty())
{
ckProtScheme.kid = protScheme.kid;
break;
}
}
if (ckProtScheme.kid.empty())
{
for (const ProtectionScheme& protScheme : adpProtSchemes)
{
ckProtScheme.kid = protScheme.kid;
break;
}
}
if (!ckProtScheme.kid.empty())
protCommon = &ckProtScheme;
}

bool isEncrypted{false};
std::string selectedKid;
std::string selectedPssh;
Expand Down
21 changes: 13 additions & 8 deletions src/parser/HLSTree.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1295,6 +1295,13 @@ PLAYLIST::EncryptionType adaptive::CHLSTree::ProcessEncryption(
m_currentPssh = STRING::ToVecUint8(resp.data);
}

if (uriUrl.empty()) // No kid provided, assume key == kid
m_currentDefaultKID = STRING::ToHexadecimal(uriData);

}
else if (STRING::CompareNoCase(keyFormat, DRM::URN_WIDEVINE))
{
// Take only the KID
if (STRING::KeyExists(attribs, "KEYID"))
{
std::string keyid = attribs["KEYID"];
Expand All @@ -1305,16 +1312,14 @@ PLAYLIST::EncryptionType adaptive::CHLSTree::ProcessEncryption(
else
LOG::LogF(LOGERROR, "Incorret KEYID tag format");
}
else if (uriUrl.empty()) // No kid provided, assume key == kid
m_currentDefaultKID = STRING::ToHexadecimal(uriData);
}

if (encryptMethod == "SAMPLE-AES-CTR")
m_cryptoMode = CryptoMode::AES_CTR;
else if (encryptMethod == "SAMPLE-AES")
m_cryptoMode = CryptoMode::AES_CBC;
if (encryptMethod == "SAMPLE-AES-CTR")
m_cryptoMode = CryptoMode::AES_CTR;
else if (encryptMethod == "SAMPLE-AES")
m_cryptoMode = CryptoMode::AES_CBC;

return EncryptionType::CLEARKEY;
}
return EncryptionType::CLEARKEY;
}

// Unsupported encryption
Expand Down
Loading

0 comments on commit 6058111

Please sign in to comment.