Support ATSC license installation

[ Merge of http://go/wvgerrit/163900/ ]

ATSC licenses can be saved by calling
MediaDrm#setPropertyString("storeAtscLicense",<value>)
where <value> is
"<atsc-key-set-ID>:<license-file-data in Base64 format>"

Before storing an ATSC license a session must be opened and the
ATSC mode must be enabled.

Use MediaDrm#setPropertyString("atscMode","enable");

Bug: 176871821
Test: WV Unit/integration/Luci tests
Test: libwvdrmdrmplugin_hal_test
Test: GtsMediaTestCases
Change-Id: Iec2a8b7f87b1122395d06856202278b92316fdfe
This commit is contained in:
Rahul Frias
2022-12-17 19:34:33 -08:00
parent 233bac3a6f
commit 1e15b36b1a
9 changed files with 388 additions and 0 deletions

View File

@@ -235,6 +235,10 @@ class CdmEngine {
virtual CdmResponseType RemoveOfflineLicense(const std::string& key_set_id,
CdmSecurityLevel security_level);
virtual CdmResponseType StoreAtscLicense(
RequestedSecurityLevel security_level, const CdmKeySetId& key_set_id,
const std::string& serialized_license_data);
// Usage related methods for streaming licenses
// Retrieve a random usage info from the list of all usage infos for this app
// id. If |error_detail| is not null, an additional error code may be provided

View File

@@ -155,6 +155,9 @@ class DeviceFiles {
virtual bool RetrieveLicense(const std::string& key_set_id,
CdmLicenseData* license_data,
ResponseType* result);
virtual ResponseType StoreAtscLicense(
const CdmKeySetId& key_set_id,
const std::string& serialized_license_data);
virtual bool DeleteLicense(const std::string& key_set_id);
virtual bool ListLicenses(std::vector<std::string>* key_set_ids);

View File

@@ -1413,6 +1413,35 @@ CdmResponseType CdmEngine::RemoveOfflineLicense(
return sts;
}
CdmResponseType CdmEngine::StoreAtscLicense(
RequestedSecurityLevel requested_security_level,
const CdmKeySetId& key_set_id, const std::string& serialized_license_data) {
std::string security_level_string;
CdmResponseType status =
QueryStatus(requested_security_level, QUERY_KEY_SECURITY_LEVEL,
&security_level_string);
if (status != NO_ERROR) return status;
DeviceFiles handle(file_system_);
CdmSecurityLevel security_level =
security_level_string == QUERY_VALUE_SECURITY_LEVEL_L1 ? kSecurityLevelL1
: kSecurityLevelL3;
if (!handle.Init(security_level)) {
LOGE("Unable to initialize device files");
return CdmResponseType(STORE_ATSC_LICENSE_DEVICE_FILES_INIT_ERROR);
}
DeviceFiles::ResponseType response_type =
handle.StoreAtscLicense(key_set_id, serialized_license_data);
if (response_type != DeviceFiles::kNoError) {
LOGE("Unable to store ATSC license: response = %s",
DeviceFiles::ResponseTypeToString(response_type));
return CdmResponseType(STORE_ATSC_LICENSE_ERROR);
}
return CdmResponseType(NO_ERROR);
}
CdmResponseType CdmEngine::GetUsageInfo(const std::string& app_id,
const CdmSecureStopId& ssid,
int* error_detail,

View File

@@ -943,6 +943,11 @@ bool DeviceFiles::RetrieveLicense(const std::string& key_set_id,
&license_data->wrapped_private_key);
}
DeviceFiles::ResponseType DeviceFiles::StoreAtscLicense(
const CdmKeySetId& key_set_id, const std::string& serialized_data) {
return StoreFileWithHash(key_set_id + kLicenseFileNameExt, serialized_data);
}
bool DeviceFiles::DeleteLicense(const std::string& key_set_id) {
RETURN_FALSE_IF_UNINITIALIZED();
return RemoveFile(key_set_id + kLicenseFileNameExt);

View File

@@ -194,6 +194,10 @@ class WvContentDecryptionModule : public android::RefBase, public TimerHandler {
virtual CdmResponseType GetSessionUserId(const CdmSessionId& session_id,
uint32_t* user_id);
virtual CdmResponseType StoreAtscLicense(
const CdmIdentifier& identifier, RequestedSecurityLevel security_level,
const CdmKeySetId& key_set_id,
const std::string& serialized_license_data);
virtual bool SetDefaultOtaKeyboxFallbackDurationRules();
virtual bool SetFastOtaKeyboxFallbackDurationRules();

View File

@@ -656,6 +656,15 @@ CdmResponseType WvContentDecryptionModule::GetSessionUserId(
return CdmResponseType(NO_ERROR);
}
CdmResponseType WvContentDecryptionModule::StoreAtscLicense(
const CdmIdentifier& identifier,
RequestedSecurityLevel requested_security_level,
const CdmKeySetId& key_set_id, const std::string& serialized_license_data) {
CdmEngine* cdm_engine = EnsureCdmForIdentifier(identifier);
return cdm_engine->StoreAtscLicense(requested_security_level, key_set_id,
serialized_license_data);
}
bool WvContentDecryptionModule::SetDefaultOtaKeyboxFallbackDurationRules() {
CdmEngine* cdm_engine = EnsureCdmForIdentifier(kDefaultCdmIdentifier);
if (!cdm_engine) return false;

View File

@@ -456,6 +456,10 @@ class WVDrmPlugin : public ::aidl::android::hardware::drm::BnDrmPlugin,
in_keyStatusList,
bool in_hasNewUsableKey);
void sendSessionLostState(const std::vector<uint8_t>& in_sessionId);
WvStatus parseAtscLicenseData(const std::string& value,
std::string* key_set_id,
std::string* serialized_license_data);
};
} // namespace widevine

View File

@@ -26,6 +26,7 @@
#include "mapErrors-inl.h"
#include "media/stagefright/MediaErrors.h"
#include "openssl/sha.h"
#include "string_conversions.h"
#include "wv_cdm_constants.h"
#include "wv_metrics.pb.h"
#include "wv_metrics_adapter.h"
@@ -1344,6 +1345,32 @@ Status WVDrmPlugin::unprovisionDevice() {
if (!success) {
return toNdkScopedAStatus(Status::ERROR_DRM_UNKNOWN);
}
} else if (name == "storeAtscLicense") {
if (!mCdmIdentifierBuilder.is_sealed()) {
ALOGE("Cdm identifier builder is not sealed. Storing ATSC license "
"prohibited");
return toNdkScopedAStatus(Status::ERROR_DRM_CANNOT_HANDLE);
}
if (!mPropertySet.use_atsc_mode()) {
ALOGE("ATSC mode not enabled. Storing ATSC license prohibited");
return toNdkScopedAStatus(Status::ERROR_DRM_CANNOT_HANDLE);
}
CdmIdentifier identifier;
auto status = mCdmIdentifierBuilder.getCdmIdentifier(&identifier);
if (status != Status::OK) {
ALOGE("Unable to get CDM Identifier = %d", status.get());
return toNdkScopedAStatus(status);
}
std::string key_set_id, license_data;
status = parseAtscLicenseData(_value, &key_set_id, &license_data);
if (status != Status::OK)
return toNdkScopedAStatus(status);
const CdmResponseType res = mCDM->StoreAtscLicense(
identifier, getRequestedSecurityLevel(), key_set_id, license_data);
return toNdkScopedAStatus(mapCdmResponseType(res));
} else {
ALOGE("App set unknown string property %s", name.c_str());
return toNdkScopedAStatus(Status::ERROR_DRM_CANNOT_HANDLE);
@@ -1871,6 +1898,57 @@ bool WVDrmPlugin::isProvisioned(wvcdm::CdmSecurityLevel securityLevel,
return mCDM->IsProvisioned(securityLevel, origin, spoid, atsc_mode_enabled);
}
WvStatus WVDrmPlugin::parseAtscLicenseData(
const std::string& in_value,
std::string* key_set_id,
std::string* serialized_license_data) {
if (key_set_id == nullptr) {
ALOGE("key_set_id null");
return WvStatus(Status::ERROR_DRM_CANNOT_HANDLE);
}
if (serialized_license_data == nullptr) {
ALOGE("serialized_license_data null");
return WvStatus(Status::ERROR_DRM_CANNOT_HANDLE);
}
if (in_value.compare(0,
strlen(wvcdm::ATSC_KEY_SET_ID_PREFIX),
&wvcdm::ATSC_KEY_SET_ID_PREFIX[0]) != 0) {
ALOGE("ATSC license input does not conform to expectations. Key set does "
"not have a valid ATSC Key set prefix %s", in_value.c_str());
return WvStatus(Status::BAD_VALUE);
}
const char kColon = ':';
const size_t pos = in_value.find(kColon);
if (pos == std::string::npos) {
ALOGE("ATSC license input does not conform to expectations. Missing colon "
"= %s", in_value.c_str());
return WvStatus(Status::BAD_VALUE);
}
if (pos == in_value.length()) {
ALOGE("ATSC license input does not conform to expectations. No data after "
"colon");
return WvStatus(Status::BAD_VALUE);
}
*key_set_id = in_value.substr(0, pos);
const std::vector<uint8_t> license_data_binary =
wvutil::Base64Decode(in_value.substr(pos+1));
if (license_data_binary.empty()) {
ALOGE("ATSC license input does not conform to expectations. License data "
"failed to decode from Base64");
return WvStatus(Status::BAD_VALUE);
}
serialized_license_data->assign(license_data_binary.begin(),
license_data_binary.end());
return WvStatus(Status::OK);
}
WvStatus WVDrmPlugin::mapAndNotifyOfCdmResponseType(
const vector<uint8_t>& sessionId, CdmResponseType res) {
notifyOfCdmResponseType(sessionId, res);

View File

@@ -239,6 +239,11 @@ public:
MOCK_METHOD(CdmResponseType, RemoveOfflineLicense,
(const std::string &, CdmSecurityLevel, const CdmIdentifier &),
(override));
MOCK_METHOD(CdmResponseType, StoreAtscLicense,
(const CdmIdentifier &, wvcdm::RequestedSecurityLevel,
const std::string &, const std::string &),
(override));
};
class MockCrypto : public WVGenericCryptoInterface {
@@ -2909,6 +2914,253 @@ TEST_F(WVDrmPluginHalTest, RemoveOfflineLicense) {
EXPECT_TRUE(ret.isOk());
}
TEST_F(WVDrmPluginHalTest, CanStoreAtscLicense) {
android::sp<StrictMock<MockCDM>> cdm = new StrictMock<MockCDM>();
StrictMock<MockCrypto> crypto;
std::string appPackageName = "com.test.package";
const CdmClientPropertySet *propertySet = nullptr;
CdmIdentifier mCdmIdAtscModeSet;
// Generate ATSC keyset and license data
uint8_t atscKeySetIdRaw[kKeySetIdSize];
std::vector<uint8_t> licenseDataRaw;
const size_t kLicenseDataSize = 512;
licenseDataRaw.resize(kLicenseDataSize);
FILE *fp = fopen("/dev/urandom", "r");
ASSERT_NE(fp, nullptr) << "Failed to open /dev/urandom";
fread(atscKeySetIdRaw, sizeof(uint8_t), sizeof(atscKeySetIdRaw), fp);
fread(&licenseDataRaw[0], sizeof(uint8_t), licenseDataRaw.size(), fp);
fclose(fp);
// Generate an ATSC key set ID of the form <ATSC prefix>+<random hex>
// with a total length of kKeySetIdSize
std::string atscKeySetId(wvcdm::ATSC_KEY_SET_ID_PREFIX);
atscKeySetId.append(wvutil::HexEncode(atscKeySetIdRaw, kKeySetIdSize));
atscKeySetId.resize(kKeySetIdSize);
memcpy(atscKeySetIdRaw, atscKeySetId.c_str(), sizeof(atscKeySetIdRaw));
std::string licenseDataBase64 = Base64Encode(licenseDataRaw);
std::string atscLicenseData = atscKeySetId + ":" + licenseDataBase64;
// Provide expected mock behavior
{
// Provide expected behavior in response to OpenSession and store the
// property set
EXPECT_CALL(*cdm, OpenSession(_, _, _, _, _))
.WillOnce(DoAll(SetArgPointee<4>(mCdmSessionId),
SaveArg<1>(&propertySet),
SaveArg<2>(&mCdmIdAtscModeSet),
testing::Return(CdmResponseType(wvcdm::NO_ERROR))));
// Provide expected behavior when plugin requests session control info
EXPECT_CALL(*cdm, QueryOemCryptoSessionId(mCdmSessionId, _))
.WillRepeatedly(Invoke(setSessionIdOnMap<4>));
EXPECT_CALL(
*cdm, StoreAtscLicense(HasOrigin(EMPTY_ORIGIN), wvcdm::kLevelDefault,
ElementsAreArray(atscKeySetIdRaw, kKeySetIdSize),
ElementsAreArray(licenseDataRaw)))
.Times(1);
EXPECT_CALL(*cdm, CloseSession(_)).Times(1);
}
std::shared_ptr<WVDrmPlugin> plugin = ::ndk::SharedRefBase::make<WVDrmPlugin>(
cdm.get(), appPackageName, &crypto, false);
// Test turning on ATSC mode
auto ret =
plugin->setPropertyString(std::string("atscMode"), std::string("enable"));
EXPECT_TRUE(ret.isOk());
ret = plugin->openSession(SecurityLevel::DEFAULT, &sessionId);
EXPECT_TRUE(ret.isOk());
ASSERT_THAT(propertySet, NotNull());
EXPECT_TRUE(propertySet->use_atsc_mode());
EXPECT_EQ(mCdmIdAtscModeSet.app_package_name, wvcdm::ATSC_APP_PACKAGE_NAME);
ret = plugin->setPropertyString(std::string("storeAtscLicense"),
atscLicenseData);
EXPECT_TRUE(ret.isOk());
ret = plugin->closeSession(sessionId);
EXPECT_TRUE(ret.isOk());
}
TEST_F(WVDrmPluginHalTest, FailsToStoreAtscLicense) {
android::sp<StrictMock<MockCDM>> cdm1 = new StrictMock<MockCDM>();
android::sp<StrictMock<MockCDM>> cdm2 = new StrictMock<MockCDM>();
android::sp<StrictMock<MockCDM>> cdm3 = new StrictMock<MockCDM>();
StrictMock<MockCrypto> crypto;
std::string appPackageName = "com.test.package";
const CdmClientPropertySet *propertySet = nullptr;
CdmIdentifier mCdmIdAtscModeSet;
// Generate ATSC keyset and license data
uint8_t atscKeySetIdRaw[kKeySetIdSize];
std::vector<uint8_t> licenseDataRaw;
const size_t kLicenseDataSize = 512;
licenseDataRaw.resize(kLicenseDataSize);
FILE *fp = fopen("/dev/urandom", "r");
ASSERT_NE(fp, nullptr) << "Failed to open /dev/urandom";
fread(atscKeySetIdRaw, sizeof(uint8_t), sizeof(atscKeySetIdRaw), fp);
fread(&licenseDataRaw[0], sizeof(uint8_t), licenseDataRaw.size(), fp);
fclose(fp);
std::string atscKeySetId(wvcdm::ATSC_KEY_SET_ID_PREFIX);
atscKeySetId.append(wvutil::HexEncode(&atscKeySetIdRaw[0], kKeySetIdSize));
atscKeySetId.resize(kKeySetIdSize);
memcpy(atscKeySetIdRaw, atscKeySetId.c_str(), sizeof(atscKeySetIdRaw));
std::string licenseDataBase64 = Base64Encode(licenseDataRaw);
std::string atscLicenseData = atscKeySetId + ":" + licenseDataBase64;
// Provide expected mock behavior
{
// No mock behavior expected for plugin1/cdm1
// For plugin2/cdm2
// Provide expected behavior in response to OpenSession and store the
// property set
EXPECT_CALL(*cdm2, OpenSession(_, _, _, _, _))
.WillOnce(DoAll(SetArgPointee<4>(mCdmSessionId),
SaveArg<1>(&propertySet),
SaveArg<2>(&mCdmIdAtscModeSet),
testing::Return(CdmResponseType(wvcdm::NO_ERROR))));
// Provide expected behavior when plugin2 requests session control info
EXPECT_CALL(*cdm2, QueryOemCryptoSessionId(mCdmSessionId, _))
.WillRepeatedly(Invoke(setSessionIdOnMap<4>));
EXPECT_CALL(*cdm2, CloseSession(_)).Times(1);
// For plugin3/cdm3
// Provide expected behavior in response to OpenSession and store the
// property set
EXPECT_CALL(*cdm3, OpenSession(_, _, _, _, _))
.WillOnce(DoAll(SetArgPointee<4>(mCdmSessionId),
SaveArg<1>(&propertySet),
SaveArg<2>(&mCdmIdAtscModeSet),
testing::Return(CdmResponseType(wvcdm::NO_ERROR))));
// Provide expected behavior when plugin3 requests session control info
EXPECT_CALL(*cdm3, QueryOemCryptoSessionId(mCdmSessionId, _))
.WillRepeatedly(Invoke(setSessionIdOnMap<4>));
EXPECT_CALL(*cdm3, CloseSession(_)).Times(1);
}
// Try storing an ATSC license without opening a session. Should fail.
std::shared_ptr<WVDrmPlugin> plugin1 =
::ndk::SharedRefBase::make<WVDrmPlugin>(
cdm1.get(), appPackageName.c_str(), &crypto, false);
auto ret = plugin1->setPropertyString(std::string("storeAtscLicense"),
atscLicenseData);
EXPECT_TRUE(!ret.isOk());
EXPECT_EQ(static_cast<int>(Status::ERROR_DRM_CANNOT_HANDLE),
ret.getServiceSpecificError());
// Try storing an ATSC license without setting ATSC mode. Should fail.
std::shared_ptr<WVDrmPlugin> plugin2 =
::ndk::SharedRefBase::make<WVDrmPlugin>(
cdm2.get(), appPackageName.c_str(), &crypto, false);
ret = plugin2->openSession(SecurityLevel::DEFAULT, &sessionId);
EXPECT_TRUE(ret.isOk());
ASSERT_THAT(propertySet, NotNull());
EXPECT_FALSE(propertySet->use_atsc_mode());
EXPECT_NE(mCdmIdAtscModeSet.app_package_name, wvcdm::ATSC_APP_PACKAGE_NAME);
ret = plugin2->setPropertyString(std::string("storeAtscLicense"),
atscLicenseData);
EXPECT_TRUE(!ret.isOk());
EXPECT_EQ(static_cast<int>(Status::ERROR_DRM_CANNOT_HANDLE),
ret.getServiceSpecificError());
ret = plugin2->closeSession(sessionId);
EXPECT_TRUE(ret.isOk());
// Try storing an ATSC license
// (a) Without ATSC key set prefix
// (b) Only ATSC key set
// (c) Only license data
// (d) Invalid Base64 license data
// (e) No ATSC key set
// (f) No license data
// All these should fail.
std::shared_ptr<WVDrmPlugin> plugin3 =
::ndk::SharedRefBase::make<WVDrmPlugin>(
cdm3.get(), appPackageName.c_str(), &crypto, false);
// Test turning on ATSC mode
ret = plugin3->setPropertyString(std::string("atscMode"),
std::string("enable"));
EXPECT_TRUE(ret.isOk());
ret = plugin3->openSession(SecurityLevel::DEFAULT, &sessionId);
EXPECT_TRUE(ret.isOk());
ASSERT_THAT(propertySet, NotNull());
EXPECT_TRUE(propertySet->use_atsc_mode());
EXPECT_EQ(mCdmIdAtscModeSet.app_package_name, wvcdm::ATSC_APP_PACKAGE_NAME);
// (a) No ATSC key set prefix
atscKeySetId.assign(wvutil::HexEncode(&atscKeySetIdRaw[0], kKeySetIdSize));
atscKeySetId.resize(kKeySetIdSize);
atscLicenseData = atscKeySetId + ":" + licenseDataBase64;
ret = plugin3->setPropertyString(std::string("storeAtscLicense"),
atscLicenseData);
EXPECT_TRUE(!ret.isOk());
EXPECT_EQ(static_cast<int>(Status::BAD_VALUE), ret.getServiceSpecificError());
// (b) Only ATSC key set
atscKeySetId.assign(wvcdm::ATSC_KEY_SET_ID_PREFIX);
atscKeySetId.append(wvutil::HexEncode(&atscKeySetIdRaw[0], kKeySetIdSize));
atscKeySetId.resize(kKeySetIdSize);
ret =
plugin3->setPropertyString(std::string("storeAtscLicense"), atscKeySetId);
EXPECT_TRUE(!ret.isOk());
// (c) Only license data
ret = plugin3->setPropertyString(std::string("storeAtscLicense"),
licenseDataBase64);
EXPECT_TRUE(!ret.isOk());
// (d) Invalid Base64 license data
licenseDataBase64.append("$");
atscLicenseData = atscKeySetId + ":" + licenseDataBase64;
ret = plugin3->setPropertyString(std::string("storeAtscLicense"),
atscLicenseData);
EXPECT_TRUE(!ret.isOk());
// (e) No ATSC key set
licenseDataBase64 = Base64Encode(licenseDataRaw);
atscLicenseData = ":" + licenseDataBase64;
ret = plugin3->setPropertyString(std::string("storeAtscLicense"),
atscLicenseData);
EXPECT_TRUE(!ret.isOk());
// (f) No license data
atscLicenseData = atscKeySetId + ":";
ret = plugin3->setPropertyString(std::string("storeAtscLicense"),
atscLicenseData);
EXPECT_TRUE(!ret.isOk());
ret = plugin3->closeSession(sessionId);
EXPECT_TRUE(ret.isOk());
}
} // namespace widevine
} // namespace drm
} // namespace hardware