398 lines
14 KiB
C++
398 lines
14 KiB
C++
// Copyright 2013 Google Inc. All Rights Reserved.
|
|
|
|
#include "certificate_provisioning.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 {
|
|
|
|
// 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";
|
|
|
|
/*
|
|
* 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.
|
|
*/
|
|
void 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;
|
|
|
|
size_t start = provisioning_response.find(json_start_substr);
|
|
|
|
if (start == provisioning_response.npos) {
|
|
// Message is not properly wrapped - reject it.
|
|
LOGE("ExtractAndDecodeSignedMessage: cannot locate start substring");
|
|
result->clear();
|
|
return;
|
|
} else {
|
|
// Appears to be JSON-wrapped protobuf - find end of protobuf portion.
|
|
size_t end = provisioning_response.find(json_end_substr,
|
|
start + json_start_substr.length());
|
|
if (end == provisioning_response.npos) {
|
|
LOGE("ExtractAndDecodeSignedMessage: cannot locate end substring");
|
|
result->clear();
|
|
return;
|
|
}
|
|
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("ExtractAndDecodeSignedMessage: CdmProvisioningResponse is empty");
|
|
result->clear();
|
|
return;
|
|
}
|
|
|
|
// Decode the base64-encoded message.
|
|
const std::vector<uint8_t> decoded_message =
|
|
wvcdm::Base64SafeDecode(message_string);
|
|
result->assign(decoded_message.begin(), decoded_message.end());
|
|
}
|
|
|
|
} // namespace
|
|
|
|
namespace wvcdm {
|
|
// Protobuf generated classes.
|
|
using video_widevine::ClientIdentification;
|
|
using video_widevine::EncryptedClientIdentification;
|
|
using video_widevine::ProvisioningOptions;
|
|
using video_widevine::ProvisioningRequest;
|
|
using video_widevine::ProvisioningResponse;
|
|
using video_widevine::SignedProvisioningMessage;
|
|
|
|
/*
|
|
* Return the ClientIdentification message token type for provisioning request.
|
|
* NOTE: a DRM Cert should never be presented to the provisioning server.
|
|
*/
|
|
bool CertificateProvisioning::GetProvisioningTokenType(
|
|
ClientIdentification::TokenType* token_type) {
|
|
CdmClientTokenType token = crypto_session_.GetPreProvisionTokenType();
|
|
switch (token) {
|
|
case kClientTokenKeybox:
|
|
*token_type = ClientIdentification::KEYBOX;
|
|
return true;
|
|
case kClientTokenOemCert:
|
|
*token_type = ClientIdentification::OEM_DEVICE_CERTIFICATE;
|
|
return true;
|
|
case kClientTokenDrmCert:
|
|
default:
|
|
// shouldn't happen
|
|
LOGE("CertificateProvisioning::GetProvisioningTokenType: unexpected "
|
|
"provisioning type: %d", token);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* 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.
|
|
*/
|
|
bool CertificateProvisioning::SetSpoidParameter(
|
|
const std::string& origin, const std::string& spoid,
|
|
ProvisioningRequest* request) {
|
|
if (!request) {
|
|
LOGE("CertificateProvisioning::SetSpoidParameter: No request buffer "
|
|
"passed to method.");
|
|
return false;
|
|
}
|
|
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("CertificateProvisioning::SetSpoidParameter: Failure getting "
|
|
"provider ID");
|
|
return false;
|
|
}
|
|
} else if (origin != EMPTY_ORIGIN) {
|
|
// Legacy behavior - Concatenate Unique ID with Origin
|
|
std::string device_unique_id;
|
|
if (!crypto_session_.GetInternalDeviceUniqueId(&device_unique_id)) {
|
|
LOGE("CertificateProvisioning::SetSpoidParameter: Failure getting "
|
|
"device unique ID");
|
|
return false;
|
|
}
|
|
request->set_stable_id(device_unique_id + origin);
|
|
} // No else clause, by design. It is valid to do nothing.
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
* Return the provisioning protocol version - dictated by OEMCrypto
|
|
* support for OEM certificates.
|
|
*/
|
|
SignedProvisioningMessage::ProtocolVersion
|
|
CertificateProvisioning::GetProtocolVersion() {
|
|
if (crypto_session_.GetPreProvisionTokenType() == kClientTokenOemCert)
|
|
return SignedProvisioningMessage::VERSION_3;
|
|
else
|
|
return SignedProvisioningMessage::VERSION_2;
|
|
}
|
|
|
|
/*
|
|
* 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 (!default_url) {
|
|
LOGE("GetProvisioningRequest: pointer for returning URL is NULL");
|
|
return CERT_PROVISIONING_REQUEST_ERROR_1;
|
|
}
|
|
|
|
default_url->assign(kProvisioningServerUrl);
|
|
|
|
CdmResponseType status = crypto_session_.Open(requested_security_level);
|
|
if (NO_ERROR != status) {
|
|
LOGE("GetProvisioningRequest: fails to create a crypto session");
|
|
return status;
|
|
}
|
|
|
|
// Prepare device provisioning request.
|
|
ProvisioningRequest provisioning_request;
|
|
std::string token;
|
|
ClientIdentification* client_id = provisioning_request.mutable_client_id();
|
|
ClientIdentification::TokenType token_type;
|
|
|
|
if (!GetProvisioningTokenType(&token_type)) {
|
|
LOGE("GetProvisioningRequest: bad token type");
|
|
return CERT_PROVISIONING_CLIENT_TOKEN_ERROR_1;
|
|
}
|
|
if (!crypto_session_.GetProvisioningToken(&token)) {
|
|
LOGE("GetProvisioningRequest: failure getting provisioning token");
|
|
return CERT_PROVISIONING_CLIENT_TOKEN_ERROR_2;
|
|
}
|
|
client_id->set_token(token);
|
|
client_id->set_type(token_type);
|
|
|
|
#if 0 // TODO(gmorgan) Encrypt ClientIdentification. Pending Design.
|
|
if (service_certificate_->has_certificate()) {
|
|
EncryptedClientIdentification* encrypted_client_id =
|
|
provisioning_request.mutable_encrypted_client_id();
|
|
CdmResponseType status;
|
|
status = service_certificate_->EncryptClientId(&crypto_session_, client_id,
|
|
encrypted_client_id);
|
|
if (status == NO_ERROR) {
|
|
provisioning_request.clear_client_id();
|
|
} else {
|
|
provisioning_request.clear_encrypted_client_id();
|
|
}
|
|
return status;
|
|
}
|
|
#endif
|
|
|
|
uint32_t nonce;
|
|
if (!crypto_session_.GenerateNonce(&nonce)) {
|
|
LOGE("GetProvisioningRequest: fails to generate a nonce");
|
|
return CERT_PROVISIONING_NONCE_GENERATION_ERROR;
|
|
}
|
|
|
|
// 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("GetProvisioningRequest: unknown certificate type %ld", cert_type);
|
|
return CERT_PROVISIONING_INVALID_CERT_TYPE;
|
|
}
|
|
|
|
cert_type_ = cert_type;
|
|
options->set_certificate_authority(cert_authority);
|
|
|
|
if (!SetSpoidParameter(origin, spoid, &provisioning_request)) {
|
|
return CERT_PROVISIONING_GET_KEYBOX_ERROR_2;
|
|
}
|
|
|
|
std::string serialized_message;
|
|
provisioning_request.SerializeToString(&serialized_message);
|
|
|
|
// Derives signing and encryption keys and constructs signature.
|
|
std::string request_signature;
|
|
if (!crypto_session_.PrepareRequest(serialized_message, true,
|
|
&request_signature)) {
|
|
LOGE("GetProvisioningRequest: fails to prepare request");
|
|
return CERT_PROVISIONING_REQUEST_ERROR_3;
|
|
}
|
|
if (request_signature.empty()) {
|
|
LOGE("GetProvisioningRequest: 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());
|
|
|
|
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->assign(Base64SafeEncodeNoPad(request_vector));
|
|
} else {
|
|
request->swap(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("HandleProvisioningResponse: 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.
|
|
ExtractAndDecodeSignedMessage(response_message, &response);
|
|
if (response.empty()) {
|
|
LOGE("HandleProvisioningResponse: 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("HandleProvisioningResponse: fails to parse signed response");
|
|
return CERT_PROVISIONING_RESPONSE_ERROR_2;
|
|
}
|
|
|
|
bool error = false;
|
|
if (!signed_response.has_signature()) {
|
|
LOGE("HandleProvisioningResponse: signature not found");
|
|
error = true;
|
|
}
|
|
|
|
if (!signed_response.has_message()) {
|
|
LOGE("HandleProvisioningResponse: message not found");
|
|
error = true;
|
|
}
|
|
|
|
if (error) return CERT_PROVISIONING_RESPONSE_ERROR_3;
|
|
|
|
const std::string& signed_message = signed_response.message();
|
|
const std::string& signature = signed_response.signature();
|
|
ProvisioningResponse provisioning_response;
|
|
|
|
if (!provisioning_response.ParseFromString(signed_message)) {
|
|
LOGE("HandleProvisioningResponse: Fails to parse signed message");
|
|
return CERT_PROVISIONING_RESPONSE_ERROR_4;
|
|
}
|
|
|
|
if (!provisioning_response.has_device_rsa_key()) {
|
|
LOGE("HandleProvisioningResponse: key not found");
|
|
return CERT_PROVISIONING_RESPONSE_ERROR_5;
|
|
}
|
|
|
|
// If Provisioning 3.0 (OEM Cert provisioned), verify that the
|
|
// message is properly signed.
|
|
if (crypto_session_.GetPreProvisionTokenType() == kClientTokenOemCert) {
|
|
if (service_certificate_->VerifySignedMessage(signed_message, signature)
|
|
!= NO_ERROR) {
|
|
LOGE("HandleProvisioningResponse: message not properly signed");
|
|
return CERT_PROVISIONING_RESPONSE_ERROR_6;
|
|
}
|
|
}
|
|
|
|
const std::string& new_private_key = provisioning_response.device_rsa_key();
|
|
const std::string& nonce = provisioning_response.nonce();
|
|
const std::string& iv = provisioning_response.device_rsa_key_iv();
|
|
|
|
const std::string& wrapping_key = (provisioning_response.has_wrapping_key()) ?
|
|
provisioning_response.wrapping_key() : std::string();
|
|
|
|
std::string wrapped_private_key;
|
|
|
|
if (!crypto_session_.RewrapCertificate(signed_message, signature, nonce,
|
|
new_private_key, iv, wrapping_key,
|
|
&wrapped_private_key)) {
|
|
LOGE("HandleProvisioningResponse: RewrapCertificate fails");
|
|
return CERT_PROVISIONING_RESPONSE_ERROR_6;
|
|
}
|
|
|
|
crypto_session_.Close();
|
|
|
|
if (cert_type_ == kCertificateX509) {
|
|
*cert = provisioning_response.device_certificate();
|
|
*wrapped_key = wrapped_private_key;
|
|
return NO_ERROR;
|
|
}
|
|
|
|
// This is the entire certificate (SignedDrmDeviceCertificate).
|
|
// This will be stored to the device as the final step in the device
|
|
// provisioning process.
|
|
const std::string& device_certificate =
|
|
provisioning_response.device_certificate();
|
|
|
|
DeviceFiles handle(file_system);
|
|
if (!handle.Init(crypto_session_.GetSecurityLevel())) {
|
|
LOGE("HandleProvisioningResponse: failed to init DeviceFiles");
|
|
return CERT_PROVISIONING_RESPONSE_ERROR_7;
|
|
}
|
|
if (!handle.StoreCertificate(device_certificate, wrapped_private_key)) {
|
|
LOGE("HandleProvisioningResponse: failed to save provisioning certificate");
|
|
return CERT_PROVISIONING_RESPONSE_ERROR_8;
|
|
}
|
|
|
|
return NO_ERROR;
|
|
}
|
|
|
|
} // namespace wvcdm
|