Files
android/libwvdrmengine/cdm/core/src/certificate_provisioning.cpp
Alex Dale 5eed0446da Clean up CdmEngine logs.
[ Merge of http://go/wvgerrit/121568 ]

The CdmEngine logs had both too much and too little information.

Since our logging has been enabled to print function names natively,
many of the log information has become superfluous.  Needless
information has been removed, and many of the important INFO logs have
been reduced to only the information not present in the function name.

Some of the INFO and ERROR logs were missing identifiers to match
failures with the same session request should the failures take more
than a few milliseconds to occur.  CDM session IDs and key set IDs
have been included in all the logs that _appeared_ to have a slow
operation between the top of the method and log.

To help make enum values more readable, several enums-to-string
functions have been implemented.  These converters are intended for
INFO logging and as such, do not log any addition information should
the enum be out of range.

To help make empty and null identifiers more readable in the logs,
empty strings will be logged as <empty> and null strings will be
logged as <null>.

While working through the "cdm_engine.cpp" file, a few minor changes
have been made:
- Adjust if statements to match with Google C++ style guidelines
  - Skipped anything that was not obvious
- Added a const qualifier to variables where appropriate
- Moved some null checks to the top of the method
  - Only where sequence is non-critical to normal operation
- Removed unnecessary string to vector to string conversions
- Reject empty |force_session_id|
  - Already enforced on CE CDM code and not uesd on Android

Bug: 183576879
Test: CE CDM unittests
Change-Id: Id165373055f7ce6097c93c48f84af74bd353c8cb
2021-04-21 21:05:10 -07:00

550 lines
20 KiB
C++

// 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 "client_identification.h"
#include "crypto_wrapped_key.h"
#include "device_files.h"
#include "file_store.h"
#include "license_protocol.pb.h"
#include "log.h"
#include "properties.h"
#include "service_certificate.h"
#include "string_conversions.h"
#include "wv_cdm_constants.h"
namespace wvcdm {
namespace {
const std::string kEmptyString;
// URL for Google Provisioning Server.
// The provisioning server supplies the certificate that is needed
// to communicate with the License Server.
const std::string kProvisioningServerUrl =
"https://www.googleapis.com/"
"certificateprovisioning/v1/devicecertificates/create"
"?key=AIzaSyB-5OLKTx2iU5mko18DfdwK5611JIjbUhE";
// NOTE: Provider ID = widevine.com
const std::string kCpProductionServiceCertificate = wvcdm::a2bs_hex(
"0ab9020803121051434fe2a44c763bcc2c826a2d6ef9a718f7d793d005228e02"
"3082010a02820101009e27088659dbd9126bc6ed594caf652b0eaab82abb9862"
"ada1ee6d2cb5247e94b28973fef5a3e11b57d0b0872c930f351b5694354a8c77"
"ed4ee69834d2630372b5331c5710f38bdbb1ec3024cfadb2a8ac94d977d391b7"
"d87c20c5c046e9801a9bffaf49a36a9ee6c5163eff5cdb63bfc750cf4a218618"
"984e485e23a10f08587ec5d990e9ab0de71460dfc334925f3fb9b55761c61e28"
"8398c387a0925b6e4dcaa1b36228d9feff7e789ba6e5ef6cf3d97e6ae05525db"
"38f826e829e9b8764c9e2c44530efe6943df4e048c3c5900ca2042c5235dc80d"
"443789e734bf8e59a55804030061ed48e7d139b521fbf35524b3000b3e2f6de0"
"001f5eeb99e9ec635f02030100013a0c7769646576696e652e636f6d12800332"
"2c2f3fedc47f8b7ba88a135a355466e378ed56a6fc29ce21f0cafc7fb253b073"
"c55bed253d8650735417aad02afaefbe8d5687902b56a164490d83d590947515"
"68860e7200994d322b5de07f82ef98204348a6c2c9619092340eb87df26f63bf"
"56c191dc069b80119eb3060d771afaaeb2d30b9da399ef8a41d16f45fd121e09"
"a0c5144da8f8eb46652c727225537ad65e2a6a55799909bbfb5f45b5775a1d1e"
"ac4e06116c57adfa9ce0672f19b70b876f88e8b9fbc4f96ccc500c676cfb173c"
"b6f52601573e2e45af1d9d2a17ef1487348c05cfc6d638ec2cae3fadb655e943"
"1330a75d2ceeaa54803e371425111e20248b334a3a50c8eca683c448b8ac402c"
"76e6f76e2751fbefb669f05703cec8c64cf7a62908d5fb870375eb0cc96c508e"
"26e0c050f3fd3ebe68cef9903ef6405b25fc6e31f93559fcff05657662b3653a"
"8598ed5751b38694419242a875d9e00d5a5832933024b934859ec8be78adccbb"
"1ec7127ae9afeef9c5cd2e15bd3048e8ce652f7d8c5d595a0323238c598a28");
/*
* Provisioning response is a base64-encoded protobuf, optionally within a
* JSON wrapper. If the JSON wrapper is present, extract the embedded response
* message. Then perform the base64 decode and return the result.
*
* If an error occurs during the parse or the decode, return an empty string.
*/
bool ExtractAndDecodeSignedMessage(const std::string& provisioning_response,
std::string* result) {
const std::string json_start_substr("\"signedResponse\": \"");
const std::string json_end_substr("\"");
std::string message_string;
if (result == nullptr) {
LOGE("Output parameter |result| is not provided");
return false;
}
size_t start = provisioning_response.find(json_start_substr);
if (start == provisioning_response.npos) {
// Message is not properly wrapped - reject it.
LOGE("Cannot locate start substring");
result->clear();
return false;
}
// Appears to be JSON-wrapped protobuf - find end of protobuf portion.
const size_t end = provisioning_response.find(
json_end_substr, start + json_start_substr.length());
if (end == provisioning_response.npos) {
LOGE("Cannot locate end substring");
result->clear();
return false;
}
size_t b64_string_size = end - start - json_start_substr.length();
message_string.assign(provisioning_response,
start + json_start_substr.length(), b64_string_size);
if (message_string.empty()) {
LOGE("CDM provisioning response is empty");
result->clear();
return false;
}
// Decode the base64-encoded message.
const std::vector<uint8_t> decoded_message =
wvcdm::Base64SafeDecode(message_string);
result->assign(decoded_message.begin(), decoded_message.end());
return true;
}
} // namespace
// Protobuf generated classes.
using video_widevine::ClientIdentification_ClientCapabilities;
using video_widevine::ClientIdentification_NameValue;
using video_widevine::DrmCertificate;
using video_widevine::EncryptedClientIdentification;
using video_widevine::ProvisioningOptions;
using video_widevine::ProvisioningRequest;
using video_widevine::ProvisioningResponse;
using video_widevine::SignedDrmCertificate;
using video_widevine::SignedProvisioningMessage;
using video_widevine::
SignedProvisioningMessage_ProvisioningProtocolVersion_VERSION_1_1;
CdmResponseType CertificateProvisioning::Init(
const std::string& service_certificate) {
std::string certificate = service_certificate.empty()
? kCpProductionServiceCertificate
: service_certificate;
return service_certificate_->Init(certificate);
}
/*
* Fill in the appropriate SPOID (Stable Per-Origin IDentifier) option.
* One of spoid, provider_id or stable_id will be passed to the provisioning
* server for determining a unique per origin ID for the device.
* It is also valid (though deprecated) to leave the settings unset.
*/
CdmResponseType CertificateProvisioning::SetSpoidParameter(
const std::string& origin, const std::string& spoid,
ProvisioningRequest* request) {
if (!request) {
LOGE("Output parameter |request| is not provided");
return PARAMETER_NULL;
}
if (!spoid.empty()) {
// Use the SPOID that has been pre-provided
request->set_spoid(spoid);
} else if (Properties::UseProviderIdInProvisioningRequest()) {
if (!service_certificate_->provider_id().empty()) {
request->set_provider_id(service_certificate_->provider_id());
} else {
LOGE(
"Failed to set provider ID: "
"Service certificate provider ID is empty");
return SERVICE_CERTIFICATE_PROVIDER_ID_EMPTY;
}
} else if (origin != EMPTY_ORIGIN) {
// Legacy behavior - Concatenate Unique ID with Origin
std::string device_unique_id;
CdmResponseType status =
crypto_session_->GetInternalDeviceUniqueId(&device_unique_id);
if (status != NO_ERROR) {
LOGE("Failed to get device unique ID: status = %d",
static_cast<int>(status));
return status;
}
request->set_stable_id(device_unique_id + origin);
} // No else clause, by design. It is valid to do nothing.
return NO_ERROR;
}
/*
* Return the provisioning protocol version - dictated by OEMCrypto
* support for OEM certificates.
*/
SignedProvisioningMessage::ProvisioningType
CertificateProvisioning::GetProvisioningType() {
if (crypto_session_->GetPreProvisionTokenType() == kClientTokenOemCert)
return SignedProvisioningMessage::PROVISIONING_30;
else
return SignedProvisioningMessage::PROVISIONING_20;
}
/*
* Compose a device provisioning request and output *request in a
* JSON-compatible format (web-safe base64).
* Also return *default_url of the provisioning server.
*
* Returns NO_ERROR for success and CERT_PROVISIONING_REQUEST_ERROR_? if fails.
*/
CdmResponseType CertificateProvisioning::GetProvisioningRequest(
SecurityLevel requested_security_level, CdmCertificateType cert_type,
const std::string& cert_authority, const std::string& origin,
const std::string& spoid, CdmProvisioningRequest* request,
std::string* default_url) {
return CloseSessionOnError(GetProvisioningRequestInternal(
requested_security_level, cert_type, cert_authority, origin, spoid,
request, default_url));
}
CdmResponseType CertificateProvisioning::GetProvisioningRequestInternal(
SecurityLevel requested_security_level, CdmCertificateType cert_type,
const std::string& cert_authority, const std::string& origin,
const std::string& spoid, CdmProvisioningRequest* request,
std::string* default_url) {
if (!request || !default_url) {
LOGE("Output parameter |%s| is not provided",
request ? "default_url" : "request");
return CERT_PROVISIONING_REQUEST_ERROR_1;
}
default_url->assign(kProvisioningServerUrl);
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));
return status;
}
// Prepare device provisioning request.
ProvisioningRequest provisioning_request;
wvcdm::ClientIdentification id;
status = id.Init(crypto_session_.get());
if (status != NO_ERROR) return status;
video_widevine::ClientIdentification client_id;
CdmAppParameterMap app_parameter;
status = id.Prepare(app_parameter, kEmptyString, &client_id);
if (status != NO_ERROR) return status;
if (!service_certificate_->has_certificate()) {
LOGE("Service certificate not staged");
return CERT_PROVISIONING_EMPTY_SERVICE_CERTIFICATE;
}
// Encrypt client identification
EncryptedClientIdentification* encrypted_client_id =
provisioning_request.mutable_encrypted_client_id();
status = service_certificate_->EncryptClientId(
crypto_session_.get(), &client_id, encrypted_client_id);
uint32_t nonce;
status = crypto_session_->GenerateNonce(&nonce);
if (status != NO_ERROR) {
LOGE("Failed to generate a nonce: status = %d", static_cast<int>(status));
return status == NONCE_GENERATION_ERROR
? CERT_PROVISIONING_NONCE_GENERATION_ERROR
: status;
}
// The provisioning server does not convert the nonce to uint32_t, it just
// passes the binary data to the response message.
std::string the_nonce(reinterpret_cast<char*>(&nonce), sizeof(nonce));
provisioning_request.set_nonce(the_nonce);
ProvisioningOptions* options = provisioning_request.mutable_options();
switch (cert_type) {
case kCertificateWidevine:
options->set_certificate_type(
video_widevine::ProvisioningOptions_CertificateType_WIDEVINE_DRM);
break;
case kCertificateX509:
options->set_certificate_type(
video_widevine::ProvisioningOptions_CertificateType_X509);
break;
default:
LOGE("Unknown certificate type: %d", static_cast<int>(cert_type));
return CERT_PROVISIONING_INVALID_CERT_TYPE;
}
cert_type_ = cert_type;
options->set_certificate_authority(cert_authority);
status = SetSpoidParameter(origin, spoid, &provisioning_request);
if (status != NO_ERROR) return status;
std::string serialized_message;
provisioning_request.SerializeToString(&serialized_message);
// Derives signing and encryption keys and constructs signature.
std::string core_message;
std::string request_signature;
status = crypto_session_->PrepareAndSignProvisioningRequest(
serialized_message, &core_message, &request_signature);
if (status != NO_ERROR) {
LOGE("Failed to prepare provisioning request: status = %d",
static_cast<int>(status));
return status;
}
if (request_signature.empty()) {
LOGE("Request signature is empty");
return CERT_PROVISIONING_REQUEST_ERROR_4;
}
SignedProvisioningMessage signed_provisioning_msg;
signed_provisioning_msg.set_message(serialized_message);
signed_provisioning_msg.set_signature(request_signature);
signed_provisioning_msg.set_provisioning_type(GetProvisioningType());
if (core_message.empty()) {
// OEMCrypto does not support core messages.
supports_core_messages_ = false;
} else {
signed_provisioning_msg.set_oemcrypto_core_message(core_message);
}
signed_provisioning_msg.set_protocol_version(
SignedProvisioningMessage_ProvisioningProtocolVersion_VERSION_1_1);
std::string serialized_request;
signed_provisioning_msg.SerializeToString(&serialized_request);
if (!wvcdm::Properties::provisioning_messages_are_binary()) {
// Return request as web-safe base64 string
*request = Base64SafeEncodeNoPad(serialized_request);
} else {
*request = std::move(serialized_request);
}
return NO_ERROR;
}
/*
* The response message consists of a device certificate and the device RSA key.
* The device RSA key is stored in the T.E.E. The device certificate is stored
* in the device.
*
* Returns NO_ERROR for success and CERT_PROVISIONING_RESPONSE_ERROR_? if fails.
*/
CdmResponseType CertificateProvisioning::HandleProvisioningResponse(
FileSystem* file_system, const CdmProvisioningResponse& response_message,
std::string* cert, std::string* wrapped_key) {
if (response_message.empty()) {
LOGE("Provisioning response message is empty");
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.
const bool result =
ExtractAndDecodeSignedMessage(response_message, &response);
if (!result || response.empty()) {
LOGE("Provisioning 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(response)) {
LOGE("Failed to parse signed provisioining response");
return CERT_PROVISIONING_RESPONSE_ERROR_2;
}
bool error = false;
if (!signed_response.has_signature()) {
LOGE("Signed response does not have signature");
error = true;
}
if (!signed_response.has_message()) {
LOGE("Signed response does not have message");
error = true;
}
if (supports_core_messages() &&
(!signed_response.has_oemcrypto_core_message() ||
signed_response.oemcrypto_core_message().empty())) {
LOGE("Signed response does not have core message");
error = true;
} else if (!supports_core_messages() &&
(signed_response.has_oemcrypto_core_message() &&
!signed_response.oemcrypto_core_message().empty())) {
const std::string& core_message = signed_response.oemcrypto_core_message();
// This case should not occur. However, the CDM will let OEMCrypto
// fail.
LOGW(
"Received unexpected core message in provisioning request: "
"core_message_size = %zu",
core_message.size());
}
if (error) return CERT_PROVISIONING_RESPONSE_ERROR_3;
const std::string& signed_message = signed_response.message();
const std::string& signature = signed_response.signature();
const std::string core_message =
supports_core_messages() ? signed_response.oemcrypto_core_message()
: std::string();
ProvisioningResponse provisioning_response;
if (!provisioning_response.ParseFromString(signed_message)) {
LOGE("Failed to parse provisioning response");
return CERT_PROVISIONING_RESPONSE_ERROR_4;
}
if (provisioning_response.has_status()) {
if (provisioning_response.status() != ProvisioningResponse::NO_ERROR) {
LOGE("Provisioning Response status: %d", provisioning_response.status());
}
switch (provisioning_response.status()) {
case ProvisioningResponse::NO_ERROR:
break;
case ProvisioningResponse::REVOKED_DEVICE_CREDENTIALS:
case ProvisioningResponse::REVOKED_DEVICE_SERIES:
return DEVICE_REVOKED;
default:
return CERT_PROVISIONING_RESPONSE_ERROR_10;
}
}
CryptoWrappedKey private_key;
const CdmResponseType status = crypto_session_->LoadProvisioning(
signed_message, core_message, signature, &private_key.key());
if (status != NO_ERROR) {
LOGE("LoadProvisioning failed: status = %d", static_cast<int>(status));
return status;
}
const CdmSecurityLevel security_level = crypto_session_->GetSecurityLevel();
CloseSession();
// This is the entire certificate (SignedDrmCertificate).
const std::string& device_cert_data =
provisioning_response.device_certificate();
if (cert_type_ == kCertificateX509) {
*cert = device_cert_data;
*wrapped_key = private_key.key();
return NO_ERROR;
}
// Need to parse cert for key type.
SignedDrmCertificate signed_device_cert;
if (!signed_device_cert.ParseFromString(device_cert_data)) {
LOGE("Failed to parse signed DRM certificate");
return CERT_PROVISIONING_RESPONSE_ERROR_9;
}
DrmCertificate device_cert;
if (!device_cert.ParseFromString(signed_device_cert.drm_certificate())) {
LOGE("Failed to parse DRM certificate");
return CERT_PROVISIONING_RESPONSE_ERROR_9;
}
if (!device_cert.has_algorithm()) {
LOGW("DRM certificate does not specify algorithm type, assuming RSA");
private_key.set_type(CryptoWrappedKey::kRsa);
} else {
switch (device_cert.algorithm()) {
case DrmCertificate::RSA:
private_key.set_type(CryptoWrappedKey::kRsa);
break;
case DrmCertificate::ECC_SECP256R1:
case DrmCertificate::ECC_SECP384R1:
case DrmCertificate::ECC_SECP521R1:
private_key.set_type(CryptoWrappedKey::kEcc);
break;
default:
LOGE("Unknown DRM key type: algorithm = %d",
static_cast<int>(device_cert.algorithm()));
return CERT_PROVISIONING_RESPONSE_ERROR_9;
}
}
// The certificate will be stored to the device as the final step in
// the device provisioning process.
DeviceFiles handle(file_system);
if (!handle.Init(security_level)) {
LOGE("Failed to initialize DeviceFiles");
return CERT_PROVISIONING_RESPONSE_ERROR_7;
}
if (!handle.StoreCertificate(device_cert_data, private_key)) {
LOGE("Failed to store provisioning certificate");
return CERT_PROVISIONING_RESPONSE_ERROR_8;
}
return NO_ERROR;
}
// Static
bool CertificateProvisioning::ExtractAndDecodeSignedMessageForTesting(
const std::string& provisioning_response, std::string* result) {
return ExtractAndDecodeSignedMessage(provisioning_response, result);
}
bool CertificateProvisioning::ExtractDeviceInfo(
const std::string& device_certificate, std::string* serial_number,
uint32_t* system_id, int64_t* creation_time_seconds,
int64_t* expiration_time_seconds) {
LOGV("Extracting device info");
if (serial_number == nullptr && system_id == nullptr) {
LOGE("Output parameters |serial_number| and |system_id| not provided");
return false;
}
// Get serial number and system ID from certificate
SignedDrmCertificate signed_drm_certificate;
if (!signed_drm_certificate.ParseFromString(device_certificate) ||
!signed_drm_certificate.has_drm_certificate()) {
LOGE("Failed to parse signed DRM device certificate");
return false;
}
DrmCertificate drm_certificate;
if (!drm_certificate.ParseFromString(
signed_drm_certificate.drm_certificate()) ||
(drm_certificate.type() != video_widevine::DrmCertificate::DEVICE)) {
LOGE("Failed to parse DRM device certificate message");
return false;
}
if (serial_number != nullptr) {
*serial_number = drm_certificate.serial_number();
}
if (system_id != nullptr) {
*system_id = drm_certificate.system_id();
}
if (creation_time_seconds != nullptr) {
*creation_time_seconds = drm_certificate.has_creation_time_seconds()
? drm_certificate.creation_time_seconds()
: INVALID_TIME;
}
if (expiration_time_seconds != nullptr) {
*expiration_time_seconds = drm_certificate.has_expiration_time_seconds()
? drm_certificate.expiration_time_seconds()
: INVALID_TIME;
}
return true;
}
void CertificateProvisioning::CloseSession() {
if (crypto_session_->IsOpen()) crypto_session_->Close();
}
CdmResponseType CertificateProvisioning::CloseSessionOnError(
CdmResponseType status) {
if (status != NO_ERROR) CloseSession();
return status;
}
} // namespace wvcdm