Source release 19.6.0

GitOrigin-RevId: 13a33e34413c19da1bfe76abcc66be519c9ac9d1
This commit is contained in:
Googler
2025-05-30 14:47:25 -07:00
committed by mattfedd
parent f7ec4fdeff
commit 6d36a0c93d
59 changed files with 3327 additions and 1491 deletions

View File

@@ -1306,6 +1306,13 @@ CdmResponseType CdmEngine::HandleProvisioningResponse(
LOGE("Device has been revoked, cannot provision: status = %s",
ret.ToString().c_str());
cert_provisioning_.reset();
} else if (ret == PROVISIONING_4_STALE_RESPONSE) {
// The response is considered "stale" (likely from generating multiple
// requests, and providing out of order responses).
// Drop message without returning error or resetting
// provisioning context.
LOGW("Stale response, app may try again");
return CdmResponseType(NO_ERROR);
} else {
// It is possible that a provisioning attempt was made after this one was
// requested but before the response was received, which will cause this
@@ -1352,8 +1359,7 @@ CdmProvisioningStatus CdmEngine::GetProvisioningStatus(
return kUnknownProvisionStatus;
}
UsagePropertySet property_set;
if (handle.HasCertificate(property_set.use_atsc_mode())) {
if (handle.HasCertificate(/* atsc_mode_enabled = */ false)) {
return kProvisioned;
}
if (crypto_session->GetPreProvisionTokenType() == kClientTokenBootCertChain) {
@@ -1376,8 +1382,8 @@ CdmResponseType CdmEngine::Unprovision(CdmSecurityLevel security_level) {
LOGD("OKP fallback to L3");
security_level = kSecurityLevelL3;
}
// Devices with baked-in DRM certs cannot be reprovisioned and therefore must
// not be unprovisioned.
// Devices with baked-in DRM certs cannot be reprovisioned
// and therefore must not be unprovisioned.
std::unique_ptr<CryptoSession> crypto_session(
CryptoSession::MakeCryptoSession(metrics_->GetCryptoMetrics()));
CdmClientTokenType token_type = kClientTokenUninitialized;
@@ -1396,18 +1402,78 @@ CdmResponseType CdmEngine::Unprovision(CdmSecurityLevel security_level) {
LOGE("Unable to initialize device files");
return CdmResponseType(UNPROVISION_ERROR_1);
}
// TODO(b/141705730): Remove usage entries during unprovisioning.
if (!file_system_->IsGlobal()) {
if (!handle.RemoveCertificate() || !handle.RemoveOemCertificate()) {
LOGE("Unable to delete certificate");
// This if statement is misleading. There is no consistent
// concept of "global" vs "per-app/origin" storage in the
// core library. Android vs CE CDM behave very different.
// On CE device:
// file_system_->IsGlobal() is always true, even if app/origin
// specific.
// On Android:
// file_system_->IsGlobal() is always false, except for some C++
// test code.
// TODO(b/142280599): Refactor this once CE CDM SPOIDs are supported
// by the file system. May require moving platform-dependent behavior
// to the platform-dependent layer. Only have this remove the
// certificate and nothing else.
if (!file_system_->IsGlobal()) { // AKA is Android
// TODO(b/141705730): Remove usage entries during unprovisioning.
// Not considered an error if no certificate exists.
if (handle.HasCertificate(/* atsc_mode_enabled = */ false) &&
!handle.RemoveCertificate()) {
LOGE("Unable to delete DRM certificate");
return CdmResponseType(UNPROVISION_ERROR_2);
}
// Maintaining old behavior expected by Android.
const CdmResponseType oem_cert_status = UnprovisionOemCert(security_level);
if (oem_cert_status != NO_ERROR) return oem_cert_status;
} else { // AKA is CE CDM (or some Android tests)
// On CE CDM, deleting all files only deletes the app/origin
// specific files.
// On Android, this will delete all files (only possible
// during testing).
if (!handle.DeleteAllFiles()) {
LOGE("Unable to delete files");
return CdmResponseType(UNPROVISION_ERROR_3);
}
}
return CdmResponseType(NO_ERROR);
}
CdmResponseType CdmEngine::UnprovisionOemCert(CdmSecurityLevel security_level) {
LOGI("security_level = %s", CdmSecurityLevelToString(security_level));
if (security_level == kSecurityLevelL1 && OkpIsInFallbackMode()) {
LOGD("OKP fallback to L3");
security_level = kSecurityLevelL3;
}
// Only BCC-based system have an OEM certificate that can
// unprovisioned.
// Prov 3.0 system's OEM certs are built into the TEE.
std::unique_ptr<CryptoSession> crypto_session(
CryptoSession::MakeCryptoSession(metrics_->GetCryptoMetrics()));
CdmClientTokenType token_type = kClientTokenUninitialized;
const CdmResponseType res = crypto_session->GetProvisioningMethod(
security_level == kSecurityLevelL3 ? kLevel3 : kLevelDefault,
&token_type);
if (res != NO_ERROR) {
return res;
}
if (token_type != kClientTokenBootCertChain) {
LOGD("Device does not support OEM certificate unprovisioning");
return CdmResponseType(NO_ERROR);
}
if (!handle.DeleteAllFiles()) {
LOGE("Unable to delete files");
return CdmResponseType(UNPROVISION_ERROR_3);
// For Prov 4.0 devices, this will cause every app/origin client
// to lose their offline content for the same TEE security level.
wvutil::FileSystem global_file_system;
DeviceFiles global_handle(&global_file_system);
if (!global_handle.Init(security_level)) {
LOGE("Unable to initialize global device files");
return CdmResponseType(UNPROVISION_ERROR_1);
}
// Not considered an error if no certificate exists.
if (global_handle.HasOemCertificate() &&
!global_handle.RemoveOemCertificate()) {
LOGE("Unable to delete OEM certificate");
return CdmResponseType(UNPROVISION_ERROR_2);
}
return CdmResponseType(NO_ERROR);
}

View File

@@ -160,7 +160,9 @@ CdmResponseType CdmSession::Init(CdmClientPropertySet* cdm_client_property_set,
return CdmResponseType(NEED_PROVISIONING);
// Require reprovisioning if the root of trust has changed
if (HasRootOfTrustBeenRenewed()) return CdmResponseType(NEED_PROVISIONING);
if (HasRootOfTrustBeenRenewed(forced_session_id)) {
return CdmResponseType(NEED_PROVISIONING);
}
if (forced_session_id) {
key_set_id_ = *forced_session_id;
@@ -278,8 +280,8 @@ CdmResponseType CdmSession::RestoreOfflineSession(const CdmKeySetId& key_set_id,
// Only restore offline licenses if they are active or this is a release
// retry.
if (!(license_type == kLicenseTypeRelease ||
license_data.state == kLicenseStateActive)) {
if (license_type != kLicenseTypeRelease &&
license_data.state != kLicenseStateActive) {
LOGE("Invalid offline license state: state = %s, license_type = %s",
CdmOfflineLicenseStateToString(license_data.state),
CdmLicenseTypeToString(license_type));
@@ -335,6 +337,12 @@ CdmResponseType CdmSession::RestoreOfflineSession(const CdmKeySetId& key_set_id,
if (result != NO_ERROR) return result;
}
if (!license_data.exported_license_data.empty()) {
result =
crypto_session_->LoadLicenseData(license_data.exported_license_data);
if (result != NO_ERROR) return result;
}
if (license_type == kLicenseTypeRelease) {
result = license_parser_->RestoreLicenseForRelease(
license_data.drm_certificate, key_request_, key_response_);
@@ -596,6 +604,10 @@ CdmResponseType CdmSession::AddKeyInternal(const CdmKeyResponse& key_response) {
if (sts != KEY_ADDED)
return (sts == KEY_ERROR) ? CdmResponseType(ADD_KEY_ERROR) : sts;
// If we are L1 or export is not supported, this call will do nothing.
sts = crypto_session_->SaveLicenseData(&exported_license_data_);
if (sts != NO_ERROR) return sts;
license_received_ = true;
key_response_ = key_response;
@@ -993,7 +1005,8 @@ bool CdmSession::StoreLicense(CdmOfflineLicenseState state, int* error_detail) {
usage_entry_,
usage_entry_index_,
drm_certificate_,
wrapped_private_key_};
wrapped_private_key_,
exported_license_data_};
bool result = file_handle_->StoreLicense(license_data, &error_detail_alt);
if (error_detail != nullptr) {
@@ -1246,14 +1259,16 @@ CdmResponseType CdmSession::LoadPrivateKey(
// Use a change in system ID as an indication that Root of Trust
// has been renewed.
bool CdmSession::HasRootOfTrustBeenRenewed() {
bool CdmSession::HasRootOfTrustBeenRenewed(bool is_load) {
if (atsc_mode_enabled_) return false;
// Ignore System ID changes for non-Rikers L3 as the root of trust might not
// have changed even if the system ID has.
// have changed even if the system ID has. Also ignore for the Rikers L3 when
// loading an existing license since we can still load it without renewing.
if (crypto_session_->GetSecurityLevel() == kSecurityLevelL3 &&
crypto_session_->GetPreProvisionTokenType() !=
kClientTokenDrmCertificateReprovisioning) {
(crypto_session_->GetPreProvisionTokenType() !=
kClientTokenDrmCertificateReprovisioning ||
is_load)) {
return false;
}

View File

@@ -815,8 +815,7 @@ CdmResponseType CdmUsageTable::StoreEntry(UsageEntryIndex entry_index,
case kStorageUsageInfo: {
UsageEntry retrieved_entry;
UsageEntryIndex retrieved_entry_index;
std::string provider_session_token, init_data, key_request, key_response,
key_renewal_request;
std::string provider_session_token, key_request, key_response;
std::string drm_certificate;
CryptoWrappedKey wrapped_private_key;
if (!device_files->RetrieveUsageInfoByKeySetId(

View File

@@ -1,9 +1,10 @@
// Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary
// source code may only be used and distributed under the Widevine License
// Agreement.
#include "certificate_provisioning.h"
#include <algorithm>
#include "client_identification.h"
#include "crypto_wrapped_key.h"
#include "device_files.h"
@@ -88,6 +89,127 @@ bool RetrieveOemCertificateAndLoadPrivateKey(CryptoSession& crypto_session,
return true;
}
// Checks if any instances of |needle| sequences found in the |haystack|.
//
// Special cases:
// - An empty |needle| is always present, even if |haystack| is empty.
// Note: This is a convention used by many string utility
// libraries.
bool StringContains(const std::string& haystack, const std::string& needle) {
if (needle.empty()) return true;
if (haystack.size() < needle.size()) return false;
return haystack.find(needle) != std::string::npos;
}
// Checks if the |needle| sequences found at the end of |haystack|.
//
// Special cases:
// - An empty |needle| is always present, even if |haystack| is empty.
// Note: This is a convention used by many string utility
// libraries.
bool StringEndsWith(const std::string& haystack, const std::string& needle) {
if (haystack.size() < needle.size()) return false;
return std::equal(haystack.rbegin(), haystack.rbegin() + needle.size(),
needle.rbegin(), needle.rend());
}
// Checks the actual length of an ASN.1 DER encoded message
// roughly matches the expected length from within the message.
// Technically, the DER message may contain some trailing
// end-of-contents bytes (at most 2).
//
// Parameters:
// |actual_length| - The real length of the DER message
// |expected_length| - The reported length of the DER message plus
// the header bytes parsed.
bool IsAsn1ExpectedLength(size_t actual_length, size_t expected_length) {
return actual_length >= expected_length &&
actual_length <= (expected_length + 2);
}
// Checks if the provided |message| resembles ASN.1 DER encoded
// message.
// This is a light check, it verifies the type (SEQUENCE) and that
// the encoded length matches the total message length.
bool IsAsn1DerSequenceLike(const std::string& message) {
// Anything less than 3 bytes will not be an ASN.1 sequence.
if (message.size() < 3) return false;
// Verify type header
// class = universal(0) - bits 6-7
// p/c = constructed(1) - bit 5
// tag = sequence(0x10) - bits 0-4
static constexpr uint8_t kUniversal = (0 << 6);
static constexpr uint8_t kConstructBit = (1 << 5);
static constexpr uint8_t kSequenceTag = 0x10;
static constexpr uint8_t kSequenceHeader =
kUniversal | kConstructBit | kSequenceTag;
const uint8_t type_header = static_cast<uint8_t>(message.front());
if (type_header != kSequenceHeader) return false;
// Verify length.
const uint8_t length_header = static_cast<uint8_t>(message[1]);
// A reserved length is never used. If |length_header| is
// reserved length, then this is not an ASN.1 message.
static constexpr uint8_t kReservedLength = 0xff;
if (length_header == kReservedLength) return false;
static constexpr uint8_t kIndefiniteLength = 0x80;
if (length_header == kIndefiniteLength) {
// If length is indefinite, then search for two "end of contents"
// octets at the end.
static constexpr uint8_t kAsnEndOfContents = 0x00;
const std::string kDoubleEoc(2, kAsnEndOfContents);
return StringEndsWith(message, kDoubleEoc);
}
// Definite lengths may be long or short (most likely long for our case).
static constexpr uint8_t kLongLengthBit = 0x80;
if ((length_header & kLongLengthBit) != kLongLengthBit) {
// Short length (unlikely, but check anyways).
// For short lengths, the value component of the length
// header is the payload length.
static constexpr uint8_t kShortLengthMask = 0x7f;
const size_t payload_length =
static_cast<size_t>(length_header & kShortLengthMask);
// The total message is: type header + length header + payload.
const size_t total_length = 2 + payload_length;
return IsAsn1ExpectedLength(message.size(), total_length);
}
// Long length.
// |length_header| contains the number of bytes following the
// length header containing the payload length.
static constexpr uint8_t kLengthSizeMask = 0x7f;
const size_t length_length =
static_cast<size_t>(length_header & kLengthSizeMask);
// For long-lengths, the first two bytes were type header and
// length header.
static constexpr size_t kPayloadLengthOffset = 2;
// If the message is smaller than needed to obtain the length,
// it is either not ASN.1 (or an incomplete message, which is still
// invalid).
if ((message.size()) < (length_length + kPayloadLengthOffset)) return false;
// DER encoding should use the minimum number of bytes necessary
// to encode the length, and if the number of bytes to encode the
// length is more than 3 (payload is larged than 16 MB) which is much
// larger than any expected certificate chain.
if (length_length > 3) return false;
// Decode the length as big-endian.
size_t payload_length = 0;
for (size_t i = 0; i < length_length; i++) {
// Casting from char to uint8_t to size_t is necessary.
const uint8_t length_byte =
static_cast<uint8_t>(message[kPayloadLengthOffset + i]);
payload_length = (payload_length << 8) + static_cast<size_t>(length_byte);
}
// Total message is: type header + length header + payload length + payload.
const size_t total_length = 2 + length_length + payload_length;
return IsAsn1ExpectedLength(message.size(), total_length);
}
} // namespace
// Protobuf generated classes.
using video_widevine::DrmCertificate;
@@ -99,6 +221,25 @@ using video_widevine::PublicKeyToCertify;
using video_widevine::SignedDrmCertificate;
using video_widevine::SignedProvisioningMessage;
// static
const char* CertificateProvisioning::StateToString(State state) {
switch (state) {
case kUninitialized:
return "Uninitialized";
case kInitialized:
return "Initialized";
case kDrmRequestSent:
return "DrmRequestSent";
case kDrmResponseReceived:
return "DrmResponseReceived";
case kOemRequestSent:
return "OemRequestSent";
case kOemResponseReceived:
return "OemResponseReceived";
}
return "<unknown>";
}
// static
void CertificateProvisioning::GetProvisioningServerUrl(
std::string* default_url) {
@@ -115,7 +256,11 @@ CdmResponseType CertificateProvisioning::Init(
service_certificate.empty()
? wvutil::a2bs_hex(kCpProductionServiceCertificate)
: service_certificate;
return service_certificate_->Init(certificate);
const CdmResponseType result = service_certificate_->Init(certificate);
if (result == NO_ERROR) {
state_ = kInitialized;
}
return result;
}
// Fill in the appropriate SPOID (Stable Per-Origin IDentifier) option.
@@ -207,11 +352,18 @@ CdmResponseType CertificateProvisioning::GetProvisioningRequestInternal(
default_url->assign(kProvisioningServerUrl);
if (state_ != kInitialized) {
LOGD("Overriding old request: state = %s", StateToString(state_));
// Once the previous session is closed, there is no way to complete
// an in-flight request.
state_ = kInitialized;
}
CloseSession();
CdmResponseType status = crypto_session_->Open(requested_security_level);
if (NO_ERROR != status) {
LOGE("Failed to create a crypto session: status = %d",
static_cast<int>(status));
LOGE("Failed to create a crypto session: status = %s",
status.ToString().c_str());
return status;
}
@@ -300,6 +452,7 @@ CdmResponseType CertificateProvisioning::GetProvisioningRequestInternal(
} else {
*request = std::move(serialized_request);
}
state_ = kDrmRequestSent;
return CdmResponseType(NO_ERROR);
}
@@ -325,7 +478,11 @@ CdmResponseType CertificateProvisioning::GetProvisioning40RequestInternal(
return CdmResponseType(PROVISIONING_4_FAILED_TO_INITIALIZE_DEVICE_FILES);
}
ProvisioningRequest provisioning_request;
if (!service_certificate_) {
LOGE("Service certificate not set");
return CdmResponseType(CERT_PROVISIONING_EMPTY_SERVICE_CERTIFICATE);
}
// Determine the current stage by checking if OEM cert exists.
std::string stored_oem_cert;
if (global_file_handle.HasOemCertificate()) {
@@ -342,9 +499,11 @@ CdmResponseType CertificateProvisioning::GetProvisioning40RequestInternal(
}
}
}
const bool is_oem_prov_request = stored_oem_cert.empty();
// Retrieve the Spoid, but put it to the client identification instead, so it
// is encrypted.
ProvisioningRequest provisioning_request;
CdmAppParameterMap additional_parameter;
CdmResponseType status =
SetSpoidParameter(origin, spoid, &provisioning_request);
@@ -364,7 +523,7 @@ CdmResponseType CertificateProvisioning::GetProvisioning40RequestInternal(
provisioning_request.clear_stable_id();
}
if (stored_oem_cert.empty()) {
if (is_oem_prov_request) {
// This is the first stage provisioning.
default_url->assign(std::string(kProvisioningServerUrl) +
kProv40FirstStageServerUrlSuffix);
@@ -378,8 +537,8 @@ CdmResponseType CertificateProvisioning::GetProvisioning40RequestInternal(
// Since |stored_oem_cert| is empty, the client identification token will be
// retrieved from OEMCrypto, which is the BCC in this case.
status = FillEncryptedClientId(stored_oem_cert, provisioning_request,
wv_service_cert);
status = FillEncryptedClientId(/* client_token = */ std::string(),
provisioning_request, wv_service_cert);
if (status != NO_ERROR) return status;
} else {
// This is the second stage provisioning.
@@ -417,25 +576,24 @@ CdmResponseType CertificateProvisioning::GetProvisioning40RequestInternal(
std::string public_key;
std::string public_key_signature;
provisioning_40_wrapped_private_key_.clear();
provisioning_40_key_type_ = CryptoWrappedKey::kUninitialized;
std::string wrapped_private_key;
CryptoWrappedKey::Type private_key_type = CryptoWrappedKey::kUninitialized;
status = crypto_session_->GenerateCertificateKeyPair(
&public_key, &public_key_signature, &provisioning_40_wrapped_private_key_,
&provisioning_40_key_type_);
&public_key, &public_key_signature, &wrapped_private_key,
&private_key_type);
if (status != NO_ERROR) return status;
PublicKeyToCertify* key_to_certify =
provisioning_request.mutable_certificate_public_key();
key_to_certify->set_public_key(public_key);
key_to_certify->set_signature(public_key_signature);
key_to_certify->set_key_type(provisioning_40_key_type_ ==
CryptoWrappedKey::kRsa
key_to_certify->set_key_type(private_key_type == CryptoWrappedKey::kRsa
? PublicKeyToCertify::RSA
: PublicKeyToCertify::ECC);
std::string serialized_message;
provisioning_request.SerializeToString(&serialized_message);
provisioning_request_message_ = serialized_message;
prov40_request_ = serialized_message;
SignedProvisioningMessage signed_provisioning_msg;
signed_provisioning_msg.set_message(serialized_message);
@@ -491,6 +649,15 @@ CdmResponseType CertificateProvisioning::GetProvisioning40RequestInternal(
*request = std::move(serialized_request);
}
request_ = std::move(serialized_message);
// Need the wrapped Prov 4.0 private key to store once the response
// is received. The wrapped key is not available in the response.
prov40_wrapped_private_key_ =
CryptoWrappedKey(private_key_type, std::move(wrapped_private_key));
// Store the public key from the request. This is used to match
// up the response with the most recently generated request.
prov40_public_key_ = std::move(public_key);
state_ = is_oem_prov_request ? kOemRequestSent : kDrmRequestSent;
return CdmResponseType(NO_ERROR);
}
@@ -553,6 +720,18 @@ CdmResponseType CertificateProvisioning::HandleProvisioning40Response(
return CdmResponseType(PROVISIONING_4_RESPONSE_HAS_ERROR_STATUS);
}
}
if (state_ == kOemResponseReceived || state_ == kDrmResponseReceived) {
// A response has already been received (successfully), this
// response can be silently dropped.
LOGW("Response already received: state = %s", StateToString(state_));
return CdmResponseType(NO_ERROR);
}
if (state_ != kOemRequestSent && state_ != kDrmRequestSent) {
LOGE("Not expecting a response: state = %s", StateToString(state_));
return CdmResponseType(PROVISIONING_UNEXPECTED_RESPONSE_ERROR);
}
LOGD("Handling response: state = %s", StateToString(state_));
const bool is_oem_prov_response = (state_ == kOemRequestSent);
const std::string& device_certificate =
provisioning_response.device_certificate();
@@ -561,17 +740,16 @@ CdmResponseType CertificateProvisioning::HandleProvisioning40Response(
return CdmResponseType(PROVISIONING_4_RESPONSE_HAS_NO_CERTIFICATE);
}
if (provisioning_40_wrapped_private_key_.empty()) {
LOGE("No private key was generated");
if (!prov40_wrapped_private_key_.IsValid() || prov40_public_key_.empty()) {
LOGE("No %s key was generated",
!prov40_wrapped_private_key_.IsValid() ? "private" : "public");
return CdmResponseType(PROVISIONING_4_NO_PRIVATE_KEY);
}
const CryptoWrappedKey private_key(provisioning_40_key_type_,
provisioning_40_wrapped_private_key_);
if (cert_type_ == kCertificateX509) {
// Load csr private key to decrypt session key
auto status = crypto_session_->LoadCertificatePrivateKey(private_key);
auto status =
crypto_session_->LoadCertificatePrivateKey(prov40_wrapped_private_key_);
if (status != NO_ERROR) {
LOGE("Failed to load x509 certificate.");
return status;
@@ -582,9 +760,8 @@ CdmResponseType CertificateProvisioning::HandleProvisioning40Response(
const std::string& signature = signed_response.signature();
const std::string& core_message = signed_response.oemcrypto_core_message();
status = crypto_session_->LoadProvisioningCast(
signed_response.session_key(), provisioning_request_message_,
response_message, core_message, signature,
&cast_cert_private_key.key());
signed_response.session_key(), prov40_request_, response_message,
core_message, signature, &cast_cert_private_key.key());
if (status != NO_ERROR) {
LOGE("Failed to generate wrapped key for cast cert.");
return status;
@@ -594,11 +771,79 @@ CdmResponseType CertificateProvisioning::HandleProvisioning40Response(
*cert = device_certificate;
*wrapped_key = cast_cert_private_key.key();
state_ = is_oem_prov_response ? kOemResponseReceived : kDrmResponseReceived;
prov40_wrapped_private_key_.Clear();
prov40_public_key_.clear();
return CdmResponseType(NO_ERROR);
}
// Verify that the response contains the same key as the request.
// It is possible that multiple requests were generated, the CDM
// can only accept the response from the most recently generated
// one.
//
// Check the first few bytes to determine the type of message.
// OEM responses:
// ASN.1 DER encoded ContentInfo (containing an X.509 certificate).
// DRM responses:
// Protobuf SignedDrmCertificate
if (is_oem_prov_response) {
// Here |device_certificate| (haystack) is an X.509 cert chain, and
// |prov40_public_key_| (needle) is a SubjectPublicKeyInfo.
// The cert chain should contain a byte-for-byte copy of the
// public key.
// TODO(b/391469176): Use RSA/ECC key loading to detected mismatched
// keys.
if (!StringContains(/* haystack = */ device_certificate,
/* needle */ prov40_public_key_)) {
LOGD("OEM response is stale");
return CdmResponseType(PROVISIONING_4_STALE_RESPONSE);
}
} else { // Is DRM response
video_widevine::SignedDrmCertificate signed_certificate;
if (!signed_certificate.ParseFromString(device_certificate)) {
// Check if ASN.1 like.
if (IsAsn1DerSequenceLike(device_certificate)) {
// This might be a late OEM certificate response
// generated from before the DRM response was received.
LOGD("Received late OEM certificate response");
return CdmResponseType(PROVISIONING_4_STALE_RESPONSE);
}
LOGE("Unable to parse Signed DRM certificate");
return CdmResponseType(PROVISIONING_4_FAILED_TO_VERIFY_CERT_KEY);
}
video_widevine::DrmCertificate drm_certificate;
if (!drm_certificate.ParseFromString(
signed_certificate.drm_certificate())) {
LOGE("Unable to parse DRM certificate");
return CdmResponseType(PROVISIONING_4_FAILED_TO_VERIFY_CERT_KEY);
}
// The sent public key is of the format SubjectPublicKeyInfo;
// however, the received format is RSAPublicKey (RSA only) or
// SubjectPublicKeyInfo (ECC, and future RSA).
// Here |prov40_public_key_| (haystack) is SubjectPublicKeyInfo,
// and |drm_certificate.public_key()| (needle) may be
// SubjectPublicKeyInfo or RSAPublicKey.
// If the DRM cert's public key is in SubjectPublicKeyInfo format
// it should be a byte-for-byte copy. If the DRM cert's public key
// is RSAPublicKey format then hopefully a byte-for-byte copy is
// found within the SubjectPublicKeyInfo. Note: SubjectPublicKeyInfo
// containing an RSA public key uses RSAPublicKey to store the
// key fields.
// TODO(b/391469176): Use RSA/ECC key loading to detected mismatched
// keys.
if (!StringContains(/* haystack = */ prov40_public_key_,
/* needle = */ drm_certificate.public_key())) {
// This might be a response from a previously generated DRM
// certificate response.
LOGD("DRM response is stale");
return CdmResponseType(PROVISIONING_4_STALE_RESPONSE);
}
}
// Can clear the |prov40_public_key_| after validating.
prov40_public_key_.clear();
const CdmSecurityLevel security_level = crypto_session_->GetSecurityLevel();
CloseSession();
wvutil::FileSystem global_file_system;
DeviceFiles global_file_handle(&global_file_system);
if (!global_file_handle.Init(security_level)) {
@@ -608,10 +853,21 @@ CdmResponseType CertificateProvisioning::HandleProvisioning40Response(
// Check the stage of the provisioning by checking if an OEM cert is already
// stored in the file system.
if (!global_file_handle.HasOemCertificate()) {
if (is_oem_prov_response) {
if (global_file_handle.HasOemCertificate()) {
// Possible that concurrent apps were generated provisioning
// requests, and this one arrived after an other one.
LOGI("CDM has already received an OEM certificate");
CloseSession();
state_ = kOemResponseReceived;
prov40_wrapped_private_key_.Clear();
prov40_public_key_.clear();
return CdmResponseType(NO_ERROR);
}
// No OEM cert already stored => the response is expected to be an OEM cert.
if (!global_file_handle.StoreOemCertificate(device_certificate,
private_key)) {
prov40_wrapped_private_key_)) {
LOGE("Failed to store provisioning 4 OEM certificate");
return CdmResponseType(PROVISIONING_4_FAILED_TO_STORE_OEM_CERTIFICATE);
}
@@ -629,20 +885,27 @@ CdmResponseType CertificateProvisioning::HandleProvisioning40Response(
LOGW("Failed to extract system id from OEM certificate");
}
}
} else {
// The response is assumed to be an DRM cert.
DeviceFiles per_origin_file_handle(file_system);
if (!per_origin_file_handle.Init(security_level)) {
LOGE("Failed to initialize per-origin DeviceFiles");
return CdmResponseType(
PROVISIONING_4_FAILED_TO_INITIALIZE_DEVICE_FILES_3);
}
if (!per_origin_file_handle.StoreCertificate(device_certificate,
private_key)) {
LOGE("Failed to store provisioning 4 DRM certificate");
return CdmResponseType(PROVISIONING_4_FAILED_TO_STORE_DRM_CERTIFICATE);
}
CloseSession();
state_ = kOemResponseReceived;
prov40_wrapped_private_key_.Clear();
prov40_public_key_.clear();
return CdmResponseType(NO_ERROR);
}
// The response is assumed to be a DRM cert.
DeviceFiles per_origin_file_handle(file_system);
if (!per_origin_file_handle.Init(security_level)) {
LOGE("Failed to initialize per-origin DeviceFiles");
return CdmResponseType(PROVISIONING_4_FAILED_TO_INITIALIZE_DEVICE_FILES_3);
}
if (!per_origin_file_handle.StoreCertificate(device_certificate,
prov40_wrapped_private_key_)) {
LOGE("Failed to store provisioning 4 DRM certificate");
return CdmResponseType(PROVISIONING_4_FAILED_TO_STORE_DRM_CERTIFICATE);
}
CloseSession();
state_ = kDrmResponseReceived;
prov40_wrapped_private_key_.Clear();
prov40_public_key_.clear();
return CdmResponseType(NO_ERROR);
}
@@ -692,6 +955,15 @@ CdmResponseType CertificateProvisioning::HandleProvisioningResponse(
wrapped_key);
}
if (state_ == kDrmResponseReceived) {
LOGD("Response already received");
return CdmResponseType(NO_ERROR);
}
if (state_ != kDrmRequestSent) {
LOGE("Not expecting a response: state = %s", StateToString(state_));
return CdmResponseType(PROVISIONING_UNEXPECTED_RESPONSE_ERROR);
}
bool error = false;
if (!signed_response.has_signature()) {
LOGE("Signed response does not have signature");
@@ -767,6 +1039,7 @@ CdmResponseType CertificateProvisioning::HandleProvisioningResponse(
if (cert_type_ == kCertificateX509) {
*cert = device_cert_data;
*wrapped_key = private_key.key();
state_ = kDrmResponseReceived;
return CdmResponseType(NO_ERROR);
}
@@ -813,6 +1086,7 @@ CdmResponseType CertificateProvisioning::HandleProvisioningResponse(
return CdmResponseType(CERT_PROVISIONING_RESPONSE_ERROR_8);
}
state_ = kDrmResponseReceived;
return CdmResponseType(NO_ERROR);
}

View File

@@ -15,6 +15,7 @@
namespace wvcdm {
namespace {
// These keys come form the Widevine License Exchange Protocol.
const std::string kKeyCompanyName = "company_name";
const std::string kKeyModelName = "model_name";
const std::string kKeyModelYear = "model_year";
@@ -27,10 +28,14 @@ const std::string kKeyOemCryptoSecurityPatchLevel =
"oem_crypto_security_patch_level";
const std::string kKeyOemCryptoBuildInformation =
"oem_crypto_build_information";
// CDM uses "form_factor", though documentation may refer to this
// as "device_type".
const std::string kKeyFormFactor = "form_factor";
const std::string kKeyPlatformName = "platform_name";
// These client identification keys are used by the CDM for relaying
// important device information that cannot be overwritten by the app.
const std::array<std::string, 10> kReservedProperties = {
const std::array<std::string, 12> kReservedProperties = {
kKeyCompanyName,
kKeyModelName,
kKeyModelYear,
@@ -41,6 +46,8 @@ const std::array<std::string, 10> kReservedProperties = {
kKeyWvCdmVersion,
kKeyOemCryptoSecurityPatchLevel,
kKeyOemCryptoBuildInformation,
kKeyFormFactor,
kKeyPlatformName,
// TODO(b/148813171,b/142280599): include "origin" and "application_name"
// to this list once collection of this information has been moved
// to the core CDM.
@@ -213,6 +220,16 @@ CdmResponseType ClientIdentification::Prepare(
client_info->set_name(kKeyWvCdmVersion);
client_info->set_value(value);
}
if (Properties::GetPlatform(&value)) {
client_info = client_id->add_client_info();
client_info->set_name(kKeyPlatformName);
client_info->set_value(value);
}
if (Properties::GetFormFactor(&value)) {
client_info = client_id->add_client_info();
client_info->set_name(kKeyFormFactor);
client_info->set_value(value);
}
client_info = client_id->add_client_info();
client_info->set_name(kKeyOemCryptoSecurityPatchLevel);
client_info->set_value(

View File

@@ -356,6 +356,42 @@ CdmResponseType CryptoSession::GetProvisioningMethod(
return CdmResponseType(NO_ERROR);
}
CdmResponseType CryptoSession::LoadLicenseData(const std::string& data) {
RETURN_IF_UNINITIALIZED(CRYPTO_SESSION_NOT_INITIALIZED);
auto sts = WithOecSessionLock("LoadLicenseData", [&] {
return OEMCrypto_LoadLicenseData(
oec_session_id_, reinterpret_cast<const uint8_t*>(data.data()),
data.size());
});
// level3_adapter may return this, and in case partners implement it, we
// ignore not-implemented errors.
if (sts == OEMCrypto_ERROR_NOT_IMPLEMENTED) sts = OEMCrypto_SUCCESS;
return MapOEMCryptoResult(sts, LOAD_KEY_ERROR, "LoadLicenseData");
}
CdmResponseType CryptoSession::SaveLicenseData(std::string* data) {
RETURN_IF_UNINITIALIZED(CRYPTO_SESSION_NOT_INITIALIZED);
size_t data_size = data->size();
auto sts = WithOecSessionLock("SaveLicenseData - attempt 1", [&] {
return OEMCrypto_SaveLicenseData(
oec_session_id_, reinterpret_cast<uint8_t*>(data->data()), &data_size);
});
if (sts == OEMCrypto_ERROR_SHORT_BUFFER) {
data->resize(data_size);
sts = WithOecSessionLock("SaveLicenseData - attempt 2", [&] {
return OEMCrypto_SaveLicenseData(
oec_session_id_, reinterpret_cast<uint8_t*>(data->data()),
&data_size);
});
}
data->resize(data_size);
// level3_adapter may return this, and in case partners implement it, we
// ignore not-implemented errors.
if (sts == OEMCrypto_ERROR_NOT_IMPLEMENTED) sts = OEMCrypto_SUCCESS;
return MapOEMCryptoResult(sts, STORE_LICENSE_ERROR_1, "SaveLicenseData");
}
void CryptoSession::Init() {
LOGV("Initializing crypto session");
bool initialized = false;
@@ -2268,12 +2304,17 @@ bool CryptoSession::IsAntiRollbackHwPresent() {
CdmResponseType CryptoSession::GenerateNonce(uint32_t* nonce) {
RETURN_IF_NULL(nonce, PARAMETER_NULL);
OEMCryptoResult result;
WithOecWriteLock("GenerateNonce", [&] {
result = OEMCrypto_GenerateNonce(oec_session_id_, nonce);
// Some OEMCrypto implementation might modify the provided
// |nonce| value on failure (setting zero).
// Using an intermediate |temp_nonce| to protect against this.
uint32_t temp_nonce = 0;
const OEMCryptoResult result = WithOecWriteLock("GenerateNonce", [&] {
return OEMCrypto_GenerateNonce(oec_session_id_, &temp_nonce);
});
metrics_->oemcrypto_generate_nonce_.Increment(result);
if (result == OEMCrypto_SUCCESS) {
*nonce = temp_nonce;
}
return MapOEMCryptoResult(result, NONCE_GENERATION_ERROR, "GenerateNonce");
}
@@ -2406,7 +2447,8 @@ CdmResponseType CryptoSession::LoadProvisioningCast(
CdmResponseType CryptoSession::GetHdcpCapabilities(HdcpCapability* current,
HdcpCapability* max) {
LOGV("Getting HDCP capabilities: id = %u", oec_session_id_);
LOGV("Getting HDCP capabilities: security_level = %s",
RequestedSecurityLevelToString(requested_security_level_));
RETURN_IF_NOT_OPEN(CRYPTO_SESSION_NOT_OPEN);
return GetHdcpCapabilities(requested_security_level_, current, max);
}
@@ -2414,8 +2456,8 @@ CdmResponseType CryptoSession::GetHdcpCapabilities(HdcpCapability* current,
CdmResponseType CryptoSession::GetHdcpCapabilities(
RequestedSecurityLevel security_level, HdcpCapability* current,
HdcpCapability* max) {
LOGV("Getting HDCP capabilities: id = %u, security_level = %s",
oec_session_id_, RequestedSecurityLevelToString(security_level));
LOGV("Getting HDCP capabilities: security_level = %s",
RequestedSecurityLevelToString(security_level));
RETURN_IF_UNINITIALIZED(CRYPTO_SESSION_NOT_INITIALIZED);
RETURN_IF_NULL(current, PARAMETER_NULL);
RETURN_IF_NULL(max, PARAMETER_NULL);
@@ -2438,7 +2480,8 @@ CdmResponseType CryptoSession::GetHdcpCapabilities(
bool CryptoSession::GetSupportedCertificateTypes(
SupportedCertificateTypes* support) {
LOGV("Getting supported certificate types: id = %u", oec_session_id_);
LOGV("Getting supported certificate types: security_level = %s",
RequestedSecurityLevelToString(requested_security_level_));
RETURN_IF_UNINITIALIZED(false);
RETURN_IF_NULL(support, false);
const uint32_t oec_support =
@@ -2456,8 +2499,8 @@ bool CryptoSession::GetSupportedCertificateTypes(
CdmResponseType CryptoSession::GetNumberOfOpenSessions(
RequestedSecurityLevel security_level, size_t* count) {
LOGV("Getting number of open sessions: id = %u, security_level = %s",
oec_session_id_, RequestedSecurityLevelToString(security_level));
LOGV("Getting number of open sessions: security_level = %s",
RequestedSecurityLevelToString(security_level));
RETURN_IF_UNINITIALIZED(CRYPTO_SESSION_NOT_INITIALIZED);
RETURN_IF_NULL(count, PARAMETER_NULL);
@@ -2480,8 +2523,8 @@ CdmResponseType CryptoSession::GetNumberOfOpenSessions(
CdmResponseType CryptoSession::GetMaxNumberOfSessions(
RequestedSecurityLevel security_level, size_t* max) {
LOGV("Getting max number of sessions: id = %u, security_level = %s",
oec_session_id_, RequestedSecurityLevelToString(security_level));
LOGV("Getting max number of sessions: security_level = %s",
RequestedSecurityLevelToString(security_level));
RETURN_IF_UNINITIALIZED(CRYPTO_SESSION_NOT_INITIALIZED);
RETURN_IF_NULL(max, PARAMETER_NULL);
@@ -3236,7 +3279,8 @@ CdmResponseType CryptoSession::MoveUsageEntry(UsageEntryIndex new_entry_index) {
bool CryptoSession::GetAnalogOutputCapabilities(bool* can_support_output,
bool* can_disable_output,
bool* can_support_cgms_a) {
LOGV("Getting analog output capabilities: id = %u", oec_session_id_);
LOGV("Getting analog output capabilities: security_level = %s",
RequestedSecurityLevelToString(requested_security_level_));
RETURN_IF_UNINITIALIZED(false);
const uint32_t flags = WithOecReadLock("GetAnalogOutputCapabilities", [&] {
return OEMCrypto_GetAnalogOutputFlags(requested_security_level_);

View File

@@ -709,17 +709,26 @@ bool DeviceFiles::RemoveCertificate() {
RETURN_FALSE_IF_UNINITIALIZED()
std::string certificate_file_name;
if (GetCertificateFileName(kCertificateLegacy, &certificate_file_name))
RemoveFile(certificate_file_name);
if (GetCertificateFileName(kCertificateDefault, &certificate_file_name))
return RemoveFile(certificate_file_name);
return true;
// Return true so long as at least one certificate was removed.
// This is to compliment the behavior of HasCertificate() which
// returns true if at least one certificate exists.
bool result = false;
if (GetCertificateFileName(kCertificateLegacy, &certificate_file_name)) {
LOGI("Removing legacy DRM cert");
result |= RemoveFile(certificate_file_name);
}
if (GetCertificateFileName(kCertificateDefault, &certificate_file_name)) {
LOGI("Removing DRM cert");
result |= RemoveFile(certificate_file_name);
}
return result;
}
bool DeviceFiles::RemoveOemCertificate() {
RETURN_FALSE_IF_UNINITIALIZED()
std::string certificate_file_name;
if (GetOemCertificateFileName(&certificate_file_name)) {
LOGI("Removing OEM certificate");
return RemoveFile(certificate_file_name);
}
return true;
@@ -881,6 +890,9 @@ bool DeviceFiles::StoreLicense(const CdmLicenseData& license_data,
}
license->set_usage_entry(license_data.usage_entry);
license->set_usage_entry_index(license_data.usage_entry_index);
if (!license_data.exported_license_data.empty()) {
license->set_exported_license_data(license_data.exported_license_data);
}
if (!license_data.drm_certificate.empty()) {
DeviceCertificate* device_certificate = license->mutable_drm_certificate();
if (!SetDeviceCertificate(license_data.drm_certificate,
@@ -974,6 +986,8 @@ bool DeviceFiles::RetrieveLicense(const std::string& key_set_id,
license_data->usage_entry_index =
static_cast<uint32_t>(license.usage_entry_index());
license_data->exported_license_data = license.exported_license_data();
if (!license.has_drm_certificate()) {
license_data->drm_certificate.clear();
license_data->wrapped_private_key.Clear();
@@ -2026,8 +2040,6 @@ DeviceFiles::ResponseType DeviceFiles::StoreFileRaw(
DeviceFiles::ResponseType DeviceFiles::RetrieveHashedFile(
const std::string& name,
video_widevine_client::sdk::File* deserialized_file) {
std::string serialized_file;
if (deserialized_file == nullptr) {
LOGE("File handle parameter |deserialized_file| not provided");
return kParameterNull;

View File

@@ -81,6 +81,12 @@ message License {
optional bytes usage_entry = 12;
optional int64 usage_entry_index = 13;
optional DeviceCertificate drm_certificate = 14;
// OEMCrypto-specific data for the license that will need to be loaded later
// to be able to use the license. For example, encryption keys that are
// associated with the license.
// Currently only used for the L3.
optional bytes exported_license_data = 15;
}
message UsageInfo {

View File

@@ -243,3 +243,13 @@ WEAK OEMCryptoResult OEMCrypto_UseSecondaryKey(OEMCrypto_SESSION, bool) {
WEAK OEMCryptoResult OEMCrypto_MarkOfflineSession(OEMCrypto_SESSION) {
return OEMCrypto_ERROR_NOT_IMPLEMENTED;
}
WEAK OEMCryptoResult OEMCrypto_LoadLicenseData(OEMCrypto_SESSION,
const uint8_t*, size_t) {
return OEMCrypto_ERROR_NOT_IMPLEMENTED;
}
WEAK OEMCryptoResult OEMCrypto_SaveLicenseData(OEMCrypto_SESSION, uint8_t*,
size_t*) {
return OEMCrypto_ERROR_NOT_IMPLEMENTED;
}

View File

@@ -891,6 +891,12 @@ const char* CdmResponseEnumToString(CdmResponseEnum cdm_response_enum) {
return "SESSION_NOT_FOUND_24";
case GET_BCC_SIGNATURE_TYPE_ERROR:
return "GET_BCC_SIGNATURE_TYPE_ERROR";
case PROVISIONING_UNEXPECTED_RESPONSE_ERROR:
return "PROVISIONING_UNEXPECTED_RESPONSE_ERROR";
case PROVISIONING_4_STALE_RESPONSE:
return "PROVISIONING_4_STALE_RESPONSE";
case PROVISIONING_4_FAILED_TO_VERIFY_CERT_KEY:
return "PROVISIONING_4_FAILED_TO_VERIFY_CERT_KEY";
}
return UnknownValueRep(cdm_response_enum);
}