// 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 "client_identification.h" #include #include "crypto_session.h" #include "license_protocol.pb.h" #include "log.h" #include "properties.h" #include "string_conversions.h" #include "wv_cdm_constants.h" namespace wvcdm { namespace { const std::string kKeyCompanyName = "company_name"; const std::string kKeyModelName = "model_name"; const std::string kKeyModelYear = "model_year"; const std::string kKeyArchitectureName = "architecture_name"; const std::string kKeyDeviceName = "device_name"; const std::string kKeyProductName = "product_name"; const std::string kKeyBuildInfo = "build_info"; const std::string kKeyWvCdmVersion = "widevine_cdm_version"; const std::string kKeyOemCryptoSecurityPatchLevel = "oem_crypto_security_patch_level"; const std::string kKeyOemCryptoBuildInformation = "oem_crypto_build_information"; // These client identification keys are used by the CDM for relaying // important device information that cannot be overwritten by the app. const std::array kReservedProperties = { kKeyCompanyName, kKeyModelName, kKeyModelYear, kKeyArchitectureName, kKeyDeviceName, kKeyProductName, kKeyBuildInfo, kKeyWvCdmVersion, kKeyOemCryptoSecurityPatchLevel, kKeyOemCryptoBuildInformation, // 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. }; // Checks if the client-provided |prop_name| is reserved for CDM device // identification with the license server. Property keys which are // reserved should be dropped from the request. bool IsPropertyKeyReserved(const std::string& prop_name) { for (const std::string& reserved_prop_name : kReservedProperties) { if (prop_name == reserved_prop_name) return true; } return false; } } // namespace // Protobuf generated classes. using ClientCapabilities = video_widevine::ClientIdentification::ClientCapabilities; using AnalogOutputCapabilities = ClientCapabilities::AnalogOutputCapabilities; using video_widevine::ClientIdentification_NameValue; CdmResponseType ClientIdentification::InitForProvisioningRequest( const std::string& client_token, CryptoSession* crypto_session) { if (crypto_session == nullptr) { LOGE("Crypto session not provided"); return CdmResponseType(PARAMETER_NULL); } is_license_request_ = false; client_token_ = client_token; crypto_session_ = crypto_session; return CdmResponseType(NO_ERROR); } CdmResponseType ClientIdentification::InitForLicenseRequest( const std::string& client_token, CryptoSession* crypto_session) { if (crypto_session == nullptr) { LOGE("Crypto session not provided"); return CdmResponseType(PARAMETER_NULL); } if (client_token.empty()) { LOGE("Client token is empty"); return CdmResponseType(PARAMETER_NULL); } is_license_request_ = true; client_token_ = client_token; crypto_session_ = crypto_session; return CdmResponseType(NO_ERROR); } CdmResponseType ClientIdentification::InitForOtaKeyboxProvisioning( CryptoSession* crypto_session) { if (crypto_session == nullptr) { LOGE("Crypto session not provided"); return CdmResponseType(PARAMETER_NULL); } is_okp_request_ = true; crypto_session_ = crypto_session; return CdmResponseType(NO_ERROR); } /* * Return the ClientIdentification message token type for provisioning request. * NOTE: a DRM Cert should never be presented to the provisioning server unless * DRM re-provisioning is being used. */ CdmResponseType ClientIdentification::Prepare( const CdmAppParameterMap& app_parameters, const std::string& provider_client_token, video_widevine::ClientIdentification* client_id) { if (is_license_request_) { client_id->set_type( video_widevine::ClientIdentification::DRM_DEVICE_CERTIFICATE); client_id->set_token(client_token_); } else if (!client_token_.empty()) { // A token has been provided via InitForProvisioningRequest. This can only // happen in provisioning 4 (second stage) where an OEM cert is provided. client_id->set_type( video_widevine::ClientIdentification::OEM_DEVICE_CERTIFICATE); client_id->set_token(client_token_); } else if (!is_okp_request_) { // An OTA Keybox Provisioning request does not have a client id. // Otherwise this is a regular provisioning request. video_widevine::ClientIdentification::TokenType token_type; if (!GetProvisioningTokenType(&token_type)) { LOGE("Failed to get provisioning token type"); return CdmResponseType(CLIENT_IDENTIFICATION_TOKEN_ERROR_1); } client_id->set_type(token_type); std::string token; std::string additional_token; CdmResponseType status = crypto_session_->GetProvisioningToken(&token, &additional_token); if (status != NO_ERROR) { LOGE("Failed to get provisioning token: status = %d", status.ToInt()); return status; } client_id->set_token(token); if (!additional_token.empty()) { client_id->mutable_device_credentials()->set_token(additional_token); } } ClientIdentification_NameValue* client_info; // Include app parameters for license and provisioning requests if (!is_okp_request_) { CdmAppParameterMap::const_iterator iter; for (iter = app_parameters.begin(); iter != app_parameters.end(); ++iter) { if (IsPropertyKeyReserved(iter->first)) { LOGD("Discarding client property: name = \"%s\", value = \"%s\"", iter->first.c_str(), iter->second.c_str()); continue; } client_info = client_id->add_client_info(); client_info->set_name(iter->first); client_info->set_value(iter->second); } } std::string value; if (Properties::GetCompanyName(&value)) { client_info = client_id->add_client_info(); client_info->set_name(kKeyCompanyName); client_info->set_value(value); } if (Properties::GetModelName(&value)) { client_info = client_id->add_client_info(); client_info->set_name(kKeyModelName); client_info->set_value(value); } if (Properties::GetModelYear(&value)) { client_info = client_id->add_client_info(); client_info->set_name(kKeyModelYear); client_info->set_value(value); } if (Properties::GetArchitectureName(&value)) { client_info = client_id->add_client_info(); client_info->set_name(kKeyArchitectureName); client_info->set_value(value); } if (Properties::GetDeviceName(&value)) { client_info = client_id->add_client_info(); client_info->set_name(kKeyDeviceName); client_info->set_value(value); } if (Properties::GetProductName(&value)) { client_info = client_id->add_client_info(); client_info->set_name(kKeyProductName); client_info->set_value(value); } if (Properties::GetBuildInfo(&value)) { client_info = client_id->add_client_info(); client_info->set_name(kKeyBuildInfo); client_info->set_value(value); } if (Properties::GetWVCdmVersion(&value)) { client_info = client_id->add_client_info(); client_info->set_name(kKeyWvCdmVersion); client_info->set_value(value); } client_info = client_id->add_client_info(); client_info->set_name(kKeyOemCryptoSecurityPatchLevel); client_info->set_value( std::to_string((uint32_t)crypto_session_->GetSecurityPatchLevel())); std::string oec_build_info; if (crypto_session_->GetBuildInformation(&oec_build_info)) { client_info = client_id->add_client_info(); client_info->set_name(kKeyOemCryptoBuildInformation); client_info->set_value(oec_build_info); } if (!provider_client_token.empty()) { client_id->set_provider_client_token(provider_client_token); } if (is_okp_request_) { // Capabilities is not important for OTA keybox provisionining. return CdmResponseType(NO_ERROR); } ClientCapabilities* client_capabilities = client_id->mutable_client_capabilities(); client_capabilities->set_client_token(true); if (is_license_request_) { bool supports_usage_table; if (crypto_session_->HasUsageTableSupport(&supports_usage_table)) { client_capabilities->set_session_token(supports_usage_table); } client_capabilities->set_anti_rollback_usage_table( crypto_session_->IsAntiRollbackHwPresent()); } uint32_t api_version = 0; if (crypto_session_->GetApiVersion(&api_version)) { client_capabilities->set_oem_crypto_api_version(api_version); } if (is_license_request_) { CryptoSession::HdcpCapability current_version, max_version; if (crypto_session_->GetHdcpCapabilities(¤t_version, &max_version) == NO_ERROR) { switch (max_version) { case HDCP_NONE: client_capabilities->set_max_hdcp_version( ClientCapabilities::HDCP_NONE); break; case HDCP_V1: client_capabilities->set_max_hdcp_version( ClientCapabilities::HDCP_V1); break; case HDCP_V2: client_capabilities->set_max_hdcp_version( ClientCapabilities::HDCP_V2); break; case HDCP_V2_1: client_capabilities->set_max_hdcp_version( ClientCapabilities::HDCP_V2_1); break; case HDCP_V2_2: client_capabilities->set_max_hdcp_version( ClientCapabilities::HDCP_V2_2); break; case HDCP_V2_3: client_capabilities->set_max_hdcp_version( ClientCapabilities::HDCP_V2_3); break; case HDCP_NO_DIGITAL_OUTPUT: client_capabilities->set_max_hdcp_version( ClientCapabilities::HDCP_NO_DIGITAL_OUTPUT); break; default: LOGW("Unexpected HDCP max capability version: max_version = %d", static_cast(max_version)); } } } CryptoSession::SupportedCertificateTypes supported_certs; if (crypto_session_->GetSupportedCertificateTypes(&supported_certs)) { if (supported_certs.rsa_2048_bit) { client_capabilities->add_supported_certificate_key_type( ClientCapabilities::RSA_2048); } if (supported_certs.rsa_3072_bit) { client_capabilities->add_supported_certificate_key_type( ClientCapabilities::RSA_3072); } if (supported_certs.ecc_secp256r1) { client_capabilities->add_supported_certificate_key_type( ClientCapabilities::ECC_SECP256R1); } if (supported_certs.ecc_secp384r1) { client_capabilities->add_supported_certificate_key_type( ClientCapabilities::ECC_SECP384R1); } if (supported_certs.ecc_secp521r1) { client_capabilities->add_supported_certificate_key_type( ClientCapabilities::ECC_SECP521R1); } } if (is_license_request_) { client_capabilities->set_can_update_srm(false); uint16_t srm_version; if (crypto_session_->GetSrmVersion(&srm_version) == NO_ERROR) client_capabilities->set_srm_version(srm_version); } bool can_support_output; bool can_disable_output; bool can_support_cgms_a; if (crypto_session_->GetAnalogOutputCapabilities( &can_support_output, &can_disable_output, &can_support_cgms_a)) { AnalogOutputCapabilities capabilities = ClientCapabilities::ANALOG_OUTPUT_NONE; if (can_support_output) { if (can_support_cgms_a) { capabilities = ClientCapabilities::ANALOG_OUTPUT_SUPPORTS_CGMS_A; } else { capabilities = ClientCapabilities::ANALOG_OUTPUT_SUPPORTED; } } client_capabilities->set_analog_output_capabilities(capabilities); client_capabilities->set_can_disable_analog_output(can_disable_output); } else { client_capabilities->set_analog_output_capabilities( ClientCapabilities::ANALOG_OUTPUT_UNKNOWN); } if (api_version >= OEM_CRYPTO_API_VERSION_SUPPORTS_RESOURCE_RATING_TIER) { uint32_t tier; if (crypto_session_->GetResourceRatingTier(&tier)) { client_capabilities->set_resource_rating_tier(tier); } } if (is_license_request_) { CdmWatermarkingSupport support; if (!crypto_session_->GetWatermarkingSupport(&support)) { // Assume not supported. support = kWatermarkingNotSupported; } switch (support) { case kWatermarkingNotSupported: client_capabilities->set_watermarking_support( ClientCapabilities::WATERMARKING_NOT_SUPPORTED); break; case kWatermarkingConfigurable: client_capabilities->set_watermarking_support( ClientCapabilities::WATERMARKING_CONFIGURABLE); break; case kWatermarkingAlwaysOn: client_capabilities->set_watermarking_support( ClientCapabilities::WATERMARKING_ALWAYS_ON); break; } } if (api_version >= OEM_CRYPTO_API_VERSION_SUPPORTS_INITIAL_RENEWAL_DELAY_BASE) { client_capabilities->set_initial_renewal_delay_base(true); } return CdmResponseType(NO_ERROR); } bool ClientIdentification::GetProvisioningTokenType( video_widevine::ClientIdentification::TokenType* token_type) { CdmClientTokenType token = crypto_session_->GetPreProvisionTokenType(); switch (token) { case kClientTokenKeybox: *token_type = video_widevine::ClientIdentification::KEYBOX; return true; case kClientTokenOemCert: *token_type = video_widevine::ClientIdentification::OEM_DEVICE_CERTIFICATE; return true; case kClientTokenBootCertChain: { OEMCrypto_BCCType bcc_type; const CdmResponseType result = crypto_session_->GetProvisioning40TokenType(&bcc_type); if (result == NOT_IMPLEMENTED_ERROR) { // Default to CBOR BCC for OEMCrypto that doesn't support GetBCCType(). *token_type = video_widevine::ClientIdentification::BOOT_CERTIFICATE_CHAIN; return true; } if (result != NO_ERROR) return false; if (bcc_type == OEMCrypto_CBOR) { *token_type = video_widevine::ClientIdentification::BOOT_CERTIFICATE_CHAIN; } else if (bcc_type == OEMCrypto_X509) { *token_type = video_widevine::ClientIdentification::BOOT_CERTIFICATE_CHAIN_X509; } else { // shouldn't happen LOGE("Unexpected BCC type: %d", static_cast(bcc_type)); return false; } return true; } case kClientTokenDrmCertificateReprovisioning: *token_type = video_widevine::ClientIdentification::DRM_DEVICE_CERTIFICATE; return true; case kClientTokenDrmCert: default: // shouldn't happen LOGE("Unexpected provisioning type: %d", static_cast(token)); return false; } } } // namespace wvcdm