Merges to android Pi release (part 7)

These are a set of CLs merged from the wv cdm repo to the android repo.

* Resolve intermittent decrypt error.

  Author: Jeff Fore <jfore@google.com>

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

  The CdmSession's closed state was not properly
  initialized resulting in intermittent
  SESSION_NOT_FOUND_FOR_DECRYPT errors.

  In CdmEngine::Decrypt the session is looked up by
  the key id. A list of open sessions is acquired
  by calling CdmSessionMap::GetSessionList and each
  session in the list is queried to see if it has
  the key.

  In building the list in CdmSessionMap::GetSessionList,
  sessions are only added to the query list *if* the session
  is not closed.

  The closed status was not initialized and during testing
  the query list would not contain the session causing
  CdmEngine::Decrypt to return SESSION_NOT_FOUND_FOR_DECRYPT
  resulting in the ce cdm api returning widevine::Cdm::kNoKey.

* No support for pre- C++11 compilation.

  Author: Gene Morgan <gmorgan@google.com>

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

* Handle unaligned nonce pointer in RewrapDeviceRSAKey calls.

  Author: Gene Morgan <gmorgan@google.com>

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

  The pointer points into a message and it may not be aligned.
  Always copy the nonce into aligned memory before checking it.

  BUG: 38140370

  Add note to CHANGELOG for this.

* Compiler strictness: more checks and code cleanup.

  Author: Gene Morgan <gmorgan@google.com>

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

  Use the switches proposed in b/38033653 (as much as possible - some
  conflicts with protobufs and gtest prevent fully accepting them).

  Switch to clang for x32 build; ensure that both x86-64 and x86-32 builds
  compile and link cleanly.

  BUG: 38032429
  BUG: 38033653

  This partially resolves b/38458986

* Android build fixes

  Author: Rahul Frias <rfrias@google.com>

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

  These corrections address compile warnings and errors for android
  and unit tests.

* Embedded License: Add sub license key sessions.

  Author: Jeff Fore <jfore@google.com>

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

  NOTE: this adds the AddSubSession() method, but it is not yet being
  used. Use and proper cleanup is in an upcoming CL.

* Embedded license: Add track label field.

  Author: Jeff Fore <jfore@google.com>

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

  A new track label field (a string) is added to the key container and the
  sub session data objects.

  This field will be used in handling sub license requests.

* Embedded license: extract keys from init_data.

  Author: Jeff Fore <jfore@google.com>

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

* Embedded license: add protobuf messages.

  Author: Jeff Fore <jfore@google.com>

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

  also sync the widevine header definition with recent naming changes.

* Improve handling of provisioning response errors.

  Author: Gene Morgan <gmorgan@google.com>

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

  Separate out the case of no response and the case
  where the message is believed to be a JSON+base64
  message but it doesn't parse properly.

BUG: 71650075
Test: Not currently passing. Will be addressed in a subsequent
  commit in the chain.

Change-Id: I3c86f1c54980b071aec7461ac58541836551f896
This commit is contained in:
Rahul Frias
2018-01-09 23:36:42 -08:00
parent 00da44bb68
commit 80659961ac
38 changed files with 1249 additions and 360 deletions

View File

@@ -68,8 +68,8 @@ CdmEngine::CdmEngine(FileSystem* file_system, const std::string& spoid)
usage_session_(NULL),
last_usage_information_update_time_(0) {
assert(file_system);
Properties::Init();
if (!seeded_) {
Properties::Init();
srand(clock_.GetCurrentTime());
seeded_ = true;
}
@@ -1296,17 +1296,31 @@ CdmResponseType CdmEngine::ReleaseAllUsageInfo(const std::string& app_id) {
switch (usage_session_->get_usage_support_type()) {
case kUsageEntrySupport: {
std::vector<DeviceFiles::CdmUsageData> usage_data;
if (!handle.RetrieveUsageInfo(
DeviceFiles::GetUsageInfoFileName(app_id), &usage_data)) {
status = RELEASE_ALL_USAGE_INFO_ERROR_4;
} else {
for (size_t k = 0; k < usage_data.size(); ++k) {
CdmResponseType status2 = usage_session_->DeleteUsageEntry(
usage_data[k].usage_entry_number);
if (status == NO_ERROR && status2 != NO_ERROR)
status = status2;
// Retrieve all usage information but delete only one before
// refetching. This is because deleting the usage entry
// might cause other entries to be shifted and information updated.
do {
if (!handle.RetrieveUsageInfo(
DeviceFiles::GetUsageInfoFileName(app_id),
&usage_data)) {
status = RELEASE_ALL_USAGE_INFO_ERROR_4;
break;
}
}
if (usage_data.empty()) break;
status = usage_session_->DeleteUsageEntry(
usage_data[0].usage_entry_number);
if (status != NO_ERROR) break;
if (!handle.DeleteUsageInfo(
DeviceFiles::GetUsageInfoFileName(app_id),
usage_data[0].provider_session_token)) {
status = RELEASE_ALL_USAGE_INFO_ERROR_6;
break;
}
} while (status == NO_ERROR && !usage_data.empty());
std::vector<std::string> provider_session_tokens;
if (!handle.DeleteAllUsageInfoForApp(

View File

@@ -28,6 +28,7 @@ CdmSession::CdmSession(FileSystem* file_system,
metrics::SessionMetrics* metrics) :
metrics_(metrics),
initialized_(false),
closed_(true),
file_handle_(new DeviceFiles(file_system)),
license_received_(false),
is_offline_(false),
@@ -193,6 +194,7 @@ CdmResponseType CdmSession::Init(
license_received_ = false;
is_initial_decryption_ = true;
initialized_ = true;
closed_ = false;
return NO_ERROR;
}
@@ -230,7 +232,8 @@ CdmResponseType CdmSession::RestoreOfflineSession(
std::string provider_session_token;
if (usage_support_type_ == kUsageEntrySupport) {
if (!license_parser_->ExtractProviderSessionToken(
key_response_, &provider_session_token)) {
key_response_, &provider_session_token) ||
usage_table_header_ == nullptr) {
provider_session_token.clear();
} else {
CdmResponseType sts =
@@ -252,13 +255,14 @@ CdmResponseType CdmSession::RestoreOfflineSession(
} else {
if (!license_parser_->RestoreOfflineLicense(
key_request_, key_response_, offline_key_renewal_response_,
playback_start_time, last_playback_time, grace_period_end_time)) {
playback_start_time, last_playback_time, grace_period_end_time,
this)) {
return RESTORE_OFFLINE_LICENSE_ERROR_2;
}
}
if (usage_support_type_ == kUsageEntrySupport &&
!provider_session_token.empty()) {
!provider_session_token.empty() && usage_table_header_ != nullptr) {
CdmResponseType sts =
usage_table_header_->UpdateEntry(crypto_session_.get(), &usage_entry_);
if (sts != NO_ERROR) {
@@ -290,9 +294,10 @@ CdmResponseType CdmSession::RestoreUsageSession(
usage_entry_number_ = usage_data.usage_entry_number;
usage_provider_session_token_ = usage_data.provider_session_token;
if (usage_support_type_ == kUsageEntrySupport) {
if (usage_support_type_ == kUsageEntrySupport &&
usage_table_header_ != nullptr) {
CdmResponseType sts = usage_table_header_->LoadEntry(
crypto_session_.get(), usage_entry_, usage_entry_number_);
crypto_session_.get(), usage_entry_, usage_entry_number_);
if (sts != NO_ERROR) {
LOGE("CdmSession::RestoreUsageSession: failed to load usage entry = %d",
sts);
@@ -304,7 +309,8 @@ CdmResponseType CdmSession::RestoreUsageSession(
return RELEASE_LICENSE_ERROR_2;
}
if (usage_support_type_ == kUsageEntrySupport) {
if (usage_support_type_ == kUsageEntrySupport &&
usage_table_header_ != nullptr) {
CdmResponseType sts =
usage_table_header_->UpdateEntry(crypto_session_.get(), &usage_entry_);
if (sts != NO_ERROR) {
@@ -424,7 +430,8 @@ CdmResponseType CdmSession::AddKey(const CdmKeyResponse& key_response) {
// to be created.
CdmResponseType sts;
std::string provider_session_token;
if (usage_support_type_ == kUsageEntrySupport) {
if (usage_support_type_ == kUsageEntrySupport &&
usage_table_header_ != nullptr) {
if (license_parser_->ExtractProviderSessionToken(
key_response, &provider_session_token) &&
!provider_session_token.empty()) {
@@ -440,7 +447,8 @@ CdmResponseType CdmSession::AddKey(const CdmKeyResponse& key_response) {
// Update or delete entry if usage table header+entries are supported
if (usage_support_type_ == kUsageEntrySupport &&
!provider_session_token.empty()) {
!provider_session_token.empty() &&
usage_table_header_ != nullptr) {
if (sts != KEY_ADDED) {
CdmResponseType sts =
usage_table_header_->DeleteEntry(usage_entry_number_,
@@ -463,7 +471,8 @@ CdmResponseType CdmSession::AddKey(const CdmKeyResponse& key_response) {
if (is_offline_ || has_provider_session_token()) {
if (has_provider_session_token() &&
usage_support_type_ == kUsageEntrySupport) {
usage_support_type_ == kUsageEntrySupport &&
usage_table_header_ != nullptr) {
usage_table_header_->UpdateEntry(crypto_session_.get(), &usage_entry_);
}
@@ -671,8 +680,7 @@ CdmResponseType CdmSession::ReleaseKey(const CdmKeyResponse& key_response) {
}
CdmResponseType CdmSession::DeleteUsageEntry(uint32_t usage_entry_number) {
if (usage_support_type_ != kUsageEntrySupport ||
!has_provider_session_token()) {
if (usage_support_type_ != kUsageEntrySupport) {
LOGE("CdmSession::DeleteUsageEntry: Unexpected usage type supported: %d",
usage_support_type_);
return INCORRECT_USAGE_SUPPORT_TYPE_1;
@@ -881,7 +889,8 @@ CdmResponseType CdmSession::UpdateUsageTableInformation() {
CdmResponseType CdmSession::UpdateUsageEntryInformation() {
if (usage_support_type_ != kUsageEntrySupport ||
!has_provider_session_token()) {
!has_provider_session_token() ||
usage_table_header_ == nullptr) {
LOGE("CdmSession::UpdateUsageEntryInformation: Unexpected usage type "
"supported: %d", usage_support_type_);
return INCORRECT_USAGE_SUPPORT_TYPE_2;

View File

@@ -280,29 +280,33 @@ CdmResponseType CertificateProvisioning::GetProvisioningRequest(
* Returns NO_ERROR for success and CERT_PROVISIONING_RESPONSE_ERROR_? if fails.
*/
CdmResponseType CertificateProvisioning::HandleProvisioningResponse(
FileSystem* file_system, const CdmProvisioningResponse& response,
FileSystem* file_system, const CdmProvisioningResponse& response_message,
std::string* cert, std::string* wrapped_key) {
std::string raw_string;
if (!wvcdm::Properties::provisioning_messages_are_binary()) {
// The response is base64 encoded in a JSON wrapper.
// Extract it and decode it. If errors, return an empty string.
ExtractAndDecodeSignedMessage(response, &raw_string);
} else {
raw_string.assign(response);
if (response_message.empty()) {
LOGE("HandleProvisioningResponse: response message is empty.");
return CERT_PROVISIONING_RESPONSE_ERROR_1;
}
if (raw_string.empty()) {
LOGE("HandleProvisioningResponse: response message is empty or "
"an invalid JSON/base64 string.");
return CERT_PROVISIONING_RESPONSE_ERROR_1;
std::string response;
if (wvcdm::Properties::provisioning_messages_are_binary()) {
response.assign(response_message);
} else {
// The response is base64 encoded in a JSON wrapper.
// Extract it and decode it. On error return an empty string.
ExtractAndDecodeSignedMessage(response_message, &response);
if (response.empty()) {
LOGE("HandleProvisioningResponse: response message is "
"an invalid JSON/base64 string.");
return CERT_PROVISIONING_RESPONSE_ERROR_1;
}
}
// Authenticates provisioning response using D1s (server key derived from
// the provisioing request's input). Validate provisioning response and
// stores private device RSA key and certificate.
SignedProvisioningMessage signed_response;
if (!signed_response.ParseFromString(raw_string)) {
if (!signed_response.ParseFromString(response)) {
LOGE("HandleProvisioningResponse: fails to parse signed response");
return CERT_PROVISIONING_RESPONSE_ERROR_2;
}

View File

@@ -502,24 +502,25 @@ CdmResponseType CryptoSession::Open(SecurityLevel requested_security_level) {
CdmSecurityLevel security_level = GetSecurityLevel();
if (security_level == kSecurityLevelL1 ||
security_level == kSecurityLevelL3) {
UsageTableHeader* header = security_level == kSecurityLevelL1 ?
usage_table_header_l1_ : usage_table_header_l3_;
if (header == NULL) {
header = new UsageTableHeader();
UsageTableHeader** header = security_level == kSecurityLevelL1 ?
&usage_table_header_l1_ : &usage_table_header_l3_;
if (*header == NULL) {
*header = new UsageTableHeader();
// Ignore errors since we do not know when a session is opened,
// if it is intended to be used for offline/usage session related
// or otherwise.
if (!header->Init(security_level, this)) {
delete header;
crypto_lock_.Release();
bool is_usage_table_header_inited =
(*header)->Init(security_level, this);
crypto_lock_.Acquire();
if (!is_usage_table_header_inited) {
delete *header;
*header = NULL;
usage_table_header_ = NULL;
return NO_ERROR;
}
if (security_level == kSecurityLevelL1)
usage_table_header_l1_ = header;
else
usage_table_header_l3_ = header;
}
usage_table_header_ = header;
usage_table_header_ = *header;
}
}
}
@@ -2258,6 +2259,55 @@ CdmResponseType CryptoSession::MoveUsageEntry(uint32_t new_entry_number) {
return NO_ERROR;
}
bool CryptoSession::CreateOldUsageEntry(
uint64_t time_since_license_received,
uint64_t time_since_first_decrypt,
uint64_t time_since_last_decrypt,
UsageDurationStatus usage_duration_status,
const std::string& server_mac_key,
const std::string& client_mac_key,
const std::string& provider_session_token) {
LOGV("CreateOldUsageEntry: Lock");
AutoLock auto_lock(crypto_lock_);
if (server_mac_key.size() < MAC_KEY_SIZE ||
client_mac_key.size() < MAC_KEY_SIZE) {
LOGE("CreateOldUsageEntry: Invalid mac key size: server mac key size %d, "
"client mac key size: %d", server_mac_key.size(),
client_mac_key.size());
return false;
}
OEMCrypto_Usage_Entry_Status status;
switch (usage_duration_status) {
case kUsageDurationsInvalid: status = kUnused; break;
case kUsageDurationPlaybackNotBegun: status = kInactiveUnused; break;
case kUsageDurationsValid: status = kActive; break;
default:
LOGE("CreateOldUsageEntry: Unrecognized usage entry status: %d", status);
status = kUnused;
return false;
}
OEMCryptoResult result =
OEMCrypto_CreateOldUsageEntry(
requested_security_level_, time_since_license_received,
time_since_first_decrypt, time_since_last_decrypt, status,
reinterpret_cast<uint8_t*>(
const_cast<char*>(server_mac_key.data())),
reinterpret_cast<uint8_t*>(
const_cast<char*>(client_mac_key.data())),
reinterpret_cast<const uint8_t*>(provider_session_token.data()),
provider_session_token.size());
if (result != OEMCrypto_SUCCESS) {
LOGE("CreateOldUsageEntry: OEMCrypto_CreateOldUsageEntry error: %d",
result);
return false;
}
return true;
}
CdmResponseType CryptoSession::CopyOldUsageEntry(
const std::string& provider_session_token) {
LOGV("CopyOldUsageEntry: id=%ld", (uint32_t)oec_session_id_);
@@ -2275,6 +2325,36 @@ CdmResponseType CryptoSession::CopyOldUsageEntry(
return NO_ERROR;
}
CdmResponseType CryptoSession::AddSubSession(
const std::string& sub_session_key_id) {
size_t exists = sub_license_oec_sessions_.count(sub_session_key_id);
if (exists > 0) {
// TODO(jfore): Should this be an error if the key exists? If so add a new
// error. If not, perhaps this should just print info message.
LOGE("AddSubSession: SubSession already exists for id: %s",
sub_session_key_id.c_str());
return UNKNOWN_ERROR;
}
OEMCryptoResult sts;
CryptoSessionId sid;
sts = OEMCrypto_OpenSession(&sid,requested_security_level_);
if (OEMCrypto_ERROR_TOO_MANY_SESSIONS == sts) {
LOGE("OEMCrypto_Open failed: %d, open sessions: %ld, initialized: %d",
sts, session_count_, (int)initialized_);
return INSUFFICIENT_CRYPTO_RESOURCES;
} else if (OEMCrypto_SUCCESS != sts) {
LOGE("OEMCrypto_Open failed: %d, open sessions: %ld, initialized: %d",
sts, session_count_, (int)initialized_);
return UNKNOWN_ERROR;
}
// TODO(jfore): Is there some session count metrics that should be add or
// update?
sub_license_oec_sessions_[sub_session_key_id] = sid;
return NO_ERROR;
}
OEMCrypto_Algorithm CryptoSession::GenericSigningAlgorithm(
CdmSigningAlgorithm algorithm) {
if (kSigningAlgorithmHmacSha256 == algorithm) {

View File

@@ -7,7 +7,6 @@
#include "buffer_reader.h"
#include "jsmn.h"
#include "license_protocol.pb.h"
#include "log.h"
#include "properties.h"
#include "string_conversions.h"
@@ -35,9 +34,9 @@ const int kDefaultNumJsonTokens = 128;
namespace wvcdm {
// Protobuf generated classes.
using video_widevine::WidevineCencHeader;
using video_widevine::WidevineCencHeader_Algorithm;
using video_widevine::WidevineCencHeader_Algorithm_AESCTR;
using video_widevine::WidevinePsshData;
using video_widevine::WidevinePsshData_Algorithm;
using video_widevine::WidevinePsshData_Algorithm_AESCTR;
InitializationData::InitializationData(const std::string& type,
const CdmInitData& data)
@@ -70,6 +69,22 @@ InitializationData::InitializationData(const std::string& type,
}
}
// Parse the pssh data and return the embedded key data if it exists.
std::vector<video_widevine::SubLicense>
InitializationData::ExtractEmbeddedKeys() const {
std::vector<video_widevine::SubLicense> keys;
WidevinePsshData cenc_header;
if (!is_cenc_ || !cenc_header.ParseFromString(data_) ||
cenc_header.sub_licenses().size() == 0)
return keys;
keys.reserve(cenc_header.sub_licenses().size());
for (int i = 0; i < cenc_header.sub_licenses().size(); ++i) {
keys.push_back(cenc_header.sub_licenses(i));
}
return keys;
}
// Parse a blob of multiple concatenated PSSH atoms to extract the first
// Widevine PSSH.
bool InitializationData::ExtractWidevinePssh(const CdmInitData& init_data,
@@ -339,8 +354,9 @@ bool InitializationData::ConstructWidevineInitData(
return false;
}
if (method != kHlsMethodAes128 && method != kHlsMethodSampleAes) {
LOGV("InitializationData::ConstructWidevineInitData: Invalid method"
" parameter");
LOGV(
"InitializationData::ConstructWidevineInitData: Invalid method"
" parameter");
return false;
}
@@ -466,10 +482,10 @@ bool InitializationData::ConstructWidevineInitData(
}
// Now format as Widevine init data protobuf
WidevineCencHeader cenc_header;
WidevinePsshData cenc_header;
// TODO(rfrias): The algorithm is a deprecated field, but proto changes
// have not yet been pushed to production. Set until then.
cenc_header.set_algorithm(WidevineCencHeader_Algorithm_AESCTR);
cenc_header.set_algorithm(WidevinePsshData_Algorithm_AESCTR);
for (size_t i = 0; i < key_ids.size(); ++i) {
cenc_header.add_key_id(key_ids[i]);
}

View File

@@ -599,7 +599,8 @@ bool CdmLicense::RestoreOfflineLicense(
const CdmKeyMessage& license_request,
const CdmKeyResponse& license_response,
const CdmKeyResponse& license_renewal_response, int64_t playback_start_time,
int64_t last_playback_time, int64_t grace_period_end_time) {
int64_t last_playback_time, int64_t grace_period_end_time,
CdmSession* cdm_session) {
if (license_request.empty() || license_response.empty()) {
LOGE(
"CdmLicense::RestoreOfflineLicense: key_request or response empty: "
@@ -641,9 +642,15 @@ bool CdmLicense::RestoreOfflineLicense(
}
if (!provider_session_token_.empty()) {
if (cdm_session) {
CdmResponseType status = cdm_session->UpdateUsageEntryInformation();
if (NO_ERROR != status) return false;
}
std::string usage_report;
CryptoSession::UsageDurationStatus usage_duration_status =
CryptoSession::kUsageDurationsInvalid;
int64_t seconds_since_started, seconds_since_last_played;
sts = crypto_session_->GenerateUsageReport(
provider_session_token_, &usage_report, &usage_duration_status,

View File

@@ -101,6 +101,7 @@ message License {
CONTENT = 2;
KEY_CONTROL = 3;
OPERATOR_SESSION = 4;
SUB_SESSION = 5;
}
// The SecurityLevel enumeration allows the server to communicate the level
@@ -199,6 +200,9 @@ message License {
// supports anti rollback of the user table. Content provider can query the
// client capabilities to determine if the client support this feature.
optional bool anti_rollback_usage_table = 11 [default = false];
// Optional not limited to commonly known track types such as SD, HD.
// It can be some provider defined label to identify the track.
optional string track_label = 12;
}
optional LicenseIdentification id = 1;
@@ -270,6 +274,19 @@ message LicenseRequest {
//}
}
message SubSessionData {
// Required. The key ID for the corresponding SUB_SESSION_KEY. The
// value must match the sub_session_key_id field for a
// corresponding SubLicense message from the PSSH.
optional string sub_session_key_id = 1;
// Required. The nonce for the track.
optional uint32 nonce = 2;
// Required for initial license request used for each CONTENT key_container
// to know which nonce to use for building its key control block.
// Not needed for renewal license request.
optional string track_label = 3;
}
enum RequestType {
NEW = 1;
RENEWAL = 2;
@@ -293,6 +310,9 @@ message LicenseRequest {
optional uint32 key_control_nonce = 7;
// Encrypted ClientIdentification message, used for privacy purposes.
optional EncryptedClientIdentification encrypted_client_id = 8;
// Optional sub session context information. Required for using
// SubLicenses from the PSSH.
repeated SubSessionData sub_session_data = 9;
}
message LicenseError {
@@ -306,6 +326,7 @@ message LicenseError {
// or similar circumstances.
SERVICE_UNAVAILABLE = 3;
}
optional Error error_code = 1;
}
@@ -410,7 +431,7 @@ message ProvisioningOptions {
optional CertificateType certificate_type = 1 [default = WIDEVINE_DRM];
// Contains the application-specific name used to identify the certificate
// authority for signing the generated certificate. This is required iff the
// authority for signing the generated certificate. This is required if the
// certificate type is X509.
optional string certificate_authority = 2;
}
@@ -680,19 +701,33 @@ message ProvisionedDeviceInfo {
}
// ----------------------------------------------------------------------------
// widevine_header.proto
// widevine_pssh.proto
// ----------------------------------------------------------------------------
// Description:
// Public protocol buffer definitions for Widevine Cenc Header
// protocol.
message WidevineCencHeader {
// Each SubLicense message represents a single content key. These keys can be
// added to Widevine CENC initialization data to support both content grouping
// and key rotation.
message SubLicense {
// Required. The key_id of a SUB_SESSION_KEY received in the master license.
// SUB_SESSION_KEY is defined in the Widevine License Protocol.
optional string sub_session_key_id = 1;
// Required. The key_msg contains the bytes of a serialized SignedMessage
// proto. Internally the message field will contain a serialized KeyContainer
// holding a single content key.
optional bytes key_msg = 2;
}
message WidevinePsshData {
enum Algorithm {
UNENCRYPTED = 0;
AESCTR = 1;
};
// Replaced with protection_scheme.
optional Algorithm algorithm = 1 [deprecated=true];
optional Algorithm algorithm = 1;
repeated bytes key_id = 2;
// Content provider name.
@@ -717,10 +752,22 @@ message WidevineCencHeader {
// serialized SignedMessage.
optional bytes grouped_license = 8;
// Protection scheme identifying the encryption algorithm. Represented as one
// of the following 4CC values: 'cenc' (AES-CTR), 'cbc1' (AES-CBC),
// 'cens' (AES-CTR subsample), 'cbcs' (AES-CBC subsample).
// Protection scheme identifying the encryption algorithm. The protection
// scheme is represented as a uint32 value. The uint32 contains 4 bytes each
// representing a single ascii character in one of the 4CC protection scheme
// values.
// 'cenc' (AES-CTR) protection_scheme = 0x63656E63,
// 'cbc1' (AES-CBC) protection_scheme = 0x63626331,
// 'cens' (AES-CTR subsample) protection_scheme = 0x63656E73,
// 'cbcs' (AES-CBC subsample) protection_scheme = 0x63626373.
optional uint32 protection_scheme = 9;
// Optional. For media using key rotation, this represents the duration
// of each crypto period in seconds.
optional uint32 crypto_period_seconds = 10;
// Required when using content keys that are embedded in content.
repeated SubLicense sub_licenses = 11;
}
// Signed device certificate definition.

View File

@@ -506,6 +506,10 @@ class WatchDog {
status_ = OEMCrypto_ERROR_INIT_FAILED;
LOGE("XXX WATCH DOG ERROR XXX");
SaveFailureInformation();
// This tells the worker thread to clean up after itself. It is not
// really needed since we are going to abort. However, if somebody
// removes the "abort()" below, then this is needed.
pthread_detach(thread_);
// This is controversial. The argument for an abort here is that if we
// do not abort, we will suck all the life out of the user's battery. By
// saving information to the file system, above, we can still track
@@ -517,7 +521,10 @@ class WatchDog {
bool should_delete = !gave_up_;
OEMCryptoResult status = status_;
pthread_mutex_unlock(&mutex_);
if (should_delete) delete this;
if (should_delete) {
pthread_join(thread_, NULL);
delete this;
}
return status;
}

View File

@@ -14,6 +14,14 @@
using video_widevine::License;
namespace {
const int kCdmPolicyTimerDurationSeconds = 1;
const int kClockSkewDelta = 5; // seconds
const int64_t kHdcpCheckInterval = 10;
} // namespace
namespace wvcdm {
PolicyEngine::PolicyEngine(CdmSessionId session_id,
@@ -28,6 +36,7 @@ PolicyEngine::PolicyEngine(CdmSessionId session_id,
last_expiry_time_set_(false),
was_expired_on_load_(false),
next_renewal_time_(0),
last_recorded_current_time_(0),
session_id_(session_id),
event_listener_(event_listener),
license_keys_(new LicenseKeys),
@@ -81,7 +90,8 @@ void PolicyEngine::CheckDeviceHdcpStatus() {
}
void PolicyEngine::OnTimerEvent() {
int64_t current_time = clock_->GetCurrentTime();
last_recorded_current_time_ += kCdmPolicyTimerDurationSeconds;
int64_t current_time = GetCurrentTime();
// If we have passed the grace period, the expiration will update.
if (grace_period_end_time_ == 0 && HasPlaybackStarted(current_time)) {
@@ -194,7 +204,7 @@ void PolicyEngine::UpdateLicense(const License& license) {
license_start_time_ = license.license_start_time();
next_renewal_time_ = license_start_time_ + policy_.renewal_delay_seconds();
int64_t current_time = clock_->GetCurrentTime();
int64_t current_time = GetCurrentTime();
if (!policy_.can_play() ||
HasLicenseOrPlaybackDurationExpired(current_time)) {
license_state_ = kLicenseStateExpired;
@@ -219,7 +229,7 @@ void PolicyEngine::BeginDecryption() {
case kLicenseStateCanPlay:
case kLicenseStateNeedRenewal:
case kLicenseStateWaitingLicenseUpdate:
playback_start_time_ = clock_->GetCurrentTime();
playback_start_time_ = GetCurrentTime();
last_playback_time_ = playback_start_time_;
if (policy_.play_start_grace_period_seconds() == 0)
grace_period_end_time_ = playback_start_time_;
@@ -239,7 +249,7 @@ void PolicyEngine::BeginDecryption() {
}
void PolicyEngine::DecryptionEvent() {
last_playback_time_ = clock_->GetCurrentTime();
last_playback_time_ = GetCurrentTime();
}
void PolicyEngine::NotifyResolution(uint32_t width, uint32_t height) {
@@ -253,7 +263,7 @@ void PolicyEngine::NotifySessionExpiration() {
CdmResponseType PolicyEngine::Query(CdmQueryMap* query_response) {
std::stringstream ss;
int64_t current_time = clock_->GetCurrentTime();
int64_t current_time = GetCurrentTime();
if (license_state_ == kLicenseStateInitial) {
query_response->clear();
@@ -294,7 +304,7 @@ CdmResponseType PolicyEngine::QueryKeyAllowedUsage(
bool PolicyEngine::GetSecondsSinceStarted(int64_t* seconds_since_started) {
if (playback_start_time_ == 0) return false;
*seconds_since_started = clock_->GetCurrentTime() - playback_start_time_;
*seconds_since_started = GetCurrentTime() - playback_start_time_;
return (*seconds_since_started >= 0) ? true : false;
}
@@ -302,12 +312,12 @@ bool PolicyEngine::GetSecondsSinceLastPlayed(
int64_t* seconds_since_last_played) {
if (last_playback_time_ == 0) return false;
*seconds_since_last_played = clock_->GetCurrentTime() - last_playback_time_;
*seconds_since_last_played = GetCurrentTime() - last_playback_time_;
return (*seconds_since_last_played >= 0) ? true : false;
}
int64_t PolicyEngine::GetLicenseOrPlaybackDurationRemaining() {
const int64_t current_time = clock_->GetCurrentTime();
const int64_t current_time = GetCurrentTime();
const int64_t expiry_time =
GetExpiryTime(current_time,
/* ignore_soft_enforce_playback_duration */ false);
@@ -331,7 +341,7 @@ void PolicyEngine::RestorePlaybackTimes(int64_t playback_start_time,
playback_start_time_ = grace_period_end_time;
}
const int64_t current_time = clock_->GetCurrentTime();
const int64_t current_time = GetCurrentTime();
const int64_t expiry_time =
GetExpiryTime(current_time,
/* ignore_soft_enforce_playback_duration */ true);
@@ -402,6 +412,8 @@ int64_t PolicyEngine::GetLicenseOrRentalDurationRemaining(
if (license_expiry_time == NEVER_EXPIRES) return LLONG_MAX;
if (license_expiry_time < current_time) return 0;
const int64_t policy_license_duration = policy_.license_duration_seconds();
if (policy_license_duration == NEVER_EXPIRES)
return license_expiry_time - current_time;
return std::min(license_expiry_time - current_time, policy_license_duration);
}
@@ -469,6 +481,15 @@ void PolicyEngine::NotifyExpirationUpdate(int64_t current_time) {
last_expiry_time_set_ = true;
}
int64_t PolicyEngine::GetCurrentTime() {
int64_t current_time = clock_->GetCurrentTime();
if (current_time + kClockSkewDelta < last_recorded_current_time_)
current_time = last_recorded_current_time_;
else
last_recorded_current_time_ = current_time;
return current_time;
}
void PolicyEngine::set_clock(Clock* clock) { clock_.reset(clock); }
} // namespace wvcdm

View File

@@ -5,9 +5,17 @@
#include "crypto_session.h"
#include "license.h"
#include "log.h"
#include "wv_cdm_constants.h"
namespace {
std::string kEmptyString;
uint64_t kOldUsageEntryTimeSinceLicenseReceived = 0;
uint64_t kOldUsageEntryTimeSinceFirstDecrypt = 0;
uint64_t kOldUsageEntryTimeSinceLastDecrypt = 0;
std::string kOldUsageEntryServerMacKey(wvcdm::MAC_KEY_SIZE, 0);
std::string kOldUsageEntryClientMacKey(wvcdm::MAC_KEY_SIZE, 0);
std::string kOldUsageEntryPoviderSessionToken =
"nahZ6achSheiqua3TohQuei0ahwohv";
}
namespace wvcdm {
@@ -38,30 +46,43 @@ bool UsageTableHeader::Init(CdmSecurityLevel security_level,
}
security_level_ = security_level;
requested_security_level_ =
security_level_ == kSecurityLevelL3 ? kLevel3 : kLevelDefault;
if (!file_handle_->Init(security_level)) {
LOGE("UsageTableHeader::Init: device files initialization failed");
return false;
}
if (!file_handle_->RetrieveUsageTableInfo(&usage_table_header_,
CdmResponseType status = USAGE_INFO_NOT_FOUND;
if (file_handle_->RetrieveUsageTableInfo(&usage_table_header_,
&usage_entry_info_)) {
CdmResponseType status =
crypto_session->CreateUsageTableHeader(&usage_table_header_);
if (status != NO_ERROR) return false;
file_handle_->StoreUsageTableInfo(usage_table_header_, usage_entry_info_);
} else {
CdmResponseType status =
crypto_session->LoadUsageTableHeader(usage_table_header_);
status = crypto_session->LoadUsageTableHeader(usage_table_header_);
if (status != NO_ERROR) {
LOGE(
"UsageTableHeader::Init: load usage table failed, security level: %d",
security_level);
return false;
file_handle_->DeleteAllLicenses();
usage_entry_info_.clear();
usage_table_header_.clear();
status = crypto_session->CreateUsageTableHeader(&usage_table_header_);
if (status != NO_ERROR) return false;
file_handle_->StoreUsageTableInfo(usage_table_header_, usage_entry_info_);
}
} else {
status = crypto_session->CreateUsageTableHeader(&usage_table_header_);
if (status != NO_ERROR) return false;
file_handle_->StoreUsageTableInfo(usage_table_header_, usage_entry_info_);
metrics::CryptoMetrics alternate_metrics;
metrics::CryptoMetrics* metrics =
crypto_session->GetCryptoMetrics() != nullptr ?
crypto_session->GetCryptoMetrics() : &alternate_metrics;
UpgradeFromUsageTable(file_handle_.get(), metrics);
file_handle_->StoreUsageTableInfo(usage_table_header_, usage_entry_info_);
}
requested_security_level_ =
security_level_ == kSecurityLevelL3 ? kLevel3 : kLevelDefault;
is_inited_ = true;
return true;
}
@@ -141,8 +162,11 @@ CdmResponseType UsageTableHeader::DeleteEntry(uint32_t usage_entry_number,
metrics::CryptoMetrics* metrics) {
LOGV("UsageTableHeader::DeleteEntry: Lock");
AutoLock auto_lock(usage_table_header_lock_);
if (usage_entry_number >= usage_entry_info_.size())
if (usage_entry_number >= usage_entry_info_.size()) {
LOGE("UsageTableHeader::DeleteEntry: usage entry number %d larger than "
"usage entry size %d", usage_entry_number, usage_entry_info_.size());
return USAGE_INVALID_PARAMETERS_1;
}
// Find the last valid entry number, in order to swap
size_t swap_entry_number = usage_entry_info_.size() - 1;
@@ -427,7 +451,7 @@ bool UsageTableHeader::UpgradeLicensesFromUsageTable(
// * save the usage table header and store the usage entry number and
// usage entry along with the license to persistent memory
std::vector<std::string> key_set_ids;
if (handle->ListLicenses(&key_set_ids)) {
if (!handle->ListLicenses(&key_set_ids)) {
LOGW(
"UpgradeUsageTableHeader::UpgradeLicensesFromUsageTable: unable to "
"retrieve list of licenses");
@@ -470,6 +494,9 @@ bool UsageTableHeader::UpgradeLicensesFromUsageTable(
if (status != NO_ERROR) continue;
// TODO(fredgc): remove when b/65730828 is addressed
if (!CreateDummyOldUsageEntry(&crypto_session)) continue;
status = AddEntry(&crypto_session, true /* persistent license */,
key_set_ids[i], kEmptyString, &usage_entry_number);
@@ -518,7 +545,7 @@ bool UsageTableHeader::UpgradeUsageInfoFromUsageTable(
// information to persistent memory along with usage entry number and usage
// entry.
std::vector<std::string> usage_info_file_names;
if (handle->ListUsageInfoFiles(&usage_info_file_names)) {
if (!handle->ListUsageInfoFiles(&usage_info_file_names)) {
LOGW(
"UpgradeUsageTableHeader::UpgradeUsageInfoFromUsageTable: Unable to "
"retrieve list of usage info file names");
@@ -535,7 +562,7 @@ bool UsageTableHeader::UpgradeUsageInfoFromUsageTable(
continue;
}
for (size_t j = 0; j < usage_data.size(); --j) {
for (size_t j = 0; j < usage_data.size(); ++j) {
if (usage_data[j].provider_session_token.empty()) {
LOGW(
"UsageTableHeader::UpgradeUsageInfoFromUsageTable: Provider "
@@ -548,16 +575,19 @@ bool UsageTableHeader::UpgradeUsageInfoFromUsageTable(
if (status != NO_ERROR) continue;
// TODO(fredgc): remove when b/65730828 is addressed
if (!CreateDummyOldUsageEntry(&crypto_session)) continue;
// TODO(rfrias): We need to fill in the app id, but it is hashed
// and we have no way to extract. Use the hased filename instead?
status = AddEntry(&crypto_session, false /* usage info */,
usage_data[j].key_set_id, kEmptyString,
&(usage_data[i].usage_entry_number));
usage_data[j].key_set_id, usage_info_file_names[i],
&(usage_data[j].usage_entry_number));
if (status != NO_ERROR) continue;
status = crypto_session.CopyOldUsageEntry(
usage_data[i].provider_session_token);
usage_data[j].provider_session_token);
if (status != NO_ERROR) {
crypto_session.Close();
@@ -565,7 +595,7 @@ bool UsageTableHeader::UpgradeUsageInfoFromUsageTable(
continue;
}
status = UpdateEntry(&crypto_session, &(usage_data[i].usage_entry));
status = UpdateEntry(&crypto_session, &(usage_data[j].usage_entry));
if (status != NO_ERROR) {
crypto_session.Close();
@@ -586,4 +616,16 @@ bool UsageTableHeader::UpgradeUsageInfoFromUsageTable(
return NO_ERROR;
}
// TODO(fredgc): remove when b/65730828 is addressed
bool UsageTableHeader::CreateDummyOldUsageEntry(CryptoSession* crypto_session) {
return crypto_session->CreateOldUsageEntry(
kOldUsageEntryTimeSinceLicenseReceived,
kOldUsageEntryTimeSinceFirstDecrypt,
kOldUsageEntryTimeSinceLastDecrypt,
CryptoSession::kUsageDurationsInvalid,
kOldUsageEntryServerMacKey,
kOldUsageEntryClientMacKey,
kOldUsageEntryPoviderSessionToken);
}
} // namespace wvcdm