Files
android/libwvdrmengine/cdm/core/src/certificate_provisioning.cpp
Alex Dale 247cf053e5 Enable the CDM to track the DRM private key type.
[ Merge of http://go/wvgerrit/110923 ]

The CDM is responsible for telling OEMCrypto the underlying DRM
private key type when loading it into a session.  To do this, the
CDM must determine and store the key type of a successfully loaded
provisioning response.  The type of key is available from the
DRM certificate proto that is provided in the reponse.

This change introduces a class to contain the wrapped key and
type together.  To store the type, the CDM device files have been
updated to include a key type with the DRM certificate and to
store from and load to the new class.

Unittests have been updated for using the new class where the
wrapped key was used before.

Test: Linux unit tests
Bug: 140813486
Change-Id: I09249afe9c291632fb651ecd00eac697d6939ec7
2021-02-01 13:37:57 -08:00

505 lines
18 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 {
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
namespace wvcdm {
// Protobuf generated classes.
using video_widevine::ClientIdentification_ClientCapabilities;
using video_widevine::ClientIdentification_NameValue;
using video_widevine::DrmDeviceCertificate;
using video_widevine::EncryptedClientIdentification;
using video_widevine::ProvisioningOptions;
using video_widevine::ProvisioningRequest;
using video_widevine::ProvisioningResponse;
using video_widevine::SignedDrmDeviceCertificate;
using video_widevine::SignedProvisioningMessage;
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::ProtocolVersion
CertificateProvisioning::GetProtocolVersion() {
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) {
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);
if (crypto_session_->IsOpen()) crypto_session_->Close();
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_protocol_version(GetProtocolVersion());
if (core_message.empty()) {
// OEMCrypto does not support core messages.
supports_core_messages_ = false;
} else {
signed_provisioning_msg.set_oemcrypto_core_message(core_message);
}
std::string serialized_request;
signed_provisioning_msg.SerializeToString(&serialized_request);
if (!wvcdm::Properties::provisioning_messages_are_binary()) {
// Return request as web-safe base64 string
std::vector<uint8_t> request_vector(serialized_request.begin(),
serialized_request.end());
*request = Base64SafeEncodeNoPad(request_vector);
} 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;
}
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();
crypto_session_->Close();
// This is the entire certificate (SignedDrmDeviceCertificate).
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.
SignedDrmDeviceCertificate signed_device_cert;
if (!signed_device_cert.ParseFromString(device_cert_data)) {
LOGE("Failed to parse signed DRM certificate");
return CERT_PROVISIONING_RESPONSE_ERROR_9;
}
DrmDeviceCertificate 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 DrmDeviceCertificate::RSA:
private_key.set_type(CryptoWrappedKey::kRsa);
break;
case DrmDeviceCertificate::ECC_SECP256R1:
case DrmDeviceCertificate::ECC_SECP384R1:
case DrmDeviceCertificate::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) {
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
SignedDrmDeviceCertificate signed_drm_device_certificate;
if (!signed_drm_device_certificate.ParseFromString(device_certificate) ||
!signed_drm_device_certificate.has_drm_certificate()) {
LOGE("Failed to parse signed DRM device certificate");
return false;
}
DrmDeviceCertificate drm_device_certificate;
if (!drm_device_certificate.ParseFromString(
signed_drm_device_certificate.drm_certificate()) ||
(drm_device_certificate.type() !=
video_widevine::DrmDeviceCertificate::DRM_USER_DEVICE)) {
LOGE("Failed to parse DRM device certificate message");
return false;
}
if (serial_number != nullptr) {
*serial_number = drm_device_certificate.serial_number();
}
if (system_id != nullptr) {
*system_id = drm_device_certificate.system_id();
}
return true;
}
} // namespace wvcdm