Files
android/libwvdrmengine/cdm/core/src/certificate_provisioning.cpp
John W. Bruce c85351682f SPOID
(This is a merge of go/wvgerrit/23182)

This patch adds the framework for Stable Per-Origin Identifiers to the
CDM. Calculating SPOIDs will be done on the client-side, and they are
sent as part of the provisioning request. SPOIDs are also available to
the app as the Device Unique ID, replacing the previous method of
returning the actual Device Unique ID from the keybox / OEM certificate.

Different SPOIDs must use separate storage, just as different origins
already do. Support for this has been added to the Android adapter to the
CDM Core. However, the code in the Android glue layer that would drive
this behavior will be checked in in a separate change. As such, all
Android devices will continue using the legacy behavior even after this
patch goes in, until the glue layer code can be updated.

Bug: 27101531
Test: CE CDM Unit Tests
Test: Linux Jenkins Unit Tests
Test: Android Unit Tests (with and without SPOIDs forced on)
Test: Android GTS Tests
Change-Id: Ia0caf890381cbcb97504d08b19aeab8b29bd07ae
2017-01-25 15:45:01 -08:00

363 lines
13 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 "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";
}
namespace wvcdm {
// Protobuf generated classes.
using video_widevine::ClientIdentification;
using video_widevine::ProvisioningOptions;
using video_widevine::ProvisioningRequest;
using video_widevine::ProvisioningResponse;
using video_widevine::SignedProvisioningMessage;
/*
* This function converts SignedProvisioningRequest into base64 string. It then
* wraps it in JSON format expected by the frontend. This server requires a
* "web-safe" base 64 encoding, where '+' becomes '-' and '/' becomes '_'.
*
* Returns the JSON formated string in *request. The JSON string will be
* appended as a query parameter, i.e. signedRequest=<base 64 encoded
* SignedProvisioningRequest>. All base64 '=' padding chars must be removed.
*
* The JSON formated request takes the following format:
*
* base64 encoded message
*/
void CertificateProvisioning::ComposeJsonRequestAsQueryString(
const std::string& message, CdmProvisioningRequest* request) {
// Performs base64 encoding for message
std::vector<uint8_t> message_vector(message.begin(), message.end());
std::string message_b64 = Base64SafeEncodeNoPad(message_vector);
request->assign(message_b64);
}
/*
* 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) {
switch (crypto_session_.GetPreProvisionTokenType()) {
case kClientTokenKeybox:
*token_type = ClientIdentification::KEYBOX;
return true;
case kClientTokenOemCert:
*token_type = ClientIdentification::OEM_DEVICE_CERTIFICATE;
return true;
case kClientTokenDrmCert:
default:
// shouldn't happen
return false;
}
}
/*
* Fill in the appropriate field relating to stable IDs in the provisioning
* request, no more than one of |stable_id|, |provider_id|, and |spoid|. It is
* also valid (though deprecated) to fill in none of these in order to leave the
* stable ID behavior up to the provisioning server.
*/
bool CertificateProvisioning::FillStableIdField(
const std::string& origin,
const std::string& spoid,
ProvisioningRequest* request) {
if (!request) {
LOGE("CertificateProvisioning::FillStableIdField : 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() &&
false /* TODO(gmorgan): use provider ID. */) {
// Use the provider ID from the service certificate
// TODO(gmorgan): use provider ID.
// request->set_provider_id(???);
} else if (origin != EMPTY_ORIGIN) {
// Legacy behavior - Concatenate Unique ID with Origin
std::string device_unique_id;
if (!crypto_session_.GetDeviceUniqueId(&device_unique_id)) {
LOGE("CryptoSession::GetStableIdField: Failure to get 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;
}
/*
* Composes a device provisioning request and output the request in JSON format
* in *request. It also returns the default url for the provisioning server
* in *default_url.
*
* 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 sts = crypto_session_.Open(requested_security_level);
if (NO_ERROR != sts) {
LOGE("GetProvisioningRequest: fails to create a crypto session");
return sts;
}
// Prepares 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) in progress - encrypt ClientIdentification.
if (encrypt) {
EncryptedClientIdentification* encrypted_client_id =
provisioning_request->mutable_encrypted_client_id();
CdmResponseType sts;
sts = EncryptClientId(client_id, encrypted_client_id, certificate);
if (NO_ERROR == sts) {
provisioning_request->clear_client_id();
} else {
provisioning_request->clear_encrypted_client_id();
}
return sts;
}
#endif
uint32_t nonce;
if (!crypto_session_.GenerateNonce(&nonce)) {
LOGE("GetProvisioningRequest: fails to generate a nonce");
return CERT_PROVISIONING_REQUEST_ERROR_2;
}
// 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 (!FillStableIdField(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);
// Converts request into JSON string
ComposeJsonRequestAsQueryString(serialized_request, request);
return NO_ERROR;
}
/*
* Parses the input json_str and locates substring using start_substr and
* end_stubstr. The found base64 substring is then decoded and returns
* in *result.
*
* Returns true for success and false if fails.
*/
bool CertificateProvisioning::ParseJsonResponse(
const CdmProvisioningResponse& json_str, const std::string& start_substr,
const std::string& end_substr, std::string* result) {
std::string b64_string;
size_t start = json_str.find(start_substr);
if (start == json_str.npos) {
LOGE("ParseJsonResponse: cannot find start substring");
return false;
}
size_t end = json_str.find(end_substr, start + start_substr.length());
if (end == json_str.npos) {
LOGE("ParseJsonResponse cannot locate end substring");
return false;
}
size_t b64_string_size = end - start - start_substr.length();
b64_string.assign(json_str, start + start_substr.length(), b64_string_size);
// Decodes base64 substring and returns it in *result
std::vector<uint8_t> result_vector = Base64SafeDecode(b64_string);
result->assign(result_vector.begin(), result_vector.end());
return true;
}
/*
* 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,
std::string* cert, std::string* wrapped_key) {
// Extracts signed response from JSON string, decodes base64 signed response
const std::string kMessageStart = "\"signedResponse\": \"";
const std::string kMessageEnd = "\"";
std::string serialized_signed_response;
if (!ParseJsonResponse(response, kMessageStart, kMessageEnd,
&serialized_signed_response)) {
LOGE("Fails to extract signed serialized response from JSON response");
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(serialized_signed_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();
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;
}
const std::string& enc_rsa_key = provisioning_response.device_rsa_key();
const std::string& nonce = provisioning_response.nonce();
const std::string& rsa_key_iv = provisioning_response.device_rsa_key_iv();
const std::string& signature = signed_response.signature();
std::string wrapped_rsa_key;
if (!crypto_session_.RewrapDeviceRSAKey(signed_message, signature, nonce,
enc_rsa_key, rsa_key_iv,
&wrapped_rsa_key)) {
LOGE("HandleProvisioningResponse: RewrapDeviceRSAKey fails");
return CERT_PROVISIONING_RESPONSE_ERROR_6;
}
crypto_session_.Close();
if (cert_type_ == kCertificateX509) {
*cert = provisioning_response.device_certificate();
*wrapped_key = wrapped_rsa_key;
return NO_ERROR;
}
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_rsa_key)) {
LOGE("HandleProvisioningResponse: failed to save provisioning certificate");
return CERT_PROVISIONING_RESPONSE_ERROR_8;
}
return NO_ERROR;
}
} // namespace wvcdm