Files
android/libwvdrmengine/cdm/core/src/certificate_provisioning.cpp
Kyle Zhang 11255b7426 Pick widevine oemcrypto-v18 change
No-Typo-Check: From a third party header file
Bug: 260918793
Test: unit tests
Test: atp v2/widevine-eng/drm_compliance
Change-Id: I36effd6a10a99bdb2399ab1f4a0fad026d607c70
2022-12-21 00:03:50 +00:00

843 lines
33 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 "license_protocol_conversions.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";
// In case of provisioning 4, the default url is used as a way to inform app of
// the current provisioning stage. In the first stage, this suffix is appended
// to kProvisioningServerUrl; in the second stage, there is no change to
// kProvisioningServerUrl.
const std::string kProv40FirstStageServerUrlSuffix = "&preProvisioning=true";
// NOTE: Provider ID = widevine.com
const std::string kCpProductionServiceCertificate = wvutil::a2bs_hex(
"0ab9020803121051434fe2a44c763bcc2c826a2d6ef9a718f7d793d005228e02"
"3082010a02820101009e27088659dbd9126bc6ed594caf652b0eaab82abb9862"
"ada1ee6d2cb5247e94b28973fef5a3e11b57d0b0872c930f351b5694354a8c77"
"ed4ee69834d2630372b5331c5710f38bdbb1ec3024cfadb2a8ac94d977d391b7"
"d87c20c5c046e9801a9bffaf49a36a9ee6c5163eff5cdb63bfc750cf4a218618"
"984e485e23a10f08587ec5d990e9ab0de71460dfc334925f3fb9b55761c61e28"
"8398c387a0925b6e4dcaa1b36228d9feff7e789ba6e5ef6cf3d97e6ae05525db"
"38f826e829e9b8764c9e2c44530efe6943df4e048c3c5900ca2042c5235dc80d"
"443789e734bf8e59a55804030061ed48e7d139b521fbf35524b3000b3e2f6de0"
"001f5eeb99e9ec635f02030100013a0c7769646576696e652e636f6d12800332"
"2c2f3fedc47f8b7ba88a135a355466e378ed56a6fc29ce21f0cafc7fb253b073"
"c55bed253d8650735417aad02afaefbe8d5687902b56a164490d83d590947515"
"68860e7200994d322b5de07f82ef98204348a6c2c9619092340eb87df26f63bf"
"56c191dc069b80119eb3060d771afaaeb2d30b9da399ef8a41d16f45fd121e09"
"a0c5144da8f8eb46652c727225537ad65e2a6a55799909bbfb5f45b5775a1d1e"
"ac4e06116c57adfa9ce0672f19b70b876f88e8b9fbc4f96ccc500c676cfb173c"
"b6f52601573e2e45af1d9d2a17ef1487348c05cfc6d638ec2cae3fadb655e943"
"1330a75d2ceeaa54803e371425111e20248b334a3a50c8eca683c448b8ac402c"
"76e6f76e2751fbefb669f05703cec8c64cf7a62908d5fb870375eb0cc96c508e"
"26e0c050f3fd3ebe68cef9903ef6405b25fc6e31f93559fcff05657662b3653a"
"8598ed5751b38694419242a875d9e00d5a5832933024b934859ec8be78adccbb"
"1ec7127ae9afeef9c5cd2e15bd3048e8ce652f7d8c5d595a0323238c598a28");
// Used in provisioning 4 client identification name value pairs.
const std::string kKeyAppParameterSpoid = "spoid";
const std::string kKeyAppParameterProviderId = "provider_id";
const std::string kKeyAppParameterStableId = "stable_id";
// Retrieves |stored_oem_cert| from |file_handle|, and load the OEM private key
// to |crypto_session|. Returns true if all operations are successful.
bool RetrieveOemCertificateAndLoadPrivateKey(CryptoSession& crypto_session,
DeviceFiles& file_handle,
std::string& stored_oem_cert) {
stored_oem_cert.clear();
CryptoWrappedKey wrapped_private_key;
if (file_handle.RetrieveOemCertificate(&stored_oem_cert,
&wrapped_private_key) !=
DeviceFiles::kCertificateValid) {
LOGE("An invalid stored OEM certificated is retrieved");
stored_oem_cert.clear();
return false;
}
if (crypto_session.LoadOemCertificatePrivateKey(wrapped_private_key) !=
NO_ERROR) {
LOGE("Can not load the OEM private key");
stored_oem_cert.clear();
return false;
}
return true;
}
} // namespace
// Protobuf generated classes.
using video_widevine::DrmCertificate;
using video_widevine::EncryptedClientIdentification;
using video_widevine::HashAlgorithmProto;
using video_widevine::ProvisioningOptions;
using video_widevine::ProvisioningRequest;
using video_widevine::ProvisioningResponse;
using video_widevine::PublicKeyToCertify;
using video_widevine::SignedDrmCertificate;
using video_widevine::SignedProvisioningMessage;
// static
void CertificateProvisioning::GetProvisioningServerUrl(
std::string* default_url) {
if (default_url == nullptr) {
LOGE("Output |default_url| is null");
return;
}
default_url->assign(kProvisioningServerUrl);
}
CdmResponseType CertificateProvisioning::Init(
const std::string& service_certificate) {
const 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 CdmResponseType(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()) {
LOGE(
"Failed to set provider ID: "
"Service certificate provider ID is empty");
return CdmResponseType(SERVICE_CERTIFICATE_PROVIDER_ID_EMPTY);
}
request->set_provider_id(service_certificate_->provider_id());
} 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 CdmResponseType(NO_ERROR);
}
// Return the provisioning protocol version - dictated by OEMCrypto
// support for OEM certificates.
SignedProvisioningMessage::ProvisioningType
CertificateProvisioning::GetProvisioningType() {
switch (crypto_session_->GetPreProvisionTokenType()) {
case kClientTokenBootCertChain:
return SignedProvisioningMessage::PROVISIONING_40;
case kClientTokenOemCert:
return SignedProvisioningMessage::PROVISIONING_30;
default:
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(
wvutil::FileSystem* file_system,
RequestedSecurityLevel 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(
file_system, requested_security_level, cert_type, cert_authority, origin,
spoid, request, default_url));
}
CdmResponseType CertificateProvisioning::GetProvisioningRequestInternal(
wvutil::FileSystem* file_system,
RequestedSecurityLevel 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 CdmResponseType(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;
}
if (crypto_session_->GetPreProvisionTokenType() ==
kClientTokenBootCertChain) {
return GetProvisioning40RequestInternal(file_system, origin, spoid, request,
default_url);
}
// Prepare device provisioning request.
ProvisioningRequest provisioning_request;
status = FillEncryptedClientId(/*client_token=*/"", provisioning_request,
*service_certificate_);
if (status != NO_ERROR) return status;
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
? CdmResponseType(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.
const std::string encoded_nonce(reinterpret_cast<char*>(&nonce),
sizeof(nonce));
provisioning_request.set_nonce(encoded_nonce);
ProvisioningOptions* options = provisioning_request.mutable_options();
switch (cert_type) {
case kCertificateWidevine:
options->set_certificate_type(ProvisioningOptions::WIDEVINE_DRM);
break;
case kCertificateX509:
options->set_certificate_type(ProvisioningOptions::X509);
break;
default:
LOGE("Unknown certificate type: %d", static_cast<int>(cert_type));
return CdmResponseType(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;
bool should_specify_algorithm;
OEMCrypto_SignatureHashAlgorithm oec_algorithm = OEMCrypto_SHA1;
status = crypto_session_->PrepareAndSignProvisioningRequest(
serialized_message, &core_message, &request_signature,
should_specify_algorithm, oec_algorithm);
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 CdmResponseType(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());
signed_provisioning_msg.set_oemcrypto_core_message(core_message);
signed_provisioning_msg.set_protocol_version(
SignedProvisioningMessage::VERSION_1_1);
if (should_specify_algorithm) {
HashAlgorithmProto proto_algorithm =
HashAlgorithmProto::HASH_ALGORITHM_UNSPECIFIED;
if (!OecAlgorithmToProtoAlgorithm(oec_algorithm, proto_algorithm)) {
return CdmResponseType(UNSUPPORTED_SIGNATURE_HASH_ALGORITHM_3);
}
signed_provisioning_msg.set_hash_algorithm(proto_algorithm);
}
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 = wvutil::Base64SafeEncodeNoPad(serialized_request);
} else {
*request = std::move(serialized_request);
}
return CdmResponseType(NO_ERROR);
}
CdmResponseType CertificateProvisioning::GetProvisioning40RequestInternal(
wvutil::FileSystem* file_system, const std::string& origin,
const std::string& spoid, CdmProvisioningRequest* request,
std::string* default_url) {
if (!crypto_session_->IsOpen()) {
LOGE("Crypto session is not open");
return CdmResponseType(PROVISIONING_4_CRYPTO_SESSION_NOT_OPEN);
}
if (file_system == nullptr) {
LOGE("file_system is nullptr but is required in provisioning 4");
return CdmResponseType(PROVISIONING_4_FILE_SYSTEM_IS_NULL);
}
const CdmSecurityLevel security_level = crypto_session_->GetSecurityLevel();
wvutil::FileSystem global_file_system;
DeviceFiles global_file_handle(&global_file_system);
if (!global_file_handle.Init(security_level)) {
LOGE("Failed to initialize global DeviceFiles");
return CdmResponseType(PROVISIONING_4_FAILED_TO_INITIALIZE_DEVICE_FILES);
}
ProvisioningRequest provisioning_request;
// Determine the current stage by checking if OEM cert exists.
std::string stored_oem_cert;
if (global_file_handle.HasOemCertificate()) {
// This is second stage requesting for DRM cert. We try to use the stored
// OEM cert. In case of error, we just fall back to the first stage
// provisioning (request for an OEM cert).
if (!RetrieveOemCertificateAndLoadPrivateKey(
*crypto_session_, global_file_handle, stored_oem_cert)) {
stored_oem_cert.clear();
LOGD("Deleting the stored OEM certificate due to unsuccessful read");
if (!global_file_handle.RemoveOemCertificate()) {
// This should not happen.
LOGE("Failed to delete the OEM certificate certificate");
}
}
}
// Retrieve the Spoid, but put it to the client identification instead, so it
// is encrypted.
CdmAppParameterMap additional_parameter;
CdmResponseType status =
SetSpoidParameter(origin, spoid, &provisioning_request);
if (status != NO_ERROR) return status;
if (provisioning_request.has_spoid()) {
additional_parameter[kKeyAppParameterSpoid] = provisioning_request.spoid();
provisioning_request.clear_spoid();
}
if (provisioning_request.has_provider_id()) {
additional_parameter[kKeyAppParameterProviderId] =
provisioning_request.provider_id();
provisioning_request.clear_provider_id();
}
if (provisioning_request.has_stable_id()) {
additional_parameter[kKeyAppParameterStableId] =
provisioning_request.stable_id();
provisioning_request.clear_stable_id();
}
if (stored_oem_cert.empty()) {
// This is the first stage provisioning.
default_url->assign(kProvisioningServerUrl +
kProv40FirstStageServerUrlSuffix);
// First-stage provisioning always uses the WV production service cert for
// encryption.
ServiceCertificate wv_service_cert;
status = wv_service_cert.Init(kCpProductionServiceCertificate);
if (status != NO_ERROR) return status;
// Since |stored_oem_cert| is empty, the client identification token will be
// retrieved from OEMCrypto, which is the BCC in this case.
status = FillEncryptedClientIdWithAdditionalParameter(
stored_oem_cert, additional_parameter, provisioning_request,
wv_service_cert);
if (status != NO_ERROR) return status;
} else {
// This is the second stage provisioning.
default_url->assign(kProvisioningServerUrl);
// Since |stored_oem_cert| is non-empty, it will be used as the client
// identification token.
status = FillEncryptedClientIdWithAdditionalParameter(
stored_oem_cert, additional_parameter, provisioning_request,
*service_certificate_);
if (status != NO_ERROR) return status;
}
std::string public_key;
std::string public_key_signature;
provisioning_40_wrapped_private_key_.clear();
provisioning_40_key_type_ = CryptoWrappedKey::kUninitialized;
status = crypto_session_->GenerateCertificateKeyPair(
&public_key, &public_key_signature, &provisioning_40_wrapped_private_key_,
&provisioning_40_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
? PublicKeyToCertify::RSA
: PublicKeyToCertify::ECC);
std::string serialized_message;
provisioning_request.SerializeToString(&serialized_message);
SignedProvisioningMessage signed_provisioning_msg;
signed_provisioning_msg.set_message(serialized_message);
signed_provisioning_msg.set_provisioning_type(GetProvisioningType());
signed_provisioning_msg.set_protocol_version(
SignedProvisioningMessage::VERSION_1_1);
// Core message and request signature are added to the provisioning request
// starting OEMCrypto v18
uint32_t api_version = 0;
const bool core_message_signature_required =
crypto_session_->GetApiVersion(&api_version) &&
(api_version >= OEM_CRYPTO_API_VERSION_SUPPORTS_PROV40_CORE_MESSAGE);
if (core_message_signature_required) {
std::string core_message;
std::string request_signature;
bool should_specify_algorithm;
OEMCrypto_SignatureHashAlgorithm oec_algorithm = OEMCrypto_SHA1;
status = crypto_session_->PrepareAndSignProvisioningRequest(
serialized_message, &core_message, &request_signature,
should_specify_algorithm, oec_algorithm);
if (status != NO_ERROR) {
LOGE("Failed to prepare provisioning 4.0 request: status = %d",
static_cast<int>(status));
return status;
}
if (core_message.empty()) {
LOGE("Core message is empty");
return CdmResponseType(CERT_PROVISIONING_REQUEST_ERROR_4);
}
if (request_signature.empty()) {
LOGE("Request signature is empty");
return CdmResponseType(CERT_PROVISIONING_REQUEST_ERROR_4);
}
signed_provisioning_msg.set_oemcrypto_core_message(core_message);
signed_provisioning_msg.set_signature(request_signature);
if (should_specify_algorithm) {
HashAlgorithmProto proto_algorithm =
HashAlgorithmProto::HASH_ALGORITHM_UNSPECIFIED;
if (!OecAlgorithmToProtoAlgorithm(oec_algorithm, proto_algorithm)) {
return CdmResponseType(UNSUPPORTED_SIGNATURE_HASH_ALGORITHM_4);
}
signed_provisioning_msg.set_hash_algorithm(proto_algorithm);
}
}
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 = wvutil::Base64SafeEncodeNoPad(serialized_request);
} else {
*request = std::move(serialized_request);
}
return CdmResponseType(NO_ERROR);
}
CdmResponseType CertificateProvisioning::FillEncryptedClientId(
const std::string& client_token, ProvisioningRequest& provisioning_request,
const ServiceCertificate& service_certificate) {
CdmAppParameterMap app_parameter;
return FillEncryptedClientIdWithAdditionalParameter(
client_token, app_parameter, provisioning_request, service_certificate);
}
CdmResponseType
CertificateProvisioning::FillEncryptedClientIdWithAdditionalParameter(
const std::string& client_token,
const CdmAppParameterMap& additional_parameter,
ProvisioningRequest& provisioning_request,
const ServiceCertificate& service_certificate) {
if (!crypto_session_->IsOpen()) {
return CdmResponseType(UNKNOWN_ERROR);
}
wvcdm::ClientIdentification id;
CdmResponseType status =
id.InitForProvisioningRequest(client_token, crypto_session_.get());
if (status != NO_ERROR) return status;
video_widevine::ClientIdentification client_id;
status = id.Prepare(additional_parameter, kEmptyString, &client_id);
if (status != NO_ERROR) return status;
if (!service_certificate.has_certificate()) {
LOGE("Service certificate not staged");
return CdmResponseType(CERT_PROVISIONING_EMPTY_SERVICE_CERTIFICATE);
}
// Encrypt client identification
return service_certificate.EncryptClientId(
crypto_session_.get(), &client_id,
provisioning_request.mutable_encrypted_client_id());
}
CdmResponseType CertificateProvisioning::HandleProvisioning40Response(
wvutil::FileSystem* file_system, const std::string& response_message) {
ProvisioningResponse provisioning_response;
if (response_message.empty() ||
!provisioning_response.ParseFromString(response_message)) {
return CdmResponseType(PROVISIONING_4_RESPONSE_FAILED_TO_PARSE_MESSAGE);
}
if (provisioning_response.has_status() &&
provisioning_response.status() != ProvisioningResponse::NO_ERROR) {
LOGE("Provisioning Response status: %d", provisioning_response.status());
switch (provisioning_response.status()) {
case ProvisioningResponse::REVOKED_DEVICE_CREDENTIALS:
case ProvisioningResponse::REVOKED_DEVICE_SERIES:
return CdmResponseType(DEVICE_REVOKED);
default:
return CdmResponseType(PROVISIONING_4_RESPONSE_HAS_ERROR_STATUS);
}
}
const std::string& device_certificate =
provisioning_response.device_certificate();
if (device_certificate.empty()) {
LOGE("Provisioning response has no certificate");
return CdmResponseType(PROVISIONING_4_RESPONSE_HAS_NO_CERTIFICATE);
}
if (provisioning_40_wrapped_private_key_.empty()) {
LOGE("No private key was generated");
return CdmResponseType(PROVISIONING_4_NO_PRIVATE_KEY);
}
const CryptoWrappedKey private_key(provisioning_40_key_type_,
provisioning_40_wrapped_private_key_);
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)) {
LOGE("Failed to initialize global DeviceFiles");
return CdmResponseType(PROVISIONING_4_FAILED_TO_INITIALIZE_DEVICE_FILES_2);
}
// Check the stage of the provisioning by checking if an OEM cert is already
// stored in the file system.
if (!global_file_handle.HasOemCertificate()) {
// No OEM cert already stored => the response is expected to be an OEM cert.
if (!global_file_handle.StoreOemCertificate(device_certificate,
private_key)) {
LOGE("Failed to store provisioning 4 OEM certificate");
return CdmResponseType(PROVISIONING_4_FAILED_TO_STORE_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);
}
}
return CdmResponseType(NO_ERROR);
}
// The response message consists of a device certificate and the
// wrapped device private key (either RSA or ECC). The wrapped device
// private key is loaded into the TEE, unwrapped, verified and
// re-wrapped. The device certificate and re-wrapped device private
// key are stored on the device.
//
// Returns NO_ERROR for success and CERT_PROVISIONING_RESPONSE_ERROR_? if fails.
CdmResponseType CertificateProvisioning::HandleProvisioningResponse(
wvutil::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 CdmResponseType(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: %s",
response.c_str());
return CdmResponseType(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 CdmResponseType(CERT_PROVISIONING_RESPONSE_ERROR_2);
}
if (signed_response.provisioning_type() ==
SignedProvisioningMessage::PROVISIONING_40) {
return HandleProvisioning40Response(file_system, signed_response.message());
}
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 (!signed_response.has_oemcrypto_core_message()) {
LOGE("Signed response does not have core message");
error = true;
}
if (error) return CdmResponseType(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 = signed_response.oemcrypto_core_message();
ProvisioningResponse provisioning_response;
if (!provisioning_response.ParseFromString(signed_message)) {
LOGE("Failed to parse provisioning response");
return CdmResponseType(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 CdmResponseType(DEVICE_REVOKED);
default:
return CdmResponseType(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 CdmResponseType(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 CdmResponseType(CERT_PROVISIONING_RESPONSE_ERROR_9);
}
DrmCertificate device_cert;
if (!device_cert.ParseFromString(signed_device_cert.drm_certificate())) {
LOGE("Failed to parse DRM certificate");
return CdmResponseType(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 CdmResponseType(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 CdmResponseType(CERT_PROVISIONING_RESPONSE_ERROR_7);
}
if (!handle.StoreCertificate(device_cert_data, private_key)) {
LOGE("Failed to store provisioning certificate");
return CdmResponseType(CERT_PROVISIONING_RESPONSE_ERROR_8);
}
return CdmResponseType(NO_ERROR);
}
// 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.
// static
bool CertificateProvisioning::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 '%s'", json_start_substr.c_str());
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 '%s'", json_end_substr.c_str());
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 =
wvutil::Base64SafeDecode(message_string);
result->assign(decoded_message.begin(), decoded_message.end());
return true;
}
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() != 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