[ Merge of http://go/wvgerrit/133729 ] The OtaKeyboxProvisioner is a system-wide provisioner for sharing the provisioning workflow between CDM engines. Bug: 189232882 Test: GtsMediaTestCases Change-Id: I873af3087cc05e1831bdd1d2c14fb002b73e6902 Added keybox provisioning proto fields. [ Merge of http://go/wvgerrit/133730 and http://go/ag/15113032 ] This CL copies over the required license_protocol.proto changes that are required for OTA keybox provisioning. These fields are defined in the server-side certificate_provisioning.proto, defined in http://cl/377533774. Note, changes are slightly different from server proto due to the RVC version of license_protocol.proto being out of date with SC and newer changes. Bug: 189232882 Test: run_x86_64_tests Change-Id: I55fcf6a7ac2ba4b6026b9acc63e822ff33c431d9 Added OTA keybox provisioning device files. [ Merge of http://go/wvgerrit/133743 and http://go/ag/15421141 ] This change adds a new set of proto messages/fields the CDM's device files for recording device and engine information around OTA keybox provisioning (OKP). To make cleanup and thread protection possible, there is a single file which will contain all the information for the device as a whole and each CDM engine tied to an app/origin. Bug: 189232882 Test: Linux unit tests Change-Id: Iaf80cd6342f32657e04416750d9b278d935821a5 Client ID for OKP requests. [ Merge of http://go/wvgerrit/133744 and http://go/ag/15645331 ] Extended the CDM ClientIdentification class to support a subset of client info used for OKP requests. Bug: 189232882 Test: Android unit tests Merged-In: I6aafb4f2164efe69bc733ece0a912f0e91893b91 Change-Id: I6aafb4f2164efe69bc733ece0a912f0e91893b91
560 lines
20 KiB
C++
560 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;
|
|
|
|
// 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) {
|
|
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.InitForProvisioning(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
|