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

@@ -274,6 +274,16 @@ class CdmEngine {
virtual size_t SessionSize() const { return session_map_.Size(); }
static CdmResponseType ParseDecryptHashString(const std::string& hash_string,
CdmSessionId* id,
uint32_t* frame_number,
std::string* hash);
virtual CdmResponseType SetDecryptHash(const CdmSessionId& session_id,
uint32_t frame_number,
const std::string& hash);
virtual CdmResponseType GetDecryptHashError(const CdmSessionId& session_id,
std::string* hash_error_string);
// Is the key known to any session?
virtual bool IsKeyLoaded(const KeyId& key_id);
virtual bool FindSessionForKey(const KeyId& key_id, CdmSessionId* sessionId);

View File

@@ -193,6 +193,11 @@ class CdmSession {
CdmSigningAlgorithm algorithm,
const std::string& signature);
virtual CdmResponseType SetDecryptHash(uint32_t frame_number,
const std::string& hash);
virtual CdmResponseType GetDecryptHashError(std::string* hash_error_string);
virtual metrics::SessionMetrics* GetMetrics() { return metrics_; }
private:

View File

@@ -152,6 +152,14 @@ class CryptoSession {
virtual bool GetResourceRatingTier(uint32_t* tier);
virtual bool GetBuildInformation(std::string* info);
virtual uint32_t IsDecryptHashSupported();
virtual CdmResponseType SetDecryptHash(uint32_t frame_number,
const std::string& hash);
virtual CdmResponseType GetDecryptHashError(std::string* error_string);
virtual CdmResponseType GenericEncrypt(const std::string& in_buffer,
const std::string& key_id,
const std::string& iv,

View File

@@ -82,6 +82,8 @@ static const std::string QUERY_KEY_WVCDM_VERSION = "WidevineCdmVersion";
static const std::string QUERY_KEY_RESOURCE_RATING_TIER = "ResourceRatingTier";
static const std::string QUERY_KEY_OEMCRYPTO_BUILD_INFORMATION =
"OemCryptoBuildInformation";
static const std::string QUERY_KEY_DECRYPT_HASH_SUPPORT =
"DecryptHashSupport";
static const std::string QUERY_VALUE_TRUE = "True";
static const std::string QUERY_VALUE_FALSE = "False";

View File

@@ -338,6 +338,10 @@ enum CdmResponseType {
DEVICE_CANNOT_REPROVISION = 293,
SESSION_NOT_FOUND_19 = 294,
KEY_SIZE_ERROR_2 = 295,
SET_DECRYPT_HASH_ERROR = 296,
GET_DECRYPT_HASH_ERROR = 297,
SESSION_NOT_FOUND_20 = 298,
INVALID_DECRYPT_HASH_FORMAT = 299,
// Don't forget to add new values to ../test/test_printers.cpp.
};

View File

@@ -676,6 +676,8 @@ CdmResponseType CdmEngine::QueryStatus(SecurityLevel security_level,
LOGW("CdmEngine::QueryStatus: GetBuildInformation failed");
return UNKNOWN_ERROR;
}
} else if (query_token == QUERY_KEY_DECRYPT_HASH_SUPPORT) {
*query_response = std::to_string(crypto_session->IsDecryptHashSupported());
} else {
LOGW("CdmEngine::QueryStatus: Unknown status requested, token = %s",
query_token.c_str());
@@ -1643,6 +1645,86 @@ CdmResponseType CdmEngine::GenericVerify(
return session->GenericVerify(message, key_id, algorithm, signature);
}
CdmResponseType CdmEngine::ParseDecryptHashString(
const std::string& hash_string,
CdmSessionId* session_id,
uint32_t* frame_number,
std::string* hash) {
if (session_id == nullptr) {
LOGE("CdmEngine::ParseDecryptHashString: |session_id| was not provided");
return PARAMETER_NULL;
}
if (frame_number == nullptr) {
LOGE("CdmEngine::ParseDecryptHashString: |frame_number| was not provided");
return PARAMETER_NULL;
}
if (hash == nullptr) {
LOGE("CdmEngine::ParseDecryptHashString: |hash| was not provided");
return PARAMETER_NULL;
}
std::stringstream ss;
std::string token;
std::vector<std::string> tokens;
ss.str(hash_string);
while (getline(ss, token, ',')) {
tokens.push_back(token);
}
if (tokens.size() != 3) {
LOGE("CdmEngine::ParseDecryptHashString: |hash_string| has invalid format, "
"unexpected number of tokens: %d (%s)",
tokens.size(), hash_string.c_str());
return INVALID_DECRYPT_HASH_FORMAT;
}
for (size_t i = 0; i < tokens.size(); ++i) {
if (tokens[i].empty()) {
LOGE("CdmEngine::ParseDecryptHashString: |hash_string| has invalid "
"format, token %d of length 0: %s", i, hash_string.c_str());
return INVALID_DECRYPT_HASH_FORMAT;
}
}
*session_id = tokens[0];
std::istringstream iss(tokens[1]);
if (!(iss >> *frame_number)) {
LOGE("CdmEngine::ParseDecryptHashString: error while trying to convert "
"frame number to a numeric format: %s", hash_string.c_str());
return INVALID_DECRYPT_HASH_FORMAT;
}
std::vector<uint8_t> hash_vec = wvcdm::Base64Decode(tokens[2]);
if (hash_vec.empty()) {
LOGE("CdmEngine::ParseDecryptHashString: malformed hash: %s",
hash_string.c_str());
return INVALID_DECRYPT_HASH_FORMAT;
}
hash->assign(hash_vec.begin(), hash_vec.end());
return NO_ERROR;
}
CdmResponseType CdmEngine::SetDecryptHash(
const CdmSessionId& session_id,
uint32_t frame_number,
const std::string& hash) {
LOGI("CdmEngine::SetDecryptHash");
std::shared_ptr<CdmSession> session;
if (!session_map_.FindSession(session_id, &session)) {
return SESSION_NOT_FOUND_20;
}
return session->SetDecryptHash(frame_number, hash);
}
CdmResponseType CdmEngine::GetDecryptHashError(
const CdmSessionId& session_id,
std::string* error_string) {
LOGI("CdmEngine::GetDecryptHashError");
std::shared_ptr<CdmSession> session;
if (!session_map_.FindSession(session_id, &session)) {
return SESSION_NOT_FOUND_20;
}
return session->GetDecryptHashError(error_string);
}
// TODO(gmorgan) Used? Delete if unused.
bool CdmEngine::IsKeyLoaded(const KeyId& key_id) {
CdmSessionList sessions;

View File

@@ -1068,6 +1068,16 @@ CdmResponseType CdmSession::GenericVerify(const std::string& message,
return sts;
}
CdmResponseType CdmSession::SetDecryptHash(uint32_t frame_number,
const std::string& hash) {
return crypto_session_->SetDecryptHash(frame_number, hash);
}
CdmResponseType CdmSession::GetDecryptHashError(
std::string* error_string) {
return crypto_session_->GetDecryptHashError(error_string);
}
bool CdmSession::UpdateUsageInfo() {
std::string app_id;
GetApplicationId(&app_id);

View File

@@ -1810,6 +1810,70 @@ bool CryptoSession::GetBuildInformation(std::string* info) {
return true;
}
uint32_t CryptoSession::IsDecryptHashSupported() {
LOGV("IsDecryptHashSupported");
if (!initialized_) return false;
uint32_t secure_decrypt_support =
OEMCrypto_SupportsDecryptHash(requested_security_level_);
switch (secure_decrypt_support) {
case OEMCrypto_Hash_Not_Supported:
case OEMCrypto_CRC_Clear_Buffer:
case OEMCrypto_Partner_Defined_Hash:
break;
default:
LOGW("OEMCrypto_SupportsDecryptHash returned unexpected result: %d",
secure_decrypt_support);
secure_decrypt_support = OEMCrypto_Hash_Not_Supported;
break;
}
return secure_decrypt_support;
}
CdmResponseType CryptoSession::SetDecryptHash(
uint32_t frame_number,
const std::string& hash) {
LOGV("SetDecryptHash");
OEMCryptoResult sts = OEMCrypto_SetDecryptHash(
oec_session_id_, frame_number,
reinterpret_cast<const uint8_t*>(hash.data()), hash.size());
if (OEMCrypto_SUCCESS != sts) {
LOGE("SetSecureDecryptHash: failed with error %d", sts);
return SET_DECRYPT_HASH_ERROR;
}
return NO_ERROR;
}
CdmResponseType CryptoSession::GetDecryptHashError(std::string* error_string) {
LOGV("GetDecryptHashError");
if (error_string == nullptr) {
LOGE("CryptoSession::GetDecryptHashError: |error_string| not provided");
return PARAMETER_NULL;
}
error_string->clear();
uint32_t failed_frame_number;
OEMCryptoResult sts = OEMCrypto_GetHashErrorCode(
oec_session_id_, &failed_frame_number);
error_string->assign(std::to_string(sts));
switch (sts) {
case OEMCrypto_SUCCESS:
case OEMCrypto_ERROR_BAD_HASH:
case OEMCrypto_ERROR_SESSION_LOST_STATE:
case OEMCrypto_ERROR_SYSTEM_INVALIDATED:
error_string->assign(std::to_string(sts));
error_string->append(",");
error_string->append(std::to_string(failed_frame_number));
return NO_ERROR;
case OEMCrypto_ERROR_UNKNOWN_FAILURE:
case OEMCrypto_ERROR_NOT_IMPLEMENTED:
default:
LOGE("GetDecryptHashError: failed with error %d", sts);
return GET_DECRYPT_HASH_ERROR;
}
}
CdmResponseType CryptoSession::GenericEncrypt(const std::string& in_buffer,
const std::string& key_id,
const std::string& iv,

View File

@@ -40,6 +40,8 @@ const int kHttpOk = 200;
const std::string kCencMimeType = "video/mp4";
const std::string kWebmMimeType = "video/webm";
const std::string kEmptyString;
const std::string kComma = ",";
} // namespace
@@ -389,4 +391,116 @@ TEST_F(WvCdmEngineTest, LicenseRenewal) {
config_.client_auth());
}
TEST_F(WvCdmEngineTest, ParseDecryptHashStringTest) {
CdmSessionId session_id;
uint32_t frame_number;
std::string hash;
const std::string test_session_id = "sample session id";
uint32_t test_frame_number = 5;
const std::string test_frame_number_string =
std::to_string(test_frame_number);
const std::string test_invalid_hash = "an invalid hash";
std::vector<uint8_t> binary_hash{ 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38 };
const std::string test_valid_decoded_hash(binary_hash.begin(),
binary_hash.end());
const std::string test_valid_hash = Base64Encode(binary_hash);
const std::string test_invalid_hash_string = "sample hash string";
const std::string test_valid_hash_string = test_session_id + kComma +
test_frame_number_string + kComma + test_valid_hash;
// invalid parameters
EXPECT_EQ(PARAMETER_NULL,
CdmEngine::ParseDecryptHashString(test_valid_hash_string, nullptr,
&frame_number, &hash));
EXPECT_EQ(PARAMETER_NULL,
CdmEngine::ParseDecryptHashString(test_valid_hash_string,
&session_id, nullptr, &hash));
EXPECT_EQ(PARAMETER_NULL,
CdmEngine::ParseDecryptHashString(test_valid_hash_string,
&session_id, &frame_number,
nullptr));
// Too few or too many parameters
EXPECT_EQ(INVALID_DECRYPT_HASH_FORMAT,
CdmEngine::ParseDecryptHashString(kEmptyString, &session_id,
&frame_number, &hash));
std::string hash_data = test_session_id;
EXPECT_EQ(INVALID_DECRYPT_HASH_FORMAT,
CdmEngine::ParseDecryptHashString(hash_data, &session_id,
&frame_number, &hash));
hash_data += kComma;
EXPECT_EQ(INVALID_DECRYPT_HASH_FORMAT,
CdmEngine::ParseDecryptHashString(hash_data, &session_id,
&frame_number, &hash));
hash_data += test_frame_number_string;
EXPECT_EQ(INVALID_DECRYPT_HASH_FORMAT,
CdmEngine::ParseDecryptHashString(hash_data, &session_id,
&frame_number, &hash));
hash_data += kComma;
EXPECT_EQ(INVALID_DECRYPT_HASH_FORMAT,
CdmEngine::ParseDecryptHashString(hash_data, &session_id,
&frame_number, &hash));
hash_data += test_valid_hash + kComma + test_valid_hash;
EXPECT_EQ(INVALID_DECRYPT_HASH_FORMAT,
CdmEngine::ParseDecryptHashString(hash_data, &session_id,
&frame_number, &hash));
// No parameters
hash_data = kComma;
EXPECT_EQ(INVALID_DECRYPT_HASH_FORMAT,
CdmEngine::ParseDecryptHashString(hash_data, &session_id,
&frame_number, &hash));
hash_data = kComma + kComma;
EXPECT_EQ(INVALID_DECRYPT_HASH_FORMAT,
CdmEngine::ParseDecryptHashString(hash_data, &session_id,
&frame_number, &hash));
hash_data = kComma + kComma + kComma;
EXPECT_EQ(INVALID_DECRYPT_HASH_FORMAT,
CdmEngine::ParseDecryptHashString(hash_data, &session_id,
&frame_number, &hash));
// Missing parameters
hash_data = kComma + test_frame_number_string + kComma + test_valid_hash;
EXPECT_EQ(INVALID_DECRYPT_HASH_FORMAT,
CdmEngine::ParseDecryptHashString(hash_data, &session_id,
&frame_number, &hash));
hash_data = test_session_id + kComma + kComma + test_valid_hash;
EXPECT_EQ(INVALID_DECRYPT_HASH_FORMAT,
CdmEngine::ParseDecryptHashString(hash_data, &session_id,
&frame_number, &hash));
hash_data = test_session_id + kComma + test_frame_number_string + kComma;
EXPECT_EQ(INVALID_DECRYPT_HASH_FORMAT,
CdmEngine::ParseDecryptHashString(hash_data, &session_id,
&frame_number, &hash));
hash_data = test_session_id + kComma + test_frame_number_string + kComma +
kComma;
EXPECT_EQ(INVALID_DECRYPT_HASH_FORMAT,
CdmEngine::ParseDecryptHashString(hash_data, &session_id,
&frame_number, &hash));
// Invalid parameters (frame number, hash)
hash_data = test_session_id + kComma + test_session_id + kComma +
test_valid_hash;
EXPECT_EQ(INVALID_DECRYPT_HASH_FORMAT,
CdmEngine::ParseDecryptHashString(hash_data, &session_id,
&frame_number, &hash));
hash_data = test_session_id + kComma + test_frame_number_string + kComma +
test_invalid_hash;
EXPECT_EQ(INVALID_DECRYPT_HASH_FORMAT,
CdmEngine::ParseDecryptHashString(hash_data, &session_id,
&frame_number, &hash));
// valid data
EXPECT_EQ(NO_ERROR,
CdmEngine::ParseDecryptHashString(test_valid_hash_string,
&session_id, &frame_number,
&hash));
EXPECT_EQ(test_session_id, session_id);
EXPECT_EQ(test_frame_number, frame_number);
EXPECT_EQ(test_valid_decoded_hash, hash);
}
} // namespace wvcdm

View File

@@ -775,6 +775,18 @@ void PrintTo(const enum CdmResponseType& value, ::std::ostream* os) {
case DEVICE_CANNOT_REPROVISION:
*os << "DEVICE_CANNOT_REPROVISION";
break;
case SET_DECRYPT_HASH_ERROR:
*os << "SET_DECRYPT_HASH_ERROR";
break;
case GET_DECRYPT_HASH_ERROR:
*os << "GET_DECRYPT_HASH_ERROR";
break;
case SESSION_NOT_FOUND_20:
*os << "SESSION_NOT_FOUND_20";
break;
case INVALID_DECRYPT_HASH_FORMAT:
*os << "INVALID_DECRYPT_HASH_FORMAT";
break;
default:
*os << "Unknown CdmResponseType";
break;

View File

@@ -139,6 +139,11 @@ class WvContentDecryptionModule : public android::RefBase, public TimerHandler {
// Closes the CdmEngine and sessions associated with the given CdmIdentifier.
virtual CdmResponseType CloseCdm(const CdmIdentifier& identifier);
virtual CdmResponseType SetDecryptHash(const std::string& hash_data,
CdmSessionId* session_id);
virtual CdmResponseType GetDecryptHashError(const CdmSessionId& session_id,
std::string* hash_error_string);
private:
struct CdmInfo {
CdmInfo();

View File

@@ -12,6 +12,7 @@
#include "metrics.pb.h"
#include "properties.h"
#include "service_certificate.h"
#include "string_conversions.h"
#include "wv_cdm_constants.h"
#include "wv_cdm_event_listener.h"
@@ -456,6 +457,44 @@ CdmResponseType WvContentDecryptionModule::CloseCdm(
return NO_ERROR;
}
CdmResponseType WvContentDecryptionModule::SetDecryptHash(
const std::string& hash_data, CdmSessionId* id) {
if (id == nullptr) {
LOGE("WVContentDecryptionModule::SetDecryptHash: |id| was not provided");
return PARAMETER_NULL;
}
uint32_t frame_number;
std::string hash;
CdmResponseType res =
CdmEngine::ParseDecryptHashString(hash_data, id, &frame_number, &hash);
if (res != NO_ERROR) return res;
CdmEngine* cdm_engine = GetCdmForSessionId(*id);
if (!cdm_engine) {
LOGE("WVContentDecryptionModule::SetDecryptHash: Unable to find CdmEngine");
return SESSION_NOT_FOUND_20;
}
res = cdm_engine->SetDecryptHash(*id, frame_number, hash);
return res;
}
CdmResponseType WvContentDecryptionModule::GetDecryptHashError(
const CdmSessionId& session_id,
std::string* hash_error_string) {
CdmEngine* cdm_engine = GetCdmForSessionId(session_id);
if (!cdm_engine) {
LOGE("WVContentDecryptionModule::GetDecryptHashError: Unable to find "
"CdmEngine");
return SESSION_NOT_FOUND_20;
}
return cdm_engine->GetDecryptHashError(session_id, hash_error_string);
}
void WvContentDecryptionModule::EnablePolicyTimer() {
std::unique_lock<std::mutex> auto_lock(policy_timer_lock_);
if (!policy_timer_.IsRunning())

View File

@@ -63,6 +63,7 @@ const wvcdm::CdmIdentifier kAlternateCdmIdentifier1 = {
};
const std::string kEmptyServiceCertificate;
const std::string kComma = ",";
// Protobuf generated classes
using video_widevine::LicenseIdentification;
@@ -5104,4 +5105,34 @@ TEST_F(WvCdmRequestLicenseTest, CloseCdmReleaseResourcesTest) {
EXPECT_TRUE(decryptor_.IsOpenSession(alternate_session_id));
}
// Enable when OEMCrypto v15 has been deployed. Currently setting a decrypt
// hash returns OEMCrypto_ERROR_NOT_IMPLEMENTED
TEST_F(WvCdmRequestLicenseTest, DISABLED_DecryptPathTest) {
Provision(kDefaultCdmIdentifier, kLevelDefault);
// Retrieve a streaming license
EXPECT_EQ(NO_ERROR, decryptor_.OpenSession(config_.key_system(), NULL,
kDefaultCdmIdentifier, NULL,
&session_id_));
CdmSessionId invalid_session_id = session_id_ + "more";
std::string frame_number = std::to_string(5);
std::vector<uint8_t> binary_hash{ 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38 };
const std::string hash = Base64Encode(binary_hash);
// Invalid session id
std::string hash_data =
invalid_session_id + kComma + std::to_string(5) + kComma + hash;
CdmSessionId session_id;
EXPECT_NE(NO_ERROR, decryptor_.SetDecryptHash(hash_data, &session_id));
// Valid hash data
hash_data = session_id_ + kComma + std::to_string(5) + kComma + hash;
EXPECT_EQ(NO_ERROR, decryptor_.SetDecryptHash(hash_data, &session_id));
EXPECT_EQ(session_id_, session_id);
decryptor_.CloseSession(session_id_);
}
} // namespace wvcdm