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:
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
196
libwvdrmengine/cdm/core/src/device_files.cpp
Normal file
196
libwvdrmengine/cdm/core/src/device_files.cpp
Normal 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;
|
||||
}
|
||||
|
||||
}
|
||||
47
libwvdrmengine/cdm/core/src/device_files.proto
Normal file
47
libwvdrmengine/cdm/core/src/device_files.proto
Normal 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;
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user