Certificate provisioning verification

bug: 8620943

This is a merge of changes made to the Widevine CDM
repository during certificate provisioning verification.

The following changes are included:

Fixes for certificate based licensing
https://widevine-internal-review.googlesource.com/#/c/5162/

Base64 encode and decode now handles non-multiple of 24-bits input
https://widevine-internal-review.googlesource.com/#/c/4981/

Fixed issues with device provisioning response handling
https://widevine-internal-review.googlesource.com/#/c/5153/

Persistent storage to support device certificates
https://widevine-internal-review.googlesource.com/#/c/5161/

Enable loading of certificates
https://widevine-internal-review.googlesource.com/#/c/5172/

Provide license server url
https://widevine-internal-review.googlesource.com/#/c/5173/

Change-Id: I0c032c1ae0055dcc1a7a77ad4b0ea0898030dc7d
This commit is contained in:
Jeff Tinker
2013-04-22 20:05:55 -07:00
parent 3a28eeeb68
commit 958bbe6d05
30 changed files with 1497 additions and 290 deletions

View File

@@ -8,6 +8,7 @@
#include "buffer_reader.h"
#include "cdm_session.h"
#include "crypto_engine.h"
#include "device_files.h"
#include "license_protocol.pb.h"
#include "log.h"
#include "properties.h"
@@ -29,7 +30,7 @@ using video_widevine_server::sdk::SignedProvisioningMessage;
typedef std::map<CdmSessionId,CdmSession*>::const_iterator CdmSessionIter;
CdmEngine::CdmEngine() {
CdmEngine::CdmEngine() : provisioning_session_(NULL) {
Properties::Init();
}
@@ -105,7 +106,8 @@ CdmResponseType CdmEngine::GenerateKeyRequest(
const CdmInitData& init_data,
const CdmLicenseType license_type,
CdmAppParameterMap& app_parameters,
CdmKeyMessage* key_request) {
CdmKeyMessage* key_request,
std::string* server_url) {
LOGI("CdmEngine::GenerateKeyRequest");
CdmSessionIter iter = sessions_.find(session_id);
@@ -140,7 +142,8 @@ CdmResponseType CdmEngine::GenerateKeyRequest(
CdmResponseType sts = iter->second->GenerateKeyRequest(extracted_pssh,
license_type,
app_parameters,
key_request);
key_request,
server_url);
if (KEY_MESSAGE != sts) {
LOGE("CdmEngine::GenerateKeyRequest: key request generation failed, sts=%d",
@@ -156,9 +159,6 @@ CdmResponseType CdmEngine::GenerateKeyRequest(
CdmResponseType CdmEngine::AddKey(
const CdmSessionId& session_id,
bool is_key_system_init_data_present,
const CdmKeySystem& key_system,
const CdmInitData& init_data,
const CdmKeyResponse& key_data) {
LOGI("CdmEngine::AddKey");
@@ -168,14 +168,6 @@ CdmResponseType CdmEngine::AddKey(
return KEY_ERROR;
}
if (is_key_system_init_data_present) {
// TODO(edwinwong, rfrias): validate key_system has not changed
}
if (is_key_system_init_data_present) {
// TODO(edwinwong, rfrias): validate init_data has not changed
}
if (key_data.empty()) {
LOGE("CdmEngine::AddKey: no key_data");
return KEY_ERROR;
@@ -218,10 +210,8 @@ CdmResponseType CdmEngine::CancelKeyRequest(
CdmResponseType CdmEngine::GenerateRenewalRequest(
const CdmSessionId& session_id,
bool is_key_system_init_data_present,
const CdmKeySystem& key_system,
const CdmInitData& init_data,
CdmKeyMessage* key_request) {
CdmKeyMessage* key_request,
std::string* server_url) {
LOGI("CdmEngine::GenerateRenewalRequest");
CdmSessionIter iter = sessions_.find(session_id);
@@ -230,14 +220,6 @@ CdmResponseType CdmEngine::GenerateRenewalRequest(
return KEY_ERROR;
}
if (is_key_system_init_data_present) {
// TODO(edwinwong, rfrias): validate key_system has not changed
}
if (is_key_system_init_data_present) {
// TODO(edwinwong, rfrias): validate init_data has not changed
}
if (!key_request) {
LOGE("CdmEngine::GenerateRenewalRequest: no key request destination provided");
return KEY_ERROR;
@@ -245,7 +227,8 @@ CdmResponseType CdmEngine::GenerateRenewalRequest(
key_request->clear();
CdmResponseType sts = iter->second->GenerateRenewalRequest(key_request);
CdmResponseType sts = iter->second->GenerateRenewalRequest(key_request,
server_url);
if (KEY_MESSAGE != sts) {
LOGE("CdmEngine::GenerateRenewalRequest: key request generation failed, sts=%d",
@@ -258,9 +241,6 @@ CdmResponseType CdmEngine::GenerateRenewalRequest(
CdmResponseType CdmEngine::RenewKey(
const CdmSessionId& session_id,
bool is_key_system_init_data_present,
const CdmKeySystem& key_system,
const CdmInitData& init_data,
const CdmKeyResponse& key_data) {
LOGI("CdmEngine::RenewKey");
@@ -270,14 +250,6 @@ CdmResponseType CdmEngine::RenewKey(
return KEY_ERROR;
}
if (is_key_system_init_data_present) {
// TODO(edwinwong, rfrias): validate key_system has not changed
}
if (is_key_system_init_data_present) {
// TODO(edwinwong, rfrias): validate init_data has not changed
}
if (key_data.empty()) {
LOGE("CdmEngine::RenewKey: no key_data");
return KEY_ERROR;
@@ -357,15 +329,9 @@ CdmResponseType CdmEngine::QueryKeyControlInfo(
return iter->second->QueryKeyControlInfo(key_info);
}
void CdmEngine::CleanupProvisioingSessions(
CdmSession* cdm_session,
CryptoEngine* crypto_engine,
const CdmSessionId& cdm_session_id) {
if (NULL == cdm_session) return;
cdm_session->DestroySession();
if (crypto_engine) crypto_engine->DestroySession(cdm_session_id);
delete cdm_session;
void CdmEngine::CleanupProvisioningSession(const CdmSessionId& cdm_session_id) {
CloseSession(cdm_session_id);
provisioning_session_ = NULL;
}
/*
@@ -391,21 +357,16 @@ void CdmEngine::ComposeJsonRequest(
// performs base64 encoding for message
std::vector<uint8_t> message_vector(message.begin(), message.end());
std::string message_b64 = Base64SafeEncode(message_vector);
LOGD("b64 serialized req:\r\n%s", message_b64.data());
// performs base64 encoding for signature
std::vector<uint8_t> signature_vector(signature.begin(), signature.end());
std::string signature_b64 = Base64SafeEncode(signature_vector);
LOGD("b64 signature:\r\n%s", signature_b64.data());
// TODO(edwinwong): write a function to escape JSON output
request->assign("{'signedRequest':{'message':'");
request->append(message_b64);
request->append("','signature':'");
request->append(signature_b64);
request->append("'}}");
LOGD("json str:\r\n%s", request->c_str());
}
/*
@@ -419,7 +380,12 @@ CdmResponseType CdmEngine::GetProvisioningRequest(
CdmProvisioningRequest* request,
std::string* default_url) {
if (!request || !default_url) {
LOGE("CdmEngine::GetProvisioningRequest: invalid input parameters");
LOGE("GetProvisioningRequest: invalid input parameters");
return UNKNOWN_ERROR;
}
if (provisioning_session_) {
LOGE("GetProvisioningRequest: duplicate provisioning request?");
return UNKNOWN_ERROR;
}
@@ -430,19 +396,19 @@ CdmResponseType CdmEngine::GetProvisioningRequest(
//
CdmSession* cdm_session = new CdmSession();
if (!cdm_session) {
LOGE("CdmEngine::GetProvisioningRequest: fails to create a cdm session");
LOGE("GetProvisioningRequest: fails to create a cdm session");
return UNKNOWN_ERROR;
}
if (cdm_session->session_id().empty()) {
LOGE("CdmEngine::GetProvisioningRequest: fails to generate session ID");
LOGE("GetProvisioningRequest: fails to generate session ID");
delete cdm_session;
return UNKNOWN_ERROR;
}
CryptoEngine* crypto_engine = CryptoEngine::GetInstance();
if (!crypto_engine) {
LOGE("CdmEngine::GetProvisioningRequest: fails to create a crypto engine");
LOGE("GetProvisioningRequest: fails to create a crypto engine");
delete cdm_session;
return UNKNOWN_ERROR;
}
@@ -450,11 +416,13 @@ CdmResponseType CdmEngine::GetProvisioningRequest(
CdmSessionId cdm_session_id = cdm_session->session_id();
CryptoSession* crypto_session = crypto_engine->CreateSession(cdm_session_id);
if (!crypto_session) {
LOGE("CdmEngine::GetProvisioningRequest: fails to create a crypto session");
LOGE("GetProvisioningRequest: fails to create a crypto session");
delete cdm_session;
return UNKNOWN_ERROR;
}
provisioning_session_ = cdm_session;
//
//---------------------------------------------------------------------------
// Prepares device provisioning request.
@@ -464,20 +432,24 @@ CdmResponseType CdmEngine::GetProvisioningRequest(
client_id->set_type(ClientIdentification::KEYBOX);
std::string token;
if (!crypto_engine->GetToken(&token)) {
LOGE("CdmEngine::GetProvisioningRequest: fails to get token");
CleanupProvisioingSessions(cdm_session, crypto_engine, cdm_session_id);
LOGE("GetProvisioningRequest: fails to get token");
CleanupProvisioningSession(cdm_session_id);
return UNKNOWN_ERROR;
}
client_id->set_token(token);
uint32_t nonce;
if (!crypto_session->GenerateNonce(&nonce)) {
LOGE("CdmEngine::GetProvisioningRequest: fails to generate a nonce");
LOGE("GetProvisioningRequest: fails to generate a nonce");
crypto_engine->DestroySession(cdm_session_id);
CleanupProvisioingSessions(cdm_session, crypto_engine, cdm_session_id);
CleanupProvisioningSession(cdm_session_id);
return UNKNOWN_ERROR;
}
provisioning_request.set_nonce(UintToString(nonce));
// 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);
// Serializes the provisioning request.
std::string serialized_request;
@@ -487,27 +459,23 @@ CdmResponseType CdmEngine::GetProvisioningRequest(
std::string request_signature;
if (!crypto_session->PrepareRequest(serialized_request, &request_signature)) {
request->clear();
CleanupProvisioingSessions(cdm_session, crypto_engine, cdm_session_id);
CleanupProvisioningSession(cdm_session_id);
return UNKNOWN_ERROR;
}
if (request_signature.empty()) {
request->clear();
CleanupProvisioingSessions(cdm_session, crypto_engine, cdm_session_id);
CleanupProvisioningSession(cdm_session_id);
return UNKNOWN_ERROR;
}
// converts request into JSON string
ComposeJsonRequest(serialized_request, request_signature, request);
// TODO(edwinwong): returns default provisioning server url
default_url->clear();
static const std::string kDefaultProvisioningServerUrl =
"http://www-googleapis-test.sandbox.google.com/certificateprovisioning/v1/devicecertificates/create";
default_url->assign(kDefaultProvisioningServerUrl);
//
//---------------------------------------------------------------------------
// Closes the cdm session.
//
CleanupProvisioingSessions(cdm_session, crypto_engine, cdm_session_id);
return NO_ERROR;
}
@@ -537,12 +505,6 @@ bool CdmEngine::ParseJsonResponse(
size_t b64_string_size = end - start - start_substr.length();
b64_string.assign(json_str, start + start_substr.length(), b64_string_size);
// Due to the size of the message, debug string cannot dump out the
// entire string. Dump the beginning and end to verify instead.
LOGD("size=%u, b64_string start=%s, end=%s", b64_string.length(),
b64_string.substr(0, 16).c_str(),
b64_string.substr(b64_string_size - 16).c_str());
}
// Decodes base64 substring and returns it in *result
@@ -587,32 +549,23 @@ CdmResponseType CdmEngine::HandleProvisioningResponse(
//
//---------------------------------------------------------------------------
// First creates a cdm session, then creates a crypto session.
// Creates a crypto session using provisioning_session_.
//
CdmSession* cdm_session = new CdmSession();
if (!cdm_session) {
LOGE("CdmEngine::HandleProvisioningResponse: fails to create a cdm session");
return UNKNOWN_ERROR;
}
if (cdm_session->session_id().empty()) {
LOGE("CdmEngine::HandleProvisioningResponse: fails to generate session ID");
delete cdm_session;
if (!provisioning_session_) {
LOGE("HandleProvisioningResponse: invalid provisioning session");
return UNKNOWN_ERROR;
}
CryptoEngine* crypto_engine = CryptoEngine::GetInstance();
if (!crypto_engine) {
LOGE("CdmEngine::HandleProvisioningResponse: fails to create a crypto engine");
delete cdm_session;
LOGE("HandleProvisioningResponse: fails to create a crypto engine");
return UNKNOWN_ERROR;
}
CdmSessionId cdm_session_id = cdm_session->session_id();
CryptoSession* crypto_session = crypto_engine->CreateSession(cdm_session_id);
CdmSessionId cdm_session_id = provisioning_session_->session_id();
CryptoSession* crypto_session = crypto_engine->FindSession(cdm_session_id);
if (!crypto_session) {
LOGE("CdmEngine::HandleProvisioningResponse: fails to create a crypto session");
delete cdm_session;
LOGE("HandleProvisioningResponse: fails to find %s", cdm_session_id.c_str());
return UNKNOWN_ERROR;
}
@@ -621,36 +574,50 @@ CdmResponseType CdmEngine::HandleProvisioningResponse(
// the provisioing request's input). Validate provisioning response and
// stores private device RSA key and certificate.
ProvisioningResponse provisioning_response;
if (!provisioning_response.ParseFromString(signed_message)) {
LOGE("CdmEngine::HandleProvisioningResponse: fails to parse signed message");
CleanupProvisioingSessions(cdm_session, crypto_engine, cdm_session_id);
return UNKNOWN_ERROR;
}
const std::string& enc_rsa_key = provisioning_response.device_rsa_key();
const std::string& enc_rsa_key_iv = provisioning_response.device_rsa_key_iv();
uint32_t nonce = strtoul(provisioning_response.nonce().data(), NULL, 10);
std::vector<uint8_t> wrapped_rsa_key;
size_t wrapped_rsa_key_length = 0;
if (!crypto_session->RewrapDeviceRSAKey(response,
&nonce,
reinterpret_cast<const uint8_t*>(enc_rsa_key.data()),
enc_rsa_key.length(),
reinterpret_cast<const uint8_t*>(enc_rsa_key_iv.data()),
&wrapped_rsa_key[0],
&wrapped_rsa_key_length)) {
LOGE("CdmEngine::HandleProvisioningResponse: RewrapDeviceRSAKey fails");
CleanupProvisioingSessions(cdm_session, crypto_engine, cdm_session_id);
LOGE("HandleProvisioningResponse: fails to parse signed message");
CleanupProvisioningSession(cdm_session_id);
return UNKNOWN_ERROR;
}
if (!provisioning_response.has_device_rsa_key()) {
LOGE("HandleProvisioningResponse: invalid response - key not found");
CleanupProvisioningSession(cdm_session_id);
return UNKNOWN_ERROR;
}
const std::string& enc_rsa_key = provisioning_response.device_rsa_key();
const std::string& rsa_key_iv = provisioning_response.device_rsa_key_iv();
const std::string& nonce = provisioning_response.nonce();
const int kRsaKeySize = 256;
size_t wrapped_rsa_key_length = kRsaKeySize + enc_rsa_key.length();
std::vector<uint8_t> wrapped_rsa_key;
wrapped_rsa_key.resize(wrapped_rsa_key_length);
if (!crypto_session->RewrapDeviceRSAKey(signed_message,
signature,
nonce.data(),
enc_rsa_key,
enc_rsa_key.size(),
rsa_key_iv,
&wrapped_rsa_key[0],
&wrapped_rsa_key_length)) {
LOGE("HandleProvisioningResponse: RewrapDeviceRSAKey fails");
CleanupProvisioningSession(cdm_session_id);
return UNKNOWN_ERROR;
}
// TODO(edwinwong): stores private device RSA key and cert
const std::string& device_certificate = provisioning_response.device_certificate();
std::string the_wrapped_rsa_key(wrapped_rsa_key.begin(), wrapped_rsa_key.end());
DeviceFiles::StoreCertificate(device_certificate, the_wrapped_rsa_key);
//
//---------------------------------------------------------------------------
// Closes the cdm session.
//
CleanupProvisioingSessions(cdm_session, crypto_engine, cdm_session_id);
CleanupProvisioningSession(cdm_session_id);
return NO_ERROR;
}

View File

@@ -8,6 +8,7 @@
#include "clock.h"
#include "crypto_engine.h"
#include "device_files.h"
#include "log.h"
#include "properties.h"
#include "string_conversions.h"
@@ -64,7 +65,8 @@ CdmResponseType CdmSession::GenerateKeyRequest(
const CdmInitData& pssh_data,
const CdmLicenseType license_type,
CdmAppParameterMap& app_parameters,
CdmKeyMessage* key_request) {
CdmKeyMessage* key_request,
std::string* server_url) {
if (!crypto_session_) {
LOGW("CdmSession::GenerateKeyRequest: Invalid crypto session");
return UNKNOWN_ERROR;
@@ -77,7 +79,8 @@ CdmResponseType CdmSession::GenerateKeyRequest(
if (license_received_) {
return Properties::require_explicit_renew_request() ?
UNKNOWN_ERROR : GenerateRenewalRequest(key_request);
UNKNOWN_ERROR : GenerateRenewalRequest(key_request,
server_url);
}
else {
if (Properties::use_certificates_as_identification()) {
@@ -88,7 +91,8 @@ CdmResponseType CdmSession::GenerateKeyRequest(
if (!license_parser_.PrepareKeyRequest(pssh_data,
license_type,
app_parameters,
key_request)) {
key_request,
server_url)) {
return KEY_ERROR;
} else {
return KEY_MESSAGE;
@@ -178,8 +182,10 @@ CdmResponseType CdmSession::Decrypt(bool is_encrypted,
// License renewal
// GenerateRenewalRequest() - Construct valid renewal request for the current
// session keys.
CdmResponseType CdmSession::GenerateRenewalRequest(CdmKeyMessage* key_request) {
if (!license_parser_.PrepareKeyRenewalRequest(key_request)) {
CdmResponseType CdmSession::GenerateRenewalRequest(CdmKeyMessage* key_request,
std::string* server_url) {
if (!license_parser_.PrepareKeyRenewalRequest(key_request,
server_url)) {
return KEY_ERROR;
} else {
return KEY_MESSAGE;
@@ -207,8 +213,8 @@ CdmSessionId CdmSession::GenerateSessionId() {
bool CdmSession::LoadDeviceCertificate(std::string* certificate,
std::string* wrapped_key) {
// TODO(edwingwong,rfrias): Need to read in the private key
return false;
return DeviceFiles::RetrieveCertificate(certificate,
wrapped_key);
}
bool CdmSession::AttachEventListener(WvCdmEventListener* listener) {

View File

@@ -16,6 +16,7 @@
#include "wv_cdm_constants.h"
namespace {
const uint32_t kMaxSignatureBufLength = 256;
// Encode unsigned integer into a big endian formatted string
std::string EncodeUint32(unsigned int u) {
std::string s;
@@ -357,8 +358,8 @@ bool CryptoSession::GenerateDerivedKeys(const std::string& message,
bool CryptoSession::GenerateSignature(const std::string& message,
std::string* signature) {
LOGV("GenerateSignature: id=%ld", (uint32_t) oec_session_id_);
uint8_t signature_buf[32];
uint32_t length = 32;
uint8_t signature_buf[kMaxSignatureBufLength];
size_t length = kMaxSignatureBufLength;
OEMCryptoResult sts;
if (Properties::use_certificates_as_identification()) {
sts = OEMCrypto_GenerateRSASignature(
@@ -475,10 +476,11 @@ bool CryptoSession::SetDestinationBufferType() {
}
bool CryptoSession::RewrapDeviceRSAKey(const std::string& message,
const uint32_t* nonce,
const uint8_t* enc_rsa_key,
const std::string& signature,
const std::string& nonce,
const std::string& enc_rsa_key,
size_t enc_rsa_key_length,
const uint8_t* enc_rsa_key_iv,
const std::string& rsa_key_iv,
uint8_t* wrapped_rsa_key,
size_t* wrapped_rsa_key_length) {
LOGV("CryptoSession::RewrapDeviceRSAKey: Lock+++");
@@ -487,31 +489,28 @@ bool CryptoSession::RewrapDeviceRSAKey(const std::string& message,
LOGV("crypto session id=%ld", static_cast<uint32_t>(oec_session_id_));
// HMAC-SHA256 signature
uint8_t signature[kSignatureSize];
size_t signature_length = kSignatureSize;
OEMCryptoResult status = OEMCrypto_GenerateSignature(
oec_session_id_,
reinterpret_cast<const uint8_t*>(message.data()),
message.size(),
signature,
&signature_length);
if (OEMCrypto_SUCCESS != status) {
LOGE("CryptoSession::RewrapDeviceRSAKey: GenerateSiganture failed");
return false;
const uint8_t* signed_msg = reinterpret_cast<const uint8_t*>(message.data());
const uint8_t* msg_rsa_key = NULL;
const uint8_t* msg_rsa_key_iv = NULL;
const uint32_t* msg_nonce = NULL;
if (enc_rsa_key.size() >= MAC_KEY_SIZE && rsa_key_iv.size() >= KEY_IV_SIZE) {
msg_rsa_key = signed_msg + GetOffset(message, enc_rsa_key);
msg_rsa_key_iv = signed_msg + GetOffset(message, rsa_key_iv);
msg_nonce = reinterpret_cast<const uint32_t*>(signed_msg + GetOffset(message, nonce));
}
status = OEMCrypto_RewrapDeviceRSAKey(
OEMCryptoResult status = OEMCrypto_RewrapDeviceRSAKey(
oec_session_id_,
reinterpret_cast<const uint8_t*>(message.data()), message.length(),
signature, signature_length,
nonce,
enc_rsa_key, enc_rsa_key_length,
enc_rsa_key_iv,
signed_msg, message.size(),
reinterpret_cast<const uint8_t*>(signature.data()), signature.size(),
msg_nonce,
msg_rsa_key, enc_rsa_key_length,
msg_rsa_key_iv,
wrapped_rsa_key,
wrapped_rsa_key_length);
if (OEMCrypto_SUCCESS != status) {
LOGE("OEMCrypto_RewrapDeviceRSAKey fails with %d", status);
return false;
}
return true;

View File

@@ -0,0 +1,196 @@
// Copyright 2013 Google Inc. All Rights Reserved.
#include "device_files.h"
#include <cstring>
#include <string>
#include <string.h>
#include "device_files.pb.h"
#include "file_store.h"
#include "log.h"
#include "openssl/sha.h"
namespace wvcdm {
// TODO(rfrias): Make this work for non-unix paths
const char* DeviceFiles::kBasePath = "/data/drm";
const char* DeviceFiles::kIdmPath = "/data/drm/IDM";
const char* DeviceFiles::kCencPath = "/data/drm/IDM/CENC";
const char* DeviceFiles::kDeviceCertificateFileName = "cert.bin";
// Protobuf generated classes.
using video_widevine_client::sdk::DeviceCertificate;
using video_widevine_client::sdk::HashedFile;
bool DeviceFiles::StoreCertificate(const std::string& certificate,
const std::string& wrapped_private_key) {
// Fill in file information
video_widevine_client::sdk::File file;
file.set_type(video_widevine_client::sdk::File::DEVICE_CERTIFICATE);
file.set_version(video_widevine_client::sdk::File::VERSION_1);
DeviceCertificate *device_certificate = file.mutable_device_certificate();
device_certificate->set_certificate(certificate);
device_certificate->set_wrapped_private_key(wrapped_private_key);
std::string serialized_string;
file.SerializeToString(&serialized_string);
// calculate SHA hash
std::string hash;
if (!Hash(serialized_string, &hash)) {
LOGW("DeviceFiles::StoreCertificate: Hash computation failed");
return false;
}
// File in hashed file data
HashedFile hashed_file;
hashed_file.set_file(serialized_string);
hashed_file.set_hash(hash);
hashed_file.SerializeToString(&serialized_string);
return StoreFile(kDeviceCertificateFileName, serialized_string);
}
bool DeviceFiles::RetrieveCertificate(std::string* certificate,
std::string* wrapped_private_key) {
std::string serialized_hashed_file;
if (!RetrieveFile(kDeviceCertificateFileName, &serialized_hashed_file))
return false;
HashedFile hashed_file;
if (!hashed_file.ParseFromString(serialized_hashed_file)) {
LOGW("DeviceFiles::RetrieveCertificate: Unable to parse hash file");
return false;
}
std::string hash;
if (!Hash(hashed_file.file(), &hash)) {
LOGW("DeviceFiles::RetrieveCertificate: Hash computation failed");
return false;
}
if (hash.compare(hashed_file.hash())) {
LOGW("DeviceFiles::RetrieveCertificate: Hash mismatch");
return false;
}
video_widevine_client::sdk::File file;
if (!file.ParseFromString(hashed_file.file())) {
LOGW("DeviceFiles::RetrieveCertificate: Unable to parse file");
return false;
}
if (file.type() != video_widevine_client::sdk::File::DEVICE_CERTIFICATE) {
LOGW("DeviceFiles::RetrieveCertificate: Incorrect file type");
return false;
}
if (file.version() != video_widevine_client::sdk::File::VERSION_1) {
LOGW("DeviceFiles::RetrieveCertificate: Incorrect file version");
return false;
}
if (!file.has_device_certificate()) {
LOGW("DeviceFiles::RetrieveCertificate: Certificate not present");
return false;
}
DeviceCertificate device_certificate = file.device_certificate();
*certificate = device_certificate.certificate();
*wrapped_private_key = device_certificate.wrapped_private_key();
return true;
}
bool DeviceFiles::Hash(const std::string& data, std::string* hash) {
if (!hash)
return false;
hash->resize(SHA256_DIGEST_LENGTH);
SHA256_CTX sha256;
SHA256_Init(&sha256);
SHA256_Update(&sha256, data.data(), data.size());
SHA256_Final(reinterpret_cast<unsigned char*>(const_cast<char*>(hash->data())), &sha256);
return true;
}
bool DeviceFiles::StoreFile(const char* name, const std::string& data) {
if (!name)
return false;
if (!File::IsDirectory(kCencPath)) {
if (!File::CreateDirectory(kCencPath))
return false;
}
std::string path = GetPath(kCencPath, name);
File file(path, File::kCreate | File::kTruncate | File::kBinary);
if (file.IsBad()) {
LOGW("DeviceFiles::StoreFile: File open failed: %s", path.c_str());
return false;
}
ssize_t bytes = file.Write(data.data(), data.size());
file.Close();
if (bytes != static_cast<ssize_t>(data.size())) {
LOGW("DeviceFiles::StoreFile: write failed: %d %d", data.size(), bytes);
return false;
}
return true;
}
bool DeviceFiles::RetrieveFile(const char* name, std::string* data) {
if (!data)
return false;
std::string path = GetPath(kCencPath, name);
if (!File::Exists(path)) {
LOGW("DeviceFiles::RetrieveFile: %s does not exist", path.c_str());
return false;
}
ssize_t bytes = File::FileSize(path);
if (bytes <= 0) {
LOGW("DeviceFiles::RetrieveFile: File size invalid: %d", path.c_str());
return false;
}
File file(path, File::kReadOnly | File::kBinary);
if (file.IsBad()) {
LOGW("DeviceFiles::RetrieveFile: File open failed: %s", path.c_str());
return false;
}
data->resize(bytes);
bytes = file.Read(reinterpret_cast<void*>(const_cast<char*>(data->data())),
data->size());
if (bytes != static_cast<ssize_t>(data->size())) {
LOGW("DeviceFiles::StoreFile: write failed: %d %d", data->size(), bytes);
return false;
}
return true;
}
std::string DeviceFiles::GetPath(const char* dir, const char * filename) {
// TODO(rfrias): Make this work for non-unix paths
std::string path = dir;
path += "/";
path += filename;
return path;
}
}

View File

@@ -0,0 +1,47 @@
// ----------------------------------------------------------------------------
// device_files.proto
// ----------------------------------------------------------------------------
// Copyright 2013 Google Inc. All Rights Reserved.
//
// Description:
// Format of various files stored at the device.
//
syntax = "proto2";
package video_widevine_client.sdk;
// need this if we are using libprotobuf-cpp-2.3.0-lite
option optimize_for = LITE_RUNTIME;
message DeviceCertificate {
optional bytes certificate = 1;
optional bytes wrapped_private_key = 2;
}
message License {
optional bytes key_set_id = 1;
optional bytes pssh_data = 2;
optional bytes license_request = 3;
optional bytes license = 4;
}
message File {
enum FileType {
DEVICE_CERTIFICATE = 1;
LICENSE = 2;
}
enum FileVersion {
VERSION_1 = 1;
}
optional FileType type = 1;
optional FileVersion version = 2 [default = VERSION_1];
optional DeviceCertificate device_certificate = 3;
repeated License licenses = 4;
}
message HashedFile {
optional bytes file = 1;
optional bytes hash = 2;
}

View File

@@ -91,7 +91,8 @@ bool CdmLicense::Init(const std::string& token,
bool CdmLicense::PrepareKeyRequest(const CdmInitData& init_data,
const CdmLicenseType license_type,
CdmAppParameterMap& app_parameters,
CdmKeyMessage* signed_request) {
CdmKeyMessage* signed_request,
std::string* server_url) {
if (!session_ ||
token_.empty()) {
return false;
@@ -104,6 +105,10 @@ bool CdmLicense::PrepareKeyRequest(const CdmInitData& init_data,
LOGE("CdmLicense::PrepareKeyRequest : No signed request provided.");
return false;
}
if (!server_url) {
LOGE("CdmLicense::PrepareKeyRequest : No server url provided.");
return false;
}
// TODO(gmorgan): Request ID owned by session?
std::string request_id;
@@ -191,6 +196,9 @@ bool CdmLicense::PrepareKeyRequest(const CdmInitData& init_data,
std::string serialized_license_req;
license_request.SerializeToString(&serialized_license_req);
if (Properties::use_certificates_as_identification())
key_request_ = serialized_license_req;
// Derive signing and encryption keys and construct signature.
std::string license_request_signature;
if (!session_->PrepareRequest(serialized_license_req,
@@ -212,13 +220,12 @@ bool CdmLicense::PrepareKeyRequest(const CdmInitData& init_data,
signed_message.SerializeToString(signed_request);
if (Properties::use_certificates_as_identification())
key_request_ = *signed_request;
*server_url = server_url_;
return true;
}
bool CdmLicense::PrepareKeyRenewalRequest(CdmKeyMessage* signed_request) {
bool CdmLicense::PrepareKeyRenewalRequest(CdmKeyMessage* signed_request,
std::string* server_url) {
if (!session_) {
return false;
}
@@ -226,6 +233,10 @@ bool CdmLicense::PrepareKeyRenewalRequest(CdmKeyMessage* signed_request) {
LOGE("CdmLicense::PrepareKeyRenewalRequest : No signed request provided.");
return false;
}
if (!server_url) {
LOGE("CdmLicense::PrepareKeyRenewalRequest : No server url provided.");
return false;
}
LicenseRequest license_request;
license_request.set_type(LicenseRequest::RENEWAL);
@@ -263,7 +274,7 @@ bool CdmLicense::PrepareKeyRenewalRequest(CdmKeyMessage* signed_request) {
signed_message.set_msg(serialized_license_req);
signed_message.SerializeToString(signed_request);
*server_url = server_url_;
return true;
}
@@ -332,6 +343,10 @@ CdmResponseType CdmLicense::HandleKeyResponse(
return KEY_ERROR;
}
if (license.policy().has_renewal_server_url()) {
server_url_ = license.policy().renewal_server_url();
}
policy_engine_->SetLicense(license);
if (session_->LoadKeys(signed_response.msg(),
@@ -378,6 +393,11 @@ CdmResponseType CdmLicense::HandleKeyRenewalResponse(
// This is the normal case.
license_id_.CopyFrom(license.id());
if (license.policy().has_renewal_server_url() &&
license.policy().renewal_server_url().size() > 0) {
server_url_ = license.policy().renewal_server_url();
}
policy_engine_->UpdateLicense(license);
std::vector<CryptoKey> key_array = ExtractContentKeys(license);

View File

@@ -1,4 +1,4 @@
// Copyright 2012 Google Inc. All Rights Reserved.
// Copyright 2013 Google Inc. All Rights Reserved.
#include "string_conversions.h"
@@ -12,14 +12,41 @@
#include "log.h"
namespace {
// Helper for Base64SafeDecode()
char B64ToBin(char inch) {
if (inch >= 'A' && inch <= 'Z') return inch - 'A';
if (inch >= 'a' && inch <= 'z') return inch - 'a' + 26;
if (inch >= '0' && inch <= '9') return inch - '0' + 52;
if (inch == '-') return 62;
// if (inch == '_')
return 63;
/*
* Returns a 8-bit char that is mapped to the 6-bit base64 in_ch.
*
* Extracted from http://www.ietf.org/rfc/rfc3548.txt.
*
The "URL and Filename safe" Base 64 Alphabet
Value Encoding Value Encoding Value Encoding Value Encoding
0 A 17 R 34 i 51 z
1 B 18 S 35 j 52 0
2 C 19 T 36 k 53 1
3 D 20 U 37 l 54 2
4 E 21 V 38 m 55 3
5 F 22 W 39 n 56 4
6 G 23 X 40 o 57 5
7 H 24 Y 41 p 58 6
8 I 25 Z 42 q 59 7
9 J 26 a 43 r 60 8
10 K 27 b 44 s 61 9
11 L 28 c 45 t 62 - (minus)
12 M 29 d 46 u 63 _
13 N 30 e 47 v (underline)
14 O 31 f 48 w
15 P 32 g 49 x
16 Q 33 h 50 y (pad) =
*/
char B64ToBin(char in_ch) {
if (in_ch >= 'A' && in_ch <= 'Z') return in_ch - 'A';
if (in_ch >= 'a' && in_ch <= 'z') return in_ch - 'a' + 26;
if (in_ch >= '0' && in_ch <= '9') return in_ch - '0' + 52;
if (in_ch == '-') return 62;
if (in_ch == '_') return 63;
// arbitrary delimiter not in Base64 encoded alphabet, do not pick 0
return '?';
}
}
@@ -75,10 +102,13 @@ std::string b2a_hex(const std::string& byte) {
byte.length());
}
// Filename-friendly base64 encoding (RFC4648).
// Filename-friendly base64 encoding (RFC4648), commonly referred as
// Base64WebSafeEncode.
// This is the encoding required by GooglePlay for certain
// license server transactions. It is also used for logging
// certain strings.
// The difference between web safe encoding vs regular encoding is that
// the web safe version replaces '+' with '-' and '/' with '_'.
std::string Base64SafeEncode(const std::vector<uint8_t>& bin_input) {
static const char kBase64Chars[] =
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
@@ -87,9 +117,13 @@ std::string Base64SafeEncode(const std::vector<uint8_t>& bin_input) {
if (bin_input.empty()) {
return std::string();
}
int in_size = bin_input.size();
int rup = ((in_size % 3) != 0) ? 1 : 0;
int out_size = ((in_size * 4) / 3) + rup;
int final_quantum_in_bytes = in_size % 3;
int full_in_chunks = in_size / 3;
int out_size = full_in_chunks * 4;
if (final_quantum_in_bytes) out_size += 4;
std::string b64_output(out_size, '\0');
int in_index = 0;
int out_index = 0;
@@ -98,7 +132,7 @@ std::string Base64SafeEncode(const std::vector<uint8_t>& bin_input) {
static const unsigned long kInMask = 0xff;
static const unsigned long kOutMask = 0x3f;
while (in_index < in_size) {
for (int i = 0; i < full_in_chunks; ++i) {
// up to 3 bytes (0..255) in
buffer = (bin_input.at(in_index) & kInMask);
buffer <<= 8;
@@ -106,6 +140,7 @@ std::string Base64SafeEncode(const std::vector<uint8_t>& bin_input) {
buffer <<= 8;
buffer |= (++in_index >= in_size) ? 0 : (bin_input.at(in_index) & kInMask);
++in_index;
// up to 4 bytes (0..63) out
out_cc = (buffer >> 18) & kOutMask;
b64_output.at(out_index) = kBase64Chars[out_cc];
@@ -123,10 +158,49 @@ std::string Base64SafeEncode(const std::vector<uint8_t>& bin_input) {
b64_output.at(out_index) = kBase64Chars[out_cc];
++out_index;
}
if (final_quantum_in_bytes) {
switch(final_quantum_in_bytes) {
case 1: {
// reads 24-bits data, which is made up of one 8-bits char
buffer = (bin_input.at(in_index++) & kInMask);
buffer <<= 16;
// writes two 6-bits chars followed by two '=' padding char
out_cc = (buffer >> 18) & kOutMask;
b64_output.at(out_index++) = kBase64Chars[out_cc];
out_cc = (buffer >> 12) & kOutMask;
b64_output.at(out_index++) = kBase64Chars[out_cc];
b64_output.at(out_index++) = '=';
b64_output.at(out_index) = '=';
break;
}
case 2: {
// reads 24-bits data, which is made up of two 8-bits chars
buffer = (bin_input.at(in_index++) & kInMask);
buffer <<= 8;
buffer |= (bin_input.at(in_index++) & kInMask);
buffer <<= 8;
// writes three 6-bits chars followed by one '=' padding char
out_cc = (buffer >> 18) & kOutMask;
b64_output.at(out_index++) = kBase64Chars[out_cc];
out_cc = (buffer >> 12) & kOutMask;
b64_output.at(out_index++) = kBase64Chars[out_cc];
out_cc = (buffer >> 6) & kOutMask;
b64_output.at(out_index++) = kBase64Chars[out_cc];
b64_output.at(out_index) = '=';
break;
}
default:
break;
}
}
return b64_output;
}
// Decode for Filename-friendly base64 encoding (RFC4648).
// Decode for Filename-friendly base64 encoding (RFC4648), commonly referred
// as Base64WebSafeDecode.
// This is the encoding required by GooglePlay for certain
// license server transactions. It is also used for logging
// certain strings.
@@ -134,9 +208,9 @@ std::vector<uint8_t> Base64SafeDecode(const std::string& b64_input) {
if (b64_input.empty()) {
return std::vector<uint8_t>();
}
int in_size = b64_input.size();
// out_size should be an integral number of bytes, assuming correct encode
int out_size = ((in_size * 3) / 4);
int out_size = in_size;
std::vector<uint8_t> bin_output(out_size, '\0');
int in_index = 0;
int out_index = 0;
@@ -144,7 +218,14 @@ std::vector<uint8_t> Base64SafeDecode(const std::string& b64_input) {
unsigned char out_cc;
static const unsigned long kOutMask = 0xff;
while (in_index < in_size) {
int counter = 0;
size_t delimiter_pos = b64_input.rfind('=');
if (delimiter_pos != std::string::npos) {
// Special case for partial last quantum indicated by '='
// at the end of encoded input.
counter = 1;
}
for (; counter < (in_size / 4); ++counter) {
// up to 4 bytes (0..63) in
buffer = B64ToBin(b64_input.at(in_index));
buffer <<= 6;
@@ -167,6 +248,37 @@ std::vector<uint8_t> Base64SafeDecode(const std::string& b64_input) {
bin_output.at(out_index) = out_cc;
++out_index;
}
if (delimiter_pos != std::string::npos) {
// it is either 2 chars plus 2 '=' or 3 chars plus one '='
buffer = B64ToBin(b64_input.at(in_index++));
buffer <<= 6;
buffer |= B64ToBin(b64_input.at(in_index++));
buffer <<= 6;
char special_char = b64_input.at(in_index++);
if ('=' == special_char) {
// we have 2 chars and 2 '='
buffer <<= 6;
out_cc = (buffer >> 16) & kOutMask;
bin_output.at(out_index++) = out_cc;
out_cc = (buffer >> 8) & kOutMask;
bin_output.at(out_index) = out_cc;
} else {
// we have 3 chars and 1 '='
buffer |= B64ToBin(special_char);
buffer <<= 6;
buffer |= B64ToBin(b64_input.at(in_index));
out_cc = (buffer >> 16) & kOutMask;
bin_output.at(out_index++) = out_cc;
out_cc = (buffer >> 8) & kOutMask;
bin_output.at(out_index++) = out_cc;
out_cc = buffer & kOutMask;
bin_output.at(out_index) = out_cc;
}
}
// adjust vector to reflect true size
bin_output.resize(out_index);
return bin_output;
}