Merge "Handle SPOID calculation for L3" into rvc-dev am: 25d1646138 am: dffadd204c am: 61f64a00c6

Original change: https://googleplex-android-review.googlesource.com/c/platform/vendor/widevine/+/11736137

Change-Id: I1830edae406e16b0c2c47290e660ae5cb3537f04
This commit is contained in:
Rahul Frias
2020-06-22 18:34:59 +00:00
committed by Automerger Merge Worker
5 changed files with 470 additions and 13 deletions

View File

@@ -99,6 +99,9 @@ class WvContentDecryptionModule : public android::RefBase, public TimerHandler {
virtual CdmResponseType Unprovision(CdmSecurityLevel level,
const CdmIdentifier& identifier);
virtual bool IsProvisioned(CdmSecurityLevel level, const std::string& origin,
const std::string& spoid, bool atsc_mode_enabled);
// Secure stop related methods
virtual CdmResponseType GetUsageInfo(const std::string& app_id,
const CdmIdentifier& identifier,

View File

@@ -240,6 +240,18 @@ CdmResponseType WvContentDecryptionModule::Unprovision(
return cdm_engine->Unprovision(level);
}
bool WvContentDecryptionModule::IsProvisioned(CdmSecurityLevel security_level,
const std::string& origin,
const std::string& spoid,
bool atsc_mode_enabled) {
FileSystem file_system;
file_system.set_origin(origin);
file_system.set_identifier(spoid + origin);
DeviceFiles device_files(&file_system);
device_files.Init(security_level);
return device_files.HasCertificate(atsc_mode_enabled);
}
CdmResponseType WvContentDecryptionModule::GetUsageInfo(
const std::string& app_id, const CdmIdentifier& identifier,
CdmUsageInfo* usage_info) {

View File

@@ -413,12 +413,15 @@ struct WVDrmPlugin : public IDrmPlugin, IDrmPluginListener,
const WVDrmPlugin& mParent;
Status calculateSpoid();
Status calculateSpoid(const std::string& deviceID, std::string* spoid);
// Gets the device-unique ID from OEMCrypto. This must be private, since
// this value must not be exposed to applications on SPOID devices. Code
// outside this class should use getDeviceUniqueId() to get the
// application-safe device-unique ID.
Status getOemcryptoDeviceId(std::string* id);
Status getOemcryptoDeviceId(wvcdm::SecurityLevel securityLevel,
std::string* id);
// The unique identifier is meant to ensure that two clients with the
// same spoid, origin and app package name still get different cdm engine
@@ -450,6 +453,11 @@ struct WVDrmPlugin : public IDrmPlugin, IDrmPluginListener,
Status queryProperty(const std::string& property,
std::vector<uint8_t>& vector_value) const;
bool isProvisioned(wvcdm::CdmSecurityLevel securityLevel,
const std::string& origin,
const std::string& spoid,
bool atsc_mode_enabled) const;
Status mapAndNotifyOfCdmResponseType(const std::vector<uint8_t>& sessionId,
CdmResponseType res);

View File

@@ -2006,6 +2006,13 @@ Status WVDrmPlugin::queryProperty(const std::string& property,
return Status::OK;
}
bool WVDrmPlugin::isProvisioned(wvcdm::CdmSecurityLevel securityLevel,
const std::string& origin,
const std::string& spoid,
bool atsc_mode_enabled) const {
return mCDM->IsProvisioned(securityLevel, origin, spoid, atsc_mode_enabled);
}
Status WVDrmPlugin::mapAndNotifyOfCdmResponseType(
const std::vector<uint8_t>& sessionId, CdmResponseType res) {
notifyOfCdmResponseType(sessionId, res);
@@ -2189,23 +2196,97 @@ bool WVDrmPlugin::CdmIdentifierBuilder::set_use_atsc_mode(bool enable) {
}
Status WVDrmPlugin::CdmIdentifierBuilder::calculateSpoid() {
if (mUseSpoid) {
std::string deviceId;
if (!mUseSpoid)
return Status::OK;
// Calculate SPOID for default security level if appropriate
std::string deviceId;
if (mParent.getRequestedSecurityLevel() == wvcdm::kLevelDefault) {
Status res = getOemcryptoDeviceId(&deviceId);
if (res != Status::OK) return res;
uint8_t hash[SHA256_DIGEST_LENGTH];
SHA256_CTX ctx;
SHA256_Init(&ctx);
SHA256_Update(&ctx, deviceId.data(), deviceId.length());
SHA256_Update(&ctx, mCdmIdentifier.app_package_name.data(),
mCdmIdentifier.app_package_name.length());
SHA256_Update(&ctx, origin().data(), origin().length());
SHA256_Final(hash, &ctx);
mCdmIdentifier.spoid =
std::string(reinterpret_cast<char*>(hash), SHA256_DIGEST_LENGTH);
return calculateSpoid(deviceId, &mCdmIdentifier.spoid);
}
// If requested security level is L3, possibilities are
// (a) L3 has not been provisioned
// (b) L3 was provisioned with L3 device ID in the CdmIdentifier
// (c) L3 was provisioned (incorrectly) with L1 device ID in the CdmIdentifier
// Check (b) first. Get L3 device ID, calculate SPOID and if provisioned
// with this SPOID, return this SPOID.
// Check (c) next. Get L1 device ID, calculate SPOID and if provisioned
// with this SPOID, return this SPOID.
// On any errors in (c) or not provisioned return L3 SPOID.
Status res = getOemcryptoDeviceId(wvcdm::kLevel3, &deviceId);
if (res != Status::OK) return res;
std::string spoidL3;
res = calculateSpoid(deviceId, &spoidL3);
if (res != Status::OK) return res;
bool atsc_mode_enabled =
mCdmIdentifier.app_package_name == wvcdm::ATSC_APP_PACKAGE_NAME;
if (mParent.isProvisioned(wvcdm::kSecurityLevelL3, origin(), spoidL3,
atsc_mode_enabled)) {
mCdmIdentifier.spoid = spoidL3;
return Status::OK;
}
// Not provisioned with CdmIdentifier containing SPOID with L3 device ID.
// Try SPOID with L1 device ID.
std::string deviceIdLevelDefault;
res = getOemcryptoDeviceId(wvcdm::kLevelDefault, &deviceIdLevelDefault);
if (res != Status::OK) {
mCdmIdentifier.spoid = spoidL3;
return Status::OK;
}
// If the L3 and default security level IDs are identical then the
// device does not support L1.
if (deviceId == deviceIdLevelDefault) {
mCdmIdentifier.spoid = spoidL3;
return Status::OK;
}
std::string spoidLevelDefault;
res = calculateSpoid(deviceIdLevelDefault, &spoidLevelDefault);
if (res != Status::OK) {
mCdmIdentifier.spoid = spoidL3;
return Status::OK;
}
if (mParent.isProvisioned(wvcdm::kSecurityLevelL1, origin(),
spoidLevelDefault, atsc_mode_enabled)) {
mCdmIdentifier.spoid = spoidLevelDefault;
return Status::OK;
}
// Not provisioned with CdmIdentifier containing SPOID with L1 or L3
// device ID. Return L3 SPOID.
mCdmIdentifier.spoid = spoidL3;
return Status::OK;
}
Status WVDrmPlugin::CdmIdentifierBuilder::calculateSpoid(
const std::string& deviceId, std::string* spoid) {
if (spoid == nullptr)
return Status::ERROR_DRM_CANNOT_HANDLE;
if (!mUseSpoid) {
spoid->clear();
return Status::OK;
}
uint8_t hash[SHA256_DIGEST_LENGTH];
SHA256_CTX ctx;
SHA256_Init(&ctx);
SHA256_Update(&ctx, deviceId.data(), deviceId.length());
SHA256_Update(&ctx, mAppPackageName.data(), mAppPackageName.length());
SHA256_Update(&ctx, origin().data(), origin().length());
SHA256_Final(hash, &ctx);
*spoid = std::string(reinterpret_cast<char*>(hash), SHA256_DIGEST_LENGTH);
return Status::OK;
}
@@ -2214,6 +2295,12 @@ Status WVDrmPlugin::CdmIdentifierBuilder::getOemcryptoDeviceId(
return mParent.queryProperty(wvcdm::QUERY_KEY_DEVICE_ID, *id);
}
Status WVDrmPlugin::CdmIdentifierBuilder::getOemcryptoDeviceId(
wvcdm::SecurityLevel securityLevel,
std::string* id) {
return mParent.queryProperty(securityLevel, wvcdm::QUERY_KEY_DEVICE_ID, *id);
}
uint32_t WVDrmPlugin::CdmIdentifierBuilder::getNextUniqueId() {
// Start with 1. 0 is reserved for the default cdm identifier.
static uint32_t unique_id = 1;

View File

@@ -190,6 +190,9 @@ class MockCDM : public WvContentDecryptionModule {
MOCK_METHOD2(Unprovision, CdmResponseType(CdmSecurityLevel,
const CdmIdentifier&));
MOCK_METHOD3(IsProvisioned, bool(CdmSecurityLevel, const std::string&,
const std::string&));
MOCK_METHOD3(GetUsageInfo, CdmResponseType(const std::string&,
const CdmIdentifier&,
CdmUsageInfo*));
@@ -1681,6 +1684,350 @@ TEST_F(WVDrmPluginTest, CompliesWithSpoidVariability) {
}
}
TEST_F(WVDrmPluginTest, ReturnsSameL1Spoid) {
StrictMock<MockCrypto> crypto;
std::string kL1DeviceId = kDeviceId + "L1";
const std::string kAppPackageName("com.google.widevine");
constexpr size_t kSpoidQuery = 2;
std::vector<uint8_t> spoid[kSpoidQuery];
android::sp<StrictMock<MockCDM>> cdm = new StrictMock<MockCDM>();
EXPECT_CALL(*cdm, QueryStatus(wvcdm::kLevelDefault, QUERY_KEY_DEVICE_ID, _))
.Times(kSpoidQuery)
.WillRepeatedly(DoAll(SetArgPointee<2>(kL1DeviceId),
testing::Return(wvcdm::NO_ERROR)));
// Provide expected behavior to support session creation
EXPECT_CALL(*cdm, OpenSession(StrEq("com.widevine"), _, _, _, _))
.Times(kSpoidQuery)
.WillRepeatedly(DoAll(SetArgPointee<4>(cdmSessionId),
testing::Return(wvcdm::NO_ERROR)));
EXPECT_CALL(*cdm, QueryOemCryptoSessionId(cdmSessionId, _))
.Times(kSpoidQuery)
.WillRepeatedly(Invoke(setSessionIdOnMap<4>));
EXPECT_CALL(*cdm, CloseSession(_))
.Times(kSpoidQuery);
// Open a session twice with the same security level, app package name and
// origin and make sure the spoids returned are the same
for (int i = 0; i < kSpoidQuery; ++i) {
WVDrmPlugin plugin(cdm.get(), kAppPackageName, &crypto, true);
plugin.openSession([&](Status status, hidl_vec<uint8_t> hSessionId) {
ASSERT_EQ(Status::OK, status);
sessionId.clear();
sessionId.assign(hSessionId.data(),
hSessionId.data() + hSessionId.size());
});
EXPECT_EQ(Status::OK, plugin.closeSession(toHidlVec(sessionId)));
plugin.getPropertyByteArray(
hidl_string("deviceUniqueId"),
[&](Status status, hidl_vec<uint8_t> vectorResult) {
ASSERT_EQ(Status::OK, status);
spoid[i] = vectorResult;
});
}
EXPECT_EQ(spoid[0], spoid[1]);
}
TEST_F(WVDrmPluginTest, ReturnsL3SpoidsWhenL3ProvisionedUsingL3Spoid) {
StrictMock<MockCrypto> crypto;
std::string kL3DeviceId = kDeviceId + "L3";
const std::string kAppPackageName("com.google.widevine");
constexpr size_t kSpoidQuery = 2;
std::vector<uint8_t> spoid[kSpoidQuery];
android::sp<StrictMock<MockCDM>> cdm = new StrictMock<MockCDM>();
EXPECT_CALL(*cdm, QueryStatus(wvcdm::kLevel3, QUERY_KEY_DEVICE_ID, _))
.Times(kSpoidQuery)
.WillRepeatedly(DoAll(SetArgPointee<2>(kL3DeviceId),
testing::Return(wvcdm::NO_ERROR)));
// Provide expected behavior to support session creation
EXPECT_CALL(*cdm, OpenSession(StrEq("com.widevine"), _, _, _, _))
.Times(kSpoidQuery)
.WillRepeatedly(DoAll(SetArgPointee<4>(cdmSessionId),
testing::Return(wvcdm::NO_ERROR)));
EXPECT_CALL(*cdm, QueryOemCryptoSessionId(cdmSessionId, _))
.Times(kSpoidQuery)
.WillRepeatedly(Invoke(setSessionIdOnMap<4>));
EXPECT_CALL(*cdm, IsProvisioned(wvcdm::kSecurityLevelL3, _, _))
.Times(kSpoidQuery)
.WillRepeatedly(testing::Return(true));
EXPECT_CALL(*cdm, CloseSession(_))
.Times(kSpoidQuery);
// The device is provisioned at L3 security level. Open a session twice
// and make sure that the spoids returned are the same when the
// session are opened at the same security level(L3), app package name
// and origin
for (size_t i = 0; i < kSpoidQuery; ++i) {
WVDrmPlugin plugin(cdm.get(), kAppPackageName, &crypto, true);
// Forcing L3
Status status = plugin.setPropertyString(hidl_string("securityLevel"),
hidl_string("L3"));
ASSERT_EQ(Status::OK, status);
plugin.openSession([&](Status status, hidl_vec<uint8_t> hSessionId) {
ASSERT_EQ(Status::OK, status);
sessionId.clear();
sessionId.assign(hSessionId.data(),
hSessionId.data() + hSessionId.size());
});
EXPECT_EQ(Status::OK, plugin.closeSession(toHidlVec(sessionId)));
plugin.getPropertyByteArray(
hidl_string("deviceUniqueId"),
[&](Status status, hidl_vec<uint8_t> vectorResult) {
ASSERT_EQ(Status::OK, status);
spoid[i] = vectorResult;
});
}
EXPECT_EQ(spoid[0], spoid[1]);
}
TEST_F(WVDrmPluginTest, ReturnsL3SpoidsWhenL3Unprovisioned) {
StrictMock<MockCrypto> crypto;
std::string kL1DeviceId = kDeviceId + "L1";
std::string kL3DeviceId = kDeviceId + "L3";
const std::string kAppPackageName("com.google.widevine");
constexpr size_t kSpoidQuery = 2;
std::vector<uint8_t> spoid[kSpoidQuery];
android::sp<StrictMock<MockCDM>> cdm = new StrictMock<MockCDM>();
EXPECT_CALL(*cdm, QueryStatus(wvcdm::kLevelDefault, QUERY_KEY_DEVICE_ID, _))
.WillOnce(DoAll(SetArgPointee<2>(kL1DeviceId),
testing::Return(wvcdm::NO_ERROR)));
EXPECT_CALL(*cdm, QueryStatus(wvcdm::kLevel3, QUERY_KEY_DEVICE_ID, _))
.Times(kSpoidQuery)
.WillRepeatedly(DoAll(SetArgPointee<2>(kL3DeviceId),
testing::Return(wvcdm::NO_ERROR)));
// Provide expected behavior to support session creation
EXPECT_CALL(*cdm, OpenSession(StrEq("com.widevine"), _, _, _, _))
.Times(kSpoidQuery)
.WillOnce(testing::Return(wvcdm::NEED_PROVISIONING))
.WillOnce(DoAll(SetArgPointee<4>(cdmSessionId),
testing::Return(wvcdm::NO_ERROR)));
EXPECT_CALL(*cdm, QueryOemCryptoSessionId(cdmSessionId, _))
.WillOnce(Invoke(setSessionIdOnMap<4>));
EXPECT_CALL(*cdm, IsProvisioned(wvcdm::kSecurityLevelL3, _, _))
.WillOnce(testing::Return(false))
.WillOnce(testing::Return(true));
EXPECT_CALL(*cdm, IsProvisioned(wvcdm::kSecurityLevelL1, _, _))
.WillOnce(testing::Return(false));
EXPECT_CALL(*cdm, CloseSession(_))
.Times(1);
// This device is unprovisioned at the L3 security level. An attempt to
// open a session results in an ERROR_DRM_NOT_PROVISIONED error.
// Spoids are computed using device Unique IDs at each security level.
// Since provisioning has not occurred for either spoid, the spoid
// using the L3 device ID is used.
android::sp<WVDrmPlugin> plugin = new WVDrmPlugin(cdm.get(), kAppPackageName,
&crypto, true);
// Force L3
Status status = plugin->setPropertyString(hidl_string("securityLevel"),
hidl_string("L3"));
ASSERT_EQ(Status::OK, status);
plugin->openSession([&](Status status, hidl_vec<uint8_t>) {
ASSERT_EQ(Status::ERROR_DRM_NOT_PROVISIONED, status);
});
plugin->getPropertyByteArray(
hidl_string("deviceUniqueId"),
[&](Status status, hidl_vec<uint8_t> vectorResult) {
ASSERT_EQ(Status::OK, status);
spoid[0] = vectorResult;
});
// Try to open a session again. If provisioning took place, this time the
// attempt will be successful. Retrieve the spoid. This time only
// the device unique ID at the L3 security level will be queried.
// Confirm that it matches the spoid queried earlier.
plugin = new WVDrmPlugin(cdm.get(), kAppPackageName, &crypto, true);
// Force L3
status = plugin->setPropertyString(hidl_string("securityLevel"),
hidl_string("L3"));
ASSERT_EQ(Status::OK, status);
plugin->openSession([&](Status status, hidl_vec<uint8_t> hSessionId) {
ASSERT_EQ(Status::OK, status);
sessionId.clear();
sessionId.assign(hSessionId.data(),
hSessionId.data() + hSessionId.size());
});
EXPECT_EQ(Status::OK, plugin->closeSession(toHidlVec(sessionId)));
plugin->getPropertyByteArray(
hidl_string("deviceUniqueId"),
[&](Status status, hidl_vec<uint8_t> vectorResult) {
ASSERT_EQ(Status::OK, status);
spoid[1] = vectorResult;
});
EXPECT_EQ(spoid[0], spoid[1]);
}
TEST_F(WVDrmPluginTest, ReturnsL1SpoidsWhenL3ProvisionedUsingL1Spoid) {
StrictMock<MockCrypto> crypto;
std::string kL1DeviceId = kDeviceId + "L1";
std::string kL3DeviceId = kDeviceId + "L3";
const std::string kAppPackageName("com.google.widevine");
constexpr size_t kSpoidQuery = 2;
std::vector<uint8_t> spoidL1;
std::vector<uint8_t> spoidL3[kSpoidQuery];
// Set expectations for the first plugin instance
android::sp<StrictMock<MockCDM>> cdm1 = new StrictMock<MockCDM>();
EXPECT_CALL(*cdm1, QueryStatus(wvcdm::kLevelDefault, QUERY_KEY_DEVICE_ID, _))
.WillOnce(DoAll(SetArgPointee<2>(kL1DeviceId),
testing::Return(wvcdm::NO_ERROR)));
// Provide expected behavior to support session creation
EXPECT_CALL(*cdm1, OpenSession(StrEq("com.widevine"), _, _, _, _))
.WillOnce(DoAll(SetArgPointee<4>(cdmSessionId),
testing::Return(wvcdm::NO_ERROR)))
.WillOnce(testing::Return(wvcdm::NEED_PROVISIONING));
EXPECT_CALL(*cdm1, QueryOemCryptoSessionId(cdmSessionId, _))
.WillOnce(Invoke(setSessionIdOnMap<4>));
EXPECT_CALL(*cdm1, CloseSession(_));
// Open a session at L1 security level. The spoid is now computed with
// the L1 device unique ID
android::sp<WVDrmPlugin> plugin = new WVDrmPlugin(cdm1.get(), kAppPackageName,
&crypto, true);
plugin->openSession([&](Status status, hidl_vec<uint8_t> hSessionId) {
ASSERT_EQ(Status::OK, status);
sessionId.clear();
sessionId.assign(hSessionId.data(),
hSessionId.data() + hSessionId.size());
});
EXPECT_EQ(Status::OK, plugin->closeSession(toHidlVec(sessionId)));
plugin->getPropertyByteArray(
hidl_string("deviceUniqueId"),
[&](Status status, hidl_vec<uint8_t> vectorResult) {
ASSERT_EQ(Status::OK, status);
spoidL1 = vectorResult;
});
// Force L3. This should not be allowed since the spoid has been
// computed. We defer correcting this, as this might have an
// impact on apps. L3 is not provisioned so a call to openSession
// returns a ERROR_DRM_NOT_PROVISIONED error. No spoid computation takes
// place.
Status status = plugin->setPropertyString(hidl_string("securityLevel"),
hidl_string("L3"));
ASSERT_EQ(Status::OK, status);
plugin->openSession([&](Status status, hidl_vec<uint8_t>) {
ASSERT_EQ(Status::ERROR_DRM_NOT_PROVISIONED, status);
});
plugin->getPropertyByteArray(
hidl_string("deviceUniqueId"),
[&](Status status, hidl_vec<uint8_t> vectorResult) {
ASSERT_EQ(Status::OK, status);
spoidL3[0] = vectorResult;
});
// Set expectations for the second plugin instance
android::sp<StrictMock<MockCDM>> cdm2 = new StrictMock<MockCDM>();
EXPECT_CALL(*cdm2, QueryStatus(wvcdm::kLevelDefault, QUERY_KEY_DEVICE_ID, _))
.WillOnce(DoAll(SetArgPointee<2>(kL1DeviceId),
testing::Return(wvcdm::NO_ERROR)));
EXPECT_CALL(*cdm2, QueryStatus(wvcdm::kLevel3, QUERY_KEY_DEVICE_ID, _))
.WillOnce(DoAll(SetArgPointee<2>(kL3DeviceId),
testing::Return(wvcdm::NO_ERROR)));
// Provide expected behavior to support session creation
EXPECT_CALL(*cdm2, OpenSession(StrEq("com.widevine"), _, _, _, _))
.WillOnce(DoAll(SetArgPointee<4>(cdmSessionId),
testing::Return(wvcdm::NO_ERROR)));
EXPECT_CALL(*cdm2, QueryOemCryptoSessionId(cdmSessionId, _))
.WillOnce(Invoke(setSessionIdOnMap<4>));
EXPECT_CALL(*cdm2, IsProvisioned(wvcdm::kSecurityLevelL1, _, _))
.WillOnce(testing::Return(true));
EXPECT_CALL(*cdm2, IsProvisioned(wvcdm::kSecurityLevelL3, _, _))
.WillOnce(testing::Return(false));
EXPECT_CALL(*cdm2, CloseSession(_));
// Try to open a session again. If provisioning took place, this time the
// attempt will be successful. Spoids are computed using device unique IDs
// from both L1 and L3. The device is provisioned for L3 using the spoid with
// L1 but not L3 device unique ID, so the spoid with L1 device unique ID is
// used.
plugin = new WVDrmPlugin(cdm2.get(), kAppPackageName, &crypto, true);
// Force L3
status = plugin->setPropertyString(hidl_string("securityLevel"),
hidl_string("L3"));
ASSERT_EQ(Status::OK, status);
plugin->openSession([&](Status status, hidl_vec<uint8_t> hSessionId) {
ASSERT_EQ(Status::OK, status);
sessionId.clear();
sessionId.assign(hSessionId.data(),
hSessionId.data() + hSessionId.size());
});
EXPECT_EQ(Status::OK, plugin->closeSession(toHidlVec(sessionId)));
plugin->getPropertyByteArray(
hidl_string("deviceUniqueId"),
[&](Status status, hidl_vec<uint8_t> vectorResult) {
ASSERT_EQ(Status::OK, status);
spoidL3[1] = vectorResult;
});
EXPECT_EQ(spoidL3[0], spoidL3[1]);
EXPECT_EQ(spoidL1, spoidL3[0]);
}
TEST_F(WVDrmPluginTest, FailsGenericMethodsWithoutAnAlgorithmSet) {
android::sp<StrictMock<MockCDM>> cdm = new StrictMock<MockCDM>();
StrictMock<MockCrypto> crypto;