Add decrypt hash support

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

Add ability to query decrypt hash support, set a hash computed over a frame
and retrieve the last error at a later point.

Bug: 34080802
Test: WV unit/integration tests. New tests added to cdm_engine_test,
      libwvdrmdrmplugin_hidl_test and request_license_test.

Change-Id: I7548c8798c873a6af3e1cfc0df57c117e1e474a6
This commit is contained in:
Rahul Frias
2018-12-12 02:04:26 -08:00
parent d44a8016ad
commit 589a3cf27e
21 changed files with 601 additions and 10 deletions

View File

@@ -263,6 +263,8 @@ class WVDrmPlugin : public android::DrmPlugin,
std::string mProvisioningServiceCertificate;
CdmSessionId mDecryptHashSessionId;
status_t queryProperty(const std::string& property,
std::string& stringValue) const;

View File

@@ -411,6 +411,8 @@ struct WVDrmPlugin : public IDrmPlugin, IDrmPluginListener,
std::string mProvisioningServiceCertificate;
wvcdm::CdmSessionId mDecryptHashSessionId;
Status queryProperty(const std::string& property,
std::string& stringValue) const;

View File

@@ -528,6 +528,14 @@ status_t WVDrmPlugin::getPropertyString(const String8& name,
return queryProperty(QUERY_KEY_RESOURCE_RATING_TIER, value);
} else if (name == "oemCryptoBuildInformation") {
return queryProperty(QUERY_KEY_OEMCRYPTO_BUILD_INFORMATION, value);
} else if (name == "decryptHashSupport") {
return queryProperty(QUERY_KEY_DECRYPT_HASH_SUPPORT, value);
} else if (name == "decryptHashError") {
std::string hash_error_string;
CdmResponseType res =
mCDM->GetDecryptHashError(mDecryptHashSessionId, &hash_error_string);
value = hash_error_string.c_str();
return mapCdmResponseType(res);
} else {
ALOGE("App requested unknown string property %s", name.string());
return android::ERROR_DRM_CANNOT_HANDLE;
@@ -630,6 +638,16 @@ status_t WVDrmPlugin::setPropertyString(const String8& name,
} else {
mCdmIdentifier.origin = value.string();
}
} else if (name == "decryptHash") {
CdmSessionId sessionId;
CdmResponseType res =
mCDM->SetDecryptHash(value.string(), &sessionId);
if (wvcdm::NO_ERROR == res) mDecryptHashSessionId = sessionId;
return mapCdmResponseType(res);
} else if (name == "decryptHashSessionId") {
mDecryptHashSessionId = value.string();
} else {
ALOGE("App set unknown string property %s", name.string());
return android::ERROR_DRM_CANNOT_HANDLE;

View File

@@ -1024,6 +1024,11 @@ Return<void> WVDrmPlugin::getPropertyString(const hidl_string& propertyName,
status = queryProperty(wvcdm::QUERY_KEY_RESOURCE_RATING_TIER, value);
} else if (name == "oemCryptoBuildInformation") {
status = queryProperty(wvcdm::QUERY_KEY_OEMCRYPTO_BUILD_INFORMATION, value);
} else if (name == "decryptHashSupport") {
status = queryProperty(wvcdm::QUERY_KEY_DECRYPT_HASH_SUPPORT, value);
} else if (name == "decryptHashError") {
status = mapCdmResponseType(
mCDM->GetDecryptHashError(mDecryptHashSessionId, &value));
} else {
ALOGE("App requested unknown string property %s", name.c_str());
status = Status::ERROR_DRM_CANNOT_HANDLE;
@@ -1166,6 +1171,16 @@ Return<Status> WVDrmPlugin::setPropertyString(const hidl_string& propertyName,
return Status::BAD_VALUE;
}
}
} else if (name == "decryptHash") {
wvcdm::CdmSessionId sessionId;
CdmResponseType res =
mCDM->SetDecryptHash(_value.c_str(), &sessionId);
if (wvcdm::NO_ERROR == res) mDecryptHashSessionId = sessionId;
return mapCdmResponseType(res);
} else if (name == "decryptHashSessionId") {
mDecryptHashSessionId = _value.c_str();
} else {
ALOGE("App set unknown string property %s", name.c_str());
return Status::ERROR_DRM_CANNOT_HANDLE;

View File

@@ -75,6 +75,7 @@ using wvcdm::kLicenseTypeRelease;
using wvcdm::kLicenseTypeStreaming;
using wvcdm::kSecurityLevelL1;
using wvcdm::kSecurityLevelL3;
using wvcdm::Base64Encode;
using wvcdm::CdmAppParameterMap;
using wvcdm::CdmCertificateType;
using wvcdm::CdmClientPropertySet;
@@ -100,6 +101,7 @@ using wvcdm::KEY_SET_ID_PREFIX;
using wvcdm::NEVER_EXPIRES;
using wvcdm::QUERY_KEY_CURRENT_SRM_VERSION;
using wvcdm::QUERY_KEY_DEVICE_ID;
using wvcdm::QUERY_KEY_DECRYPT_HASH_SUPPORT;
using wvcdm::QUERY_KEY_MAX_NUMBER_OF_SESSIONS;
using wvcdm::QUERY_KEY_NUMBER_OF_OPEN_SESSIONS;
using wvcdm::QUERY_KEY_OEMCRYPTO_API_VERSION;
@@ -208,6 +210,8 @@ class MockCDM : public WvContentDecryptionModule {
MOCK_METHOD2(GetMetrics, CdmResponseType(const CdmIdentifier&,
drm_metrics::WvCdmMetrics*));
MOCK_METHOD2(GetDecryptHashError, CdmResponseType(const CdmSessionId&, std::string*));
};
class MockCrypto : public WVGenericCryptoInterface {
@@ -344,7 +348,7 @@ TEST_F(WVDrmPluginTest, OpensSessions_1_1) {
StrictMock<MockCrypto> crypto;
std::string appPackageName;
const CdmClientPropertySet* propertySet = NULL;
const CdmClientPropertySet* propertySet = nullptr;
// Provide expected mock behavior
EXPECT_CALL(*cdm, QueryStatus(_, QUERY_KEY_SECURITY_LEVEL, _))
@@ -655,7 +659,7 @@ TEST_F(WVDrmPluginTest, HandlesPrivacyCertCaseOfAddKey) {
sp<StrictMock<MockDrmPluginListener> > listener =
new StrictMock<MockDrmPluginListener>();
const CdmClientPropertySet* propertySet = NULL;
const CdmClientPropertySet* propertySet = nullptr;
// Provide expected behavior in response to OpenSession and store the
// property set
@@ -1138,6 +1142,10 @@ TEST_F(WVDrmPluginTest, ReturnsExpectedPropertyValues) {
static const std::string cdmVersion = "Infinity Minus 1";
static const std::string resourceRatingTier = "1";
static const std::string oemCryptoBuildInformation = "Mostly Harmless";
static const std::string oemCryptoHashNotSupported = "0";
static const std::string oemCryptoCrcClearBuffer = "1";
static const std::string oemCryptoPartnerDefinedHash = "2";
static const std::string decryptHashErrorBadHashAndFrameNumber = "53, 1";
drm_metrics::WvCdmMetrics expected_metrics;
std::string serialized_metrics = wvcdm::a2bs_hex(kSerializedMetricsHex);
ASSERT_TRUE(expected_metrics.ParseFromString(serialized_metrics));
@@ -1188,10 +1196,18 @@ TEST_F(WVDrmPluginTest, ReturnsExpectedPropertyValues) {
.WillOnce(DoAll(SetArgPointee<2>(resourceRatingTier),
testing::Return(wvcdm::NO_ERROR)));
EXPECT_CALL(*cdm, QueryStatus(_, QUERY_KEY_DECRYPT_HASH_SUPPORT, _))
.WillOnce(DoAll(SetArgPointee<2>("1"),
testing::Return(wvcdm::NO_ERROR)));
EXPECT_CALL(*cdm, QueryStatus(_, QUERY_KEY_OEMCRYPTO_BUILD_INFORMATION, _))
.WillOnce(DoAll(SetArgPointee<2>(oemCryptoBuildInformation),
testing::Return(wvcdm::NO_ERROR)));
EXPECT_CALL(*cdm, GetDecryptHashError(_, _))
.WillOnce(DoAll(SetArgPointee<1>(decryptHashErrorBadHashAndFrameNumber),
testing::Return(wvcdm::NO_ERROR)));
EXPECT_CALL(*cdm, GetMetrics(_, _))
.WillOnce(DoAll(SetArgPointee<1>(expected_metrics),
testing::Return(wvcdm::NO_ERROR)));
@@ -1311,6 +1327,24 @@ TEST_F(WVDrmPluginTest, ReturnsExpectedPropertyValues) {
EXPECT_STREQ(oemCryptoBuildInformation.c_str(), stringResult.c_str());
});
std::stringstream ss;
ss << oemCryptoHashNotSupported << " " << oemCryptoCrcClearBuffer << " "
<< oemCryptoPartnerDefinedHash;
std::string validResults = ss.str();
plugin.getPropertyString(
hidl_string("decryptHashSupport"),
[&](Status status, hidl_string stringResult) {
ASSERT_EQ(Status::OK, status);
EXPECT_EQ(oemCryptoCrcClearBuffer, stringResult);
});
plugin.getPropertyString(
hidl_string("decryptHashError"),
[&](Status status, hidl_string stringResult) {
ASSERT_EQ(Status::OK, status);
EXPECT_EQ(decryptHashErrorBadHashAndFrameNumber, stringResult);
});
// This call occurs before any open session or other call. This means
// that the cdm identifer is not yet sealed, and metrics return empty
// metrics data.
@@ -2065,7 +2099,7 @@ TEST_F(WVDrmPluginTest, ProvidesExpectedDefaultPropertiesToCdm) {
StrictMock<MockCrypto> crypto;
std::string appPackageName;
const CdmClientPropertySet* propertySet = NULL;
const CdmClientPropertySet* propertySet = nullptr;
// Provide expected mock behavior
{
@@ -2104,7 +2138,7 @@ TEST_F(WVDrmPluginTest, CanSetAppId) {
StrictMock<MockCrypto> crypto;
std::string appPackageName;
const CdmClientPropertySet* propertySet = NULL;
const CdmClientPropertySet* propertySet = nullptr;
// Provide expected mock behavior
{
@@ -2205,7 +2239,7 @@ TEST_F(WVDrmPluginTest, CanSetSecurityLevel) {
StrictMock<MockCrypto> crypto;
std::string appPackageName;
const CdmClientPropertySet* propertySet = NULL;
const CdmClientPropertySet* propertySet = nullptr;
EXPECT_CALL(*cdm, QueryStatus(_, QUERY_KEY_SECURITY_LEVEL, _))
.WillOnce(DoAll(SetArgPointee<2>(QUERY_VALUE_SECURITY_LEVEL_L3),
@@ -2335,7 +2369,7 @@ TEST_F(WVDrmPluginTest, CanSetPrivacyMode) {
StrictMock<MockCrypto> crypto;
std::string appPackageName;
const CdmClientPropertySet* propertySet = NULL;
const CdmClientPropertySet* propertySet = nullptr;
// Provide expected mock behavior
{
@@ -2386,7 +2420,7 @@ TEST_F(WVDrmPluginTest, CanSetServiceCertificate) {
StrictMock<MockCrypto> crypto;
std::string appPackageName;
const CdmClientPropertySet* propertySet = NULL;
const CdmClientPropertySet* propertySet = nullptr;
static const size_t kPrivacyCertSize = 256;
uint8_t privacyCertRaw[kPrivacyCertSize];
@@ -2459,7 +2493,7 @@ TEST_F(WVDrmPluginTest, CanSetSessionSharing) {
StrictMock<MockCrypto> crypto;
std::string appPackageName;
const CdmClientPropertySet* propertySet = NULL;
const CdmClientPropertySet* propertySet = nullptr;
// Provide expected mock behavior
{
@@ -2534,7 +2568,7 @@ TEST_F(WVDrmPluginTest, AllowsStoringOfSessionSharingId) {
StrictMock<MockCrypto> crypto;
std::string appPackageName;
CdmClientPropertySet* propertySet = NULL;
CdmClientPropertySet* propertySet = nullptr;
uint32_t sharingId;
FILE* fp = fopen("/dev/urandom", "r");
@@ -2570,6 +2604,126 @@ TEST_F(WVDrmPluginTest, AllowsStoringOfSessionSharingId) {
EXPECT_EQ(sharingId, propertySet->session_sharing_id());
}
TEST_F(WVDrmPluginTest, CanSetDecryptHashProperties) {
android::sp<StrictMock<MockCDM>> cdm = new StrictMock<MockCDM>();
StrictMock<MockCrypto> crypto;
std::string appPackageName;
// Provide expected mock behavior
{
// Provide expected behavior in response to OpenSession and store the
// property set
EXPECT_CALL(*cdm, OpenSession(_, _, _, _, _))
.WillRepeatedly(DoAll(SetArgPointee<4>(cdmSessionId),
testing::Return(wvcdm::NO_ERROR)));
// Provide expected behavior when plugin requests session control info
EXPECT_CALL(*cdm, QueryOemCryptoSessionId(cdmSessionId, _))
.WillRepeatedly(Invoke(setSessionIdOnMap<4>));
EXPECT_CALL(*cdm, CloseSession(_))
.Times(AtLeast(0));
}
WVDrmPlugin plugin(cdm.get(), appPackageName, &crypto, false);
hidl_vec<uint8_t> hSessionId;
hSessionId.setToExternal(sessionIdRaw, kSessionIdSize);
plugin.openSession([&](Status status, hidl_vec<uint8_t> hSessionId) {
ASSERT_EQ(Status::OK, status);
sessionId.clear();
sessionId.assign(hSessionId.data(), hSessionId.data() + hSessionId.size());
});
// CDM expects the string property value to be in the following format:
// "<sessionId>,<frameNumber>,<base64encodedHash>"
static const std::string frameNumber = ",1";
uint32_t hash = 0xbeef; // crc32 hash
std::vector<uint8_t> hashVector(
reinterpret_cast<uint8_t*>(&hash),
reinterpret_cast<uint8_t*>(&hash) + sizeof(uint32_t));
std::string base64EncodedHash = Base64Encode(hashVector);
std::string computedHash(sessionId.begin(), sessionId.end());
computedHash.append(frameNumber.c_str());
computedHash.append(base64EncodedHash.c_str());
Status status = plugin.setPropertyString(hidl_string("decryptHash"),
hidl_string(computedHash));
ASSERT_NE(Status::OK, status);
status = plugin.closeSession(toHidlVec(sessionId));
ASSERT_EQ(Status::OK, status);
}
TEST_F(WVDrmPluginTest, DoesNotSetDecryptHashProperties) {
android::sp<StrictMock<MockCDM>> cdm = new StrictMock<MockCDM>();
StrictMock<MockCrypto> crypto;
std::string appPackageName;
// Provide expected mock behavior
{
// Provide expected behavior in response to OpenSession and store the
// property set
EXPECT_CALL(*cdm, OpenSession(_, _, _, _, _))
.WillRepeatedly(DoAll(SetArgPointee<4>(cdmSessionId),
testing::Return(wvcdm::NO_ERROR)));
// Provide expected behavior when plugin requests session control info
EXPECT_CALL(*cdm, QueryOemCryptoSessionId(cdmSessionId, _))
.WillRepeatedly(Invoke(setSessionIdOnMap<4>));
EXPECT_CALL(*cdm, CloseSession(_))
.Times(AtLeast(0));
}
WVDrmPlugin plugin(cdm.get(), appPackageName, &crypto, false);
hidl_vec<uint8_t> hSessionId;
hSessionId.setToExternal(sessionIdRaw, kSessionIdSize);
plugin.openSession([&](Status status, hidl_vec<uint8_t> hSessionId) {
ASSERT_EQ(Status::OK, status);
sessionId.clear();
sessionId.assign(hSessionId.data(), hSessionId.data() + hSessionId.size());
});
// CDM expects the string property value to be in the following format:
// "<sessionId>,<frameNumber>,<base64encodedHash>"
static const std::string frameNumber = ",1";
static const std::string hash = ",AZaz0+,/";
std::string value(sessionId.begin(), sessionId.end());
value.append(frameNumber.c_str());
// Tests for missing token handling
Status status = plugin.setPropertyString(hidl_string("decryptHash"),
hidl_string(value));
EXPECT_NE(Status::OK, status);
// Tests for empty token
value.append(",");
status = plugin.setPropertyString(hidl_string("decryptHash"),
hidl_string(value));
EXPECT_NE(Status::OK, status);
// Tests for invalid sessionId
value.clear();
value.append("bad session id");
value.append(",1");
value.append(hash.c_str());
status = plugin.setPropertyString(hidl_string("decryptHash"),
hidl_string(value));
EXPECT_NE(Status::OK, status);
// Tests for malformed Base64encode hash, with a ","
std::string computedHash(sessionId.begin(), sessionId.end());
computedHash.append(frameNumber.c_str());
computedHash.append(hash.c_str());
status = plugin.setPropertyString(hidl_string("decryptHash"),
hidl_string(computedHash));
EXPECT_NE(Status::OK, status);
status = plugin.closeSession(toHidlVec(sessionId));
ASSERT_EQ(Status::OK, status);
}
} // namespace widevine
} // namespace V1_2
} // namespace drm