411 lines
14 KiB
C++
411 lines
14 KiB
C++
// Copyright 2013 Google Inc. All Rights Reserved.
|
|
|
|
#include "wv_content_decryption_module.h"
|
|
|
|
#include <iostream>
|
|
#include <string.h>
|
|
|
|
#include "cdm_client_property_set.h"
|
|
#include "content_decryption_module.h"
|
|
|
|
#include "log.h"
|
|
#include "OEMCryptoCENC.h"
|
|
#include "properties.h"
|
|
|
|
#include "wv_cdm_constants.h"
|
|
#include "wv_cdm_types.h"
|
|
#include "wv_cdm_version.h"
|
|
|
|
void INITIALIZE_CDM_MODULE() {}
|
|
|
|
void DeinitializeCdmModule() {}
|
|
|
|
void* CreateCdmInstance(int cdm_interface_version, const char* key_system,
|
|
int key_system_size, GetCdmHostFunc get_cdm_host_func,
|
|
void* user_data) {
|
|
if (cdm_interface_version != cdm::kCdmInterfaceVersion) return NULL;
|
|
|
|
cdm::Host* host = static_cast<cdm::Host*>(
|
|
get_cdm_host_func(cdm::kHostInterfaceVersion, user_data));
|
|
|
|
if (!host) return NULL;
|
|
|
|
return static_cast<cdm::ContentDecryptionModule*>(
|
|
new wvcdm::WvContentDecryptionModule(host));
|
|
}
|
|
|
|
int GetCdmVersion() { return cdm::kCdmInterfaceVersion; }
|
|
|
|
namespace {
|
|
static const std::string kWvCdmVersionString(WV_CDM_VERSION);
|
|
|
|
const int kCdmPolicyTimerDurationSeconds = 1;
|
|
const int kCdmPolicyTimerCancel = 0;
|
|
|
|
// The iso spec only uses the lower 8 bytes of the iv as
|
|
// the counter.
|
|
const uint32_t kCencIvSize = 8;
|
|
const uint32_t kIvSize = 16;
|
|
|
|
bool Ctr128Add(size_t block_count, uint8_t* counter) {
|
|
if (NULL == counter)
|
|
return false;
|
|
if (0 == block_count)
|
|
return true;
|
|
uint8_t carry = 0;
|
|
uint8_t n = kIvSize - 1;
|
|
while (n >= kCencIvSize) {
|
|
uint32_t temp = block_count & 0xff;
|
|
temp += counter[n];
|
|
temp += carry;
|
|
counter[n] = temp & 0xff;
|
|
carry = (temp & 0x100) ? 1 : 0;
|
|
block_count = block_count >> 8;
|
|
n--;
|
|
if (!block_count && !carry) {
|
|
break;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
} // namespace
|
|
|
|
namespace wvcdm {
|
|
|
|
// An empty iv string signals that the frame is unencrypted.
|
|
bool IsBufferEncrypted(const cdm::InputBuffer& input_buffer) {
|
|
return input_buffer.iv_size != 0;
|
|
}
|
|
|
|
// cdm::ContentDecryptionModule implementation.
|
|
|
|
WvContentDecryptionModule::~WvContentDecryptionModule() {
|
|
DisablePolicyTimer();
|
|
}
|
|
|
|
cdm::Status WvContentDecryptionModule::GenerateKeyRequest(
|
|
const char* type, int type_size, const uint8_t* init_data,
|
|
int init_data_size) {
|
|
LOGI("Enter WvContentDecryptionModule::GenerateKeyRequest()");
|
|
CdmInitData init_data_internal(reinterpret_cast<const char*>(init_data),
|
|
init_data_size);
|
|
CdmKeyMessage key_request;
|
|
CdmSessionId session_id;
|
|
|
|
std::string security_level;
|
|
std::string privacy_mode;
|
|
kVectorBytes service_certificate;
|
|
|
|
host_->GetPlatformString("SecurityLevel", &security_level);
|
|
host_->GetPlatformString("PrivacyOn", &privacy_mode);
|
|
host_->GetPlatformByteArray("ServiceCertificate", &service_certificate);
|
|
|
|
property_set_.set_security_level(security_level);
|
|
property_set_.set_use_privacy_mode(privacy_mode == "True" ? 1 : 0 );
|
|
property_set_.set_service_certificate(service_certificate);
|
|
|
|
CdmResponseType result =
|
|
cdm_engine_.OpenSession("com.widevine.alpha", &property_set_, &session_id);
|
|
|
|
if (NEED_PROVISIONING == result) {
|
|
LOGI("Need to aquire a Device Certificate from the Provisioning Server");
|
|
return cdm::kNeedsDeviceCertificate;
|
|
}
|
|
|
|
if (NO_ERROR != result) return cdm::kSessionError;
|
|
|
|
if (!cdm_engine_.AttachEventListener(session_id, &host_event_listener_)) {
|
|
cdm_engine_.CloseSession(session_id);
|
|
return cdm::kSessionError;
|
|
}
|
|
|
|
CdmAppParameterMap app_parameters; // empty
|
|
CdmKeySetId key_set_id; // empty
|
|
std::string server_url;
|
|
|
|
result = cdm_engine_.GenerateKeyRequest(
|
|
session_id, key_set_id, init_data_internal, kLicenseTypeStreaming,
|
|
app_parameters, &key_request, &server_url);
|
|
if (KEY_MESSAGE != result) {
|
|
cdm_engine_.CloseSession(session_id);
|
|
return cdm::kSessionError;
|
|
}
|
|
|
|
host_->SendKeyMessage(session_id.data(), session_id.length(),
|
|
key_request.data(), key_request.length(),
|
|
server_url.data(), server_url.length());
|
|
|
|
return cdm::kSuccess;
|
|
}
|
|
|
|
cdm::Status WvContentDecryptionModule::AddKey(const char* session_id,
|
|
int session_id_size,
|
|
const uint8_t* key, int key_size,
|
|
const uint8_t* key_id,
|
|
int key_id_size) {
|
|
LOGI("Enter WvContentDecryptionModule::AddKey()\n");
|
|
CdmSessionId session_id_internal(session_id, session_id_size);
|
|
CdmKeyResponse key_data((const char*)key, key_size);
|
|
CdmKeySetId key_set_id;
|
|
|
|
CdmResponseType response = cdm_engine_.AddKey(session_id_internal,
|
|
key_data, &key_set_id);
|
|
|
|
if (response == KEY_ADDED) {
|
|
EnablePolicyTimer();
|
|
return cdm::kSuccess;
|
|
} else {
|
|
return cdm::kSessionError;
|
|
}
|
|
|
|
}
|
|
|
|
cdm::Status WvContentDecryptionModule::CancelKeyRequest(const char* session_id,
|
|
int session_id_size) {
|
|
LOGI("Enter WvContentDecryptionModule::CancelKeyRequest()\n");
|
|
CdmSessionId session_id_internal(session_id, session_id_size);
|
|
return cdm_engine_.CancelKeyRequest(session_id_internal) == NO_ERROR
|
|
? cdm::kSuccess
|
|
: cdm::kSessionError;
|
|
}
|
|
|
|
void WvContentDecryptionModule::TimerExpired(void* context) {
|
|
LOGI("Timer expired, send cdm_engine OnTimerEvent");
|
|
if (this != context) {
|
|
LOGD("Context should have been set, Timer Expired Error\n");
|
|
return;
|
|
}
|
|
OnTimerEvent();
|
|
}
|
|
|
|
cdm::Status WvContentDecryptionModule::Decrypt(
|
|
const cdm::InputBuffer& encrypted_buffer,
|
|
cdm::DecryptedBlock* decrypted_block) {
|
|
LOGI("=>Enter WvContentDecryptionModule::Decrypt()\n");
|
|
if (encrypted_buffer.iv_size != KEY_IV_SIZE)
|
|
return cdm::kDecryptError;
|
|
std::vector < uint8_t > iv(KEY_IV_SIZE);
|
|
memcpy(&iv[0], encrypted_buffer.iv, encrypted_buffer.iv_size);
|
|
|
|
KeyId key_id(reinterpret_cast<const char*>(encrypted_buffer.key_id),
|
|
encrypted_buffer.key_id_size);
|
|
|
|
CdmSessionId session_id; // it's empty but cdm_engine will locate via key_id?
|
|
|
|
if (NULL == encrypted_buffer.subsamples
|
|
|| encrypted_buffer.num_subsamples <= 0)
|
|
return cdm::kDecryptError;
|
|
|
|
CdmDecryptionParameters parameters(&key_id,
|
|
encrypted_buffer.data, 0, &iv, 0,
|
|
NULL);
|
|
parameters.is_secure = false;
|
|
return DoSubsampleDecrypt(session_id,
|
|
parameters,
|
|
iv,
|
|
encrypted_buffer,
|
|
decrypted_block);
|
|
}
|
|
|
|
cdm::Status WvContentDecryptionModule::InitializeAudioDecoder(
|
|
const cdm::AudioDecoderConfig& audio_decoder_config) {
|
|
LOGI("WvContentDecryptionModule::InitializeAudioDecoder() Not implemented\n");
|
|
return cdm::kDecodeError;
|
|
}
|
|
|
|
cdm::Status WvContentDecryptionModule::InitializeVideoDecoder(
|
|
const cdm::VideoDecoderConfig& video_decoder_config) {
|
|
LOGI("WvContentDecryptionModule::InitializeVideoDecoder() Not implemented\n");
|
|
return cdm::kDecodeError;
|
|
}
|
|
|
|
void WvContentDecryptionModule::DeinitializeDecoder(
|
|
cdm::StreamType decoder_type) {
|
|
LOGI("WvContentDecryptionModule::DeInitializeDecoder() Not implemented\n");
|
|
}
|
|
|
|
void WvContentDecryptionModule::ResetDecoder(cdm::StreamType decoder_type) {
|
|
LOGI("WvContentDecryptionModule::ResetDecoder() Not implemented\n");
|
|
}
|
|
|
|
cdm::Status WvContentDecryptionModule::DecryptAndDecodeFrame(
|
|
const cdm::InputBuffer& encrypted_buffer, cdm::VideoFrame* video_frame) {
|
|
LOGI("WvContentDecryptionModule::DecryptAndDecodeFrame() Not implemented\n");
|
|
return cdm::kDecodeError;
|
|
}
|
|
|
|
cdm::Status WvContentDecryptionModule::DecryptAndDecodeSamples(
|
|
const cdm::InputBuffer& encrypted_buffer, cdm::AudioFrames* audio_frames) {
|
|
LOGI(
|
|
"WvContentDecryptionModule::DecryptAndDecodeSamples() Not implemented\n");
|
|
return cdm::kDecodeError;
|
|
}
|
|
|
|
// This is the Level 1 API. When the host application calls the CDM's
|
|
// DecryptDecodeAndRenderFrame(), rather than the CDM's Decrypt(),
|
|
// OEMCrypto_DecryptCTR() will be told to use direct rendering with no
|
|
// cleartext in the return.
|
|
cdm::Status WvContentDecryptionModule::DecryptDecodeAndRenderFrame(
|
|
const cdm::InputBuffer& encrypted_buffer) {
|
|
LOGI("WvContentDecryptionModule::DecryptDecodeAndRenderFrame()\n");
|
|
|
|
if (encrypted_buffer.iv_size != KEY_IV_SIZE) return cdm::kDecryptError;
|
|
|
|
std::vector<uint8_t> iv(KEY_IV_SIZE);
|
|
memcpy(&iv[0], encrypted_buffer.iv, encrypted_buffer.iv_size);
|
|
KeyId key_id(reinterpret_cast<const char*>(encrypted_buffer.key_id),
|
|
encrypted_buffer.key_id_size);
|
|
CdmSessionId session_id; // it's empty but cdm_engine will locate via key_id.
|
|
|
|
if (NULL == encrypted_buffer.subsamples
|
|
|| encrypted_buffer.num_subsamples <= 0)
|
|
return cdm::kDecryptError;
|
|
|
|
CdmDecryptionParameters parameters(&key_id, encrypted_buffer.data, 0, &iv, 0,
|
|
NULL);
|
|
return DoSubsampleDecrypt(session_id, parameters, iv, encrypted_buffer,
|
|
NULL);
|
|
}
|
|
|
|
// This is the Level 1 API. When the host application calls the CDM's
|
|
// DecryptDecodeAndRenderSamples(), rather than the CDM's Decrypt(),
|
|
// OEMCrypto_DecryptCTR() will be told to use direct rendering with no cleartext
|
|
// in the return.
|
|
cdm::Status WvContentDecryptionModule::DecryptDecodeAndRenderSamples(
|
|
const cdm::InputBuffer& encrypted_buffer) {
|
|
LOGI("WvContentDecryptionModule::DecryptDecodeAndRenderSamples()\n");
|
|
|
|
if (encrypted_buffer.iv_size != KEY_IV_SIZE) return cdm::kDecryptError;
|
|
|
|
std::vector<uint8_t> iv(KEY_IV_SIZE);
|
|
memcpy(&iv[0], encrypted_buffer.iv, encrypted_buffer.iv_size);
|
|
KeyId key_id(reinterpret_cast<const char*>(encrypted_buffer.key_id),
|
|
encrypted_buffer.key_id_size);
|
|
CdmSessionId session_id; // it's empty but cdm_engine will locate via key_id.
|
|
|
|
if (NULL == encrypted_buffer.subsamples ||
|
|
encrypted_buffer.num_subsamples <= 0)
|
|
return cdm::kDecryptError;
|
|
|
|
CdmDecryptionParameters parameters(&key_id, encrypted_buffer.data, 0, &iv, 0,
|
|
NULL);
|
|
parameters.is_video = false; // override the default true value for audio.
|
|
return DoSubsampleDecrypt(session_id, parameters, iv, encrypted_buffer,
|
|
NULL);
|
|
}
|
|
|
|
void WvContentDecryptionModule::Destroy() { delete this; }
|
|
|
|
// Provisioning related methods
|
|
cdm::Status WvContentDecryptionModule::GetProvisioningRequest(
|
|
std::string* request, std::string* provisioning_server_url) {
|
|
if (cdm_engine_.GetProvisioningRequest(
|
|
static_cast<CdmProvisioningRequest*>(request),
|
|
provisioning_server_url) == NO_ERROR) {
|
|
return cdm::kSuccess;
|
|
}
|
|
return cdm::kSessionError;
|
|
}
|
|
|
|
cdm::Status WvContentDecryptionModule::HandleProvisioningResponse(
|
|
std::string& response) {
|
|
if (cdm_engine_.HandleProvisioningResponse(
|
|
static_cast<CdmProvisioningRequest&>(response)) == NO_ERROR) {
|
|
return cdm::kSuccess;
|
|
}
|
|
return cdm::kSessionError;
|
|
}
|
|
|
|
void WvContentDecryptionModule::EnablePolicyTimer() {
|
|
LOGI("WvContentDecryptionModule::EnablePolicyTimer()\n");
|
|
host_->SetTimer(kCdmPolicyTimerDurationSeconds * 1000, this);
|
|
}
|
|
|
|
void WvContentDecryptionModule::DisablePolicyTimer() {
|
|
LOGI("WvContentDecryptionModule::DisablePolicyTimer()\n");
|
|
host_->SetTimer(kCdmPolicyTimerCancel, NULL);
|
|
}
|
|
|
|
void WvContentDecryptionModule::OnTimerEvent() {
|
|
|
|
LOGI("WvContentDecryptionModule::OnTimerEvent()\n");
|
|
cdm_engine_.OnTimerEvent();
|
|
}
|
|
|
|
cdm::Status WvContentDecryptionModule::DoSubsampleDecrypt(
|
|
CdmSessionId& session_id,
|
|
CdmDecryptionParameters& parameters,
|
|
std::vector < uint8_t >& iv,
|
|
const cdm::InputBuffer& encrypted_buffer,
|
|
cdm::DecryptedBlock* decrypted_block) {
|
|
|
|
/* This routine assumes session_id and iv have already
|
|
been initialized by the caller and encrypted_buffer contains subsample
|
|
information. Also, parameters is expected to be pre-initialized with any
|
|
needed parameters not related to subsample parsing.
|
|
decrypted_block may be NULL. */
|
|
CdmResponseType status = NO_ERROR;
|
|
uint8_t* output_buffer = decrypted_block
|
|
? reinterpret_cast<uint8_t*>(
|
|
decrypted_block->DecryptedBuffer()->Data())
|
|
: NULL;
|
|
size_t offset = 0;
|
|
size_t encrypted_offset = 0;
|
|
uint32_t block_ctr = 0;
|
|
const cdm::SubsampleEntry *subsamples = encrypted_buffer.subsamples;
|
|
bool first = true;
|
|
|
|
for (int i = 0; i < encrypted_buffer.num_subsamples; ++i) {
|
|
const cdm::SubsampleEntry& subsample = subsamples[i];
|
|
|
|
for (int is_encrypted = 0; is_encrypted < 2; ++is_encrypted) {
|
|
size_t bytes =
|
|
is_encrypted ? subsample.cipher_bytes : subsample.clear_bytes;
|
|
if (0 == bytes)
|
|
continue;
|
|
if (is_encrypted) {
|
|
uint32_t counter = encrypted_offset / kIvSize;
|
|
::Ctr128Add(counter - block_ctr, &iv[0]);
|
|
block_ctr = counter;
|
|
}
|
|
|
|
parameters.encrypt_buffer = &encrypted_buffer.data[encrypted_buffer
|
|
.data_offset + offset];
|
|
if (output_buffer)
|
|
parameters.decrypt_buffer = &output_buffer[offset];
|
|
|
|
parameters.encrypt_length = bytes;
|
|
parameters.decrypt_buffer_length = encrypted_buffer.data_size - offset;
|
|
parameters.block_offset = encrypted_offset % kIvSize;
|
|
|
|
offset += bytes;
|
|
if (is_encrypted)
|
|
encrypted_offset += bytes;
|
|
|
|
parameters.is_encrypted = is_encrypted;
|
|
parameters.subsample_flags =
|
|
(true == first) ? OEMCrypto_FirstSubsample : 0;
|
|
parameters.subsample_flags |= (
|
|
offset == encrypted_buffer.data_size ? OEMCrypto_LastSubsample : 0);
|
|
|
|
first = false;
|
|
|
|
status = cdm_engine_.Decrypt(session_id, parameters);
|
|
|
|
switch (status) {
|
|
case wvcdm::NEED_KEY:
|
|
return cdm::kNoKey;
|
|
break;
|
|
case wvcdm::NO_ERROR:
|
|
break;
|
|
default:
|
|
return cdm::kDecryptError;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
return cdm::kSuccess;
|
|
}
|
|
|
|
} // namespace wvcdm
|