Source release v3.4.1
This commit is contained in:
@@ -243,6 +243,8 @@ CdmResponseType CdmSession::GenerateKeyRequest(
|
||||
case kLicenseTypeRelease:
|
||||
is_release_ = true;
|
||||
break;
|
||||
case kLicenseTypeSubSession:
|
||||
return license_parser_->HandleSubLicense(init_data);
|
||||
default:
|
||||
LOGE("CdmSession::GenerateKeyRequest: unrecognized license type: %ld",
|
||||
license_type);
|
||||
@@ -270,6 +272,17 @@ CdmResponseType CdmSession::GenerateKeyRequest(
|
||||
return KEY_REQUEST_ERROR_1;
|
||||
}
|
||||
|
||||
std::vector<video_widevine::SubLicense> embedded_key_data =
|
||||
init_data.ExtractEmbeddedKeys();
|
||||
for (int i = 0; i < embedded_key_data.size(); ++i) {
|
||||
CdmResponseType sts = crypto_session_->AddSubSession(
|
||||
embedded_key_data[i].sub_session_key_id());
|
||||
if (NO_ERROR != sts) {
|
||||
LOGE("CdmSession::GenerateKeyRequest: Unable to generate sub session");
|
||||
return sts;
|
||||
}
|
||||
}
|
||||
|
||||
app_parameters_ = app_parameters;
|
||||
CdmResponseType status = license_parser_->PrepareKeyRequest(
|
||||
init_data, license_type,
|
||||
|
||||
@@ -26,6 +26,47 @@ std::string EncodeUint32(unsigned int u) {
|
||||
s.append(1, (u >> 0) & 0xFF);
|
||||
return s;
|
||||
}
|
||||
size_t GetOffset(std::string message, std::string field) {
|
||||
size_t pos = message.find(field);
|
||||
if (pos == std::string::npos) {
|
||||
LOGE("CryptoSession::GetOffset : Cannot find offset for %s", field.c_str());
|
||||
pos = 0;
|
||||
}
|
||||
return pos;
|
||||
}
|
||||
void GenerateMacContext(const std::string& input_context,
|
||||
std::string* deriv_context) {
|
||||
if (!deriv_context) {
|
||||
LOGE("CryptoSession::GenerateMacContext : No output destination provided.");
|
||||
return;
|
||||
}
|
||||
|
||||
const std::string kSigningKeyLabel = "AUTHENTICATION";
|
||||
const size_t kSigningKeySizeBits = wvcdm::MAC_KEY_SIZE * 8;
|
||||
|
||||
deriv_context->assign(kSigningKeyLabel);
|
||||
deriv_context->append(1, '\0');
|
||||
deriv_context->append(input_context);
|
||||
deriv_context->append(EncodeUint32(kSigningKeySizeBits * 2));
|
||||
}
|
||||
|
||||
void GenerateEncryptContext(const std::string& input_context,
|
||||
std::string* deriv_context) {
|
||||
if (!deriv_context) {
|
||||
LOGE(
|
||||
"CryptoSession::GenerateEncryptContext : No output destination "
|
||||
"provided.");
|
||||
return;
|
||||
}
|
||||
|
||||
const std::string kEncryptionKeyLabel = "ENCRYPTION";
|
||||
const size_t kEncryptionKeySizeBits = wvcdm::KEY_SIZE * 8;
|
||||
|
||||
deriv_context->assign(kEncryptionKeyLabel);
|
||||
deriv_context->append(1, '\0');
|
||||
deriv_context->append(input_context);
|
||||
deriv_context->append(EncodeUint32(kEncryptionKeySizeBits));
|
||||
}
|
||||
const uint32_t kRsaSignatureLength = 256;
|
||||
const size_t kMaximumChunkSize = 100 * 1024; // 100 KiB
|
||||
}
|
||||
@@ -37,6 +78,464 @@ bool CryptoSession::initialized_ = false;
|
||||
int CryptoSession::session_count_ = 0;
|
||||
uint64_t CryptoSession::request_id_index_ = 0;
|
||||
|
||||
class DefaultKeySession : public KeySession {
|
||||
public:
|
||||
explicit DefaultKeySession(CryptoSessionId oec_session_id)
|
||||
: oec_session_id_(oec_session_id) {}
|
||||
virtual ~DefaultKeySession() {}
|
||||
|
||||
KeySessionType Type() { return kDefault; }
|
||||
bool GenerateDerivedKeys(const std::string& message) {
|
||||
std::string mac_deriv_message;
|
||||
std::string enc_deriv_message;
|
||||
GenerateMacContext(message, &mac_deriv_message);
|
||||
GenerateEncryptContext(message, &enc_deriv_message);
|
||||
|
||||
LOGV("GenerateDerivedKeys: id=%ld", (uint32_t)oec_session_id_);
|
||||
OEMCryptoResult sts = OEMCrypto_GenerateDerivedKeys(
|
||||
oec_session_id_,
|
||||
reinterpret_cast<const uint8_t*>(mac_deriv_message.data()),
|
||||
mac_deriv_message.size(),
|
||||
reinterpret_cast<const uint8_t*>(enc_deriv_message.data()),
|
||||
enc_deriv_message.size());
|
||||
if (OEMCrypto_SUCCESS != sts) {
|
||||
LOGE("GenerateDerivedKeys: OEMCrypto_GenerateDerivedKeys error=%d", sts);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool GenerateDerivedKeys(const std::string& message,
|
||||
const std::string& session_key) {
|
||||
std::string mac_deriv_message;
|
||||
std::string enc_deriv_message;
|
||||
GenerateMacContext(message, &mac_deriv_message);
|
||||
GenerateEncryptContext(message, &enc_deriv_message);
|
||||
|
||||
LOGV("GenerateDerivedKeys: id=%ld", (uint32_t)oec_session_id_);
|
||||
OEMCryptoResult sts = OEMCrypto_DeriveKeysFromSessionKey(
|
||||
oec_session_id_,
|
||||
reinterpret_cast<const uint8_t*>(session_key.data()),
|
||||
session_key.size(),
|
||||
reinterpret_cast<const uint8_t*>(mac_deriv_message.data()),
|
||||
mac_deriv_message.size(),
|
||||
reinterpret_cast<const uint8_t*>(enc_deriv_message.data()),
|
||||
enc_deriv_message.size());
|
||||
if (OEMCrypto_SUCCESS != sts) {
|
||||
LOGE("GenerateDerivedKeys: OEMCrypto_DeriveKeysFromSessionKey err=%d",
|
||||
sts);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
OEMCryptoResult LoadKeys(const std::string& message,
|
||||
const std::string& signature,
|
||||
const std::string& mac_key_iv,
|
||||
const std::string& mac_key,
|
||||
const std::vector<CryptoKey>& keys,
|
||||
const std::string& provider_session_token,
|
||||
CdmCipherMode* cipher_mode) {
|
||||
const uint8_t* msg = reinterpret_cast<const uint8_t*>(message.data());
|
||||
const uint8_t* enc_mac_key = NULL;
|
||||
const uint8_t* enc_mac_key_iv = NULL;
|
||||
if (mac_key.size() >= MAC_KEY_SIZE && mac_key_iv.size() >= KEY_IV_SIZE) {
|
||||
enc_mac_key = msg + GetOffset(message, mac_key);
|
||||
enc_mac_key_iv = msg + GetOffset(message, mac_key_iv);
|
||||
} else {
|
||||
LOGV("CryptoSession::LoadKeys: enc_mac_key not set");
|
||||
}
|
||||
std::vector<OEMCrypto_KeyObject> load_keys(keys.size());
|
||||
for (size_t i = 0; i < keys.size(); ++i) {
|
||||
const CryptoKey* ki = &keys[i];
|
||||
OEMCrypto_KeyObject* ko = &load_keys[i];
|
||||
ko->key_id = msg + GetOffset(message, ki->key_id());
|
||||
ko->key_id_length = ki->key_id().length();
|
||||
ko->key_data_iv = msg + GetOffset(message, ki->key_data_iv());
|
||||
ko->key_data = msg + GetOffset(message, ki->key_data());
|
||||
ko->key_data_length = ki->key_data().length();
|
||||
if (ki->HasKeyControl()) {
|
||||
ko->key_control_iv = msg + GetOffset(message, ki->key_control_iv());
|
||||
ko->key_control = msg + GetOffset(message, ki->key_control());
|
||||
} else {
|
||||
LOGE("For key %d: XXX key has no control block. size=%d", i,
|
||||
ki->key_control().size());
|
||||
ko->key_control_iv = NULL;
|
||||
ko->key_control = NULL;
|
||||
}
|
||||
ko->cipher_mode = ki->cipher_mode() == kCipherModeCbc
|
||||
? OEMCrypto_CipherMode_CBC
|
||||
: OEMCrypto_CipherMode_CTR;
|
||||
*cipher_mode = ki->cipher_mode();
|
||||
}
|
||||
uint8_t* pst = NULL;
|
||||
if (!provider_session_token.empty()) {
|
||||
pst = const_cast<uint8_t*>(msg) +
|
||||
GetOffset(message, provider_session_token);
|
||||
}
|
||||
|
||||
LOGV("LoadKeys: id=%ld", (uint32_t)oec_session_id_);
|
||||
return OEMCrypto_LoadKeys(
|
||||
oec_session_id_, msg, message.size(),
|
||||
reinterpret_cast<const uint8_t*>(signature.data()),
|
||||
signature.size(), enc_mac_key_iv, enc_mac_key, keys.size(),
|
||||
&load_keys[0], pst, provider_session_token.length());
|
||||
}
|
||||
|
||||
OEMCryptoResult SelectKey(const std::string& key_id) {
|
||||
// Crypto session lock already locked.
|
||||
if (!cached_key_id_.empty() && cached_key_id_ == key_id) {
|
||||
// Already using the desired key.
|
||||
return OEMCrypto_SUCCESS;
|
||||
}
|
||||
|
||||
cached_key_id_ = key_id;
|
||||
|
||||
const uint8_t* key_id_string =
|
||||
reinterpret_cast<const uint8_t*>(cached_key_id_.data());
|
||||
|
||||
OEMCryptoResult sts = OEMCrypto_SelectKey(oec_session_id_, key_id_string,
|
||||
cached_key_id_.size());
|
||||
|
||||
if (OEMCrypto_SUCCESS != sts) {
|
||||
cached_key_id_.clear();
|
||||
}
|
||||
return sts;
|
||||
}
|
||||
|
||||
OEMCryptoResult Decrypt(
|
||||
const CdmDecryptionParameters& params,
|
||||
OEMCrypto_DestBufferDesc& buffer_descriptor,
|
||||
OEMCrypto_CENCEncryptPatternDesc& pattern_descriptor) {
|
||||
|
||||
return OEMCrypto_DecryptCENC(
|
||||
oec_session_id_, params.encrypt_buffer, params.encrypt_length,
|
||||
params.is_encrypted, &(*params.iv).front(), params.block_offset,
|
||||
&buffer_descriptor, &pattern_descriptor, params.subsample_flags);
|
||||
}
|
||||
|
||||
private:
|
||||
CryptoSessionId oec_session_id_;
|
||||
KeyId cached_key_id_;
|
||||
};
|
||||
|
||||
// TODO(jfore): Move this class into it's own module.
|
||||
class SubLicenseKeySession : public KeySession {
|
||||
typedef enum {
|
||||
kInitializing,
|
||||
kInitialLicenseLoaded,
|
||||
kInitialLicenseFailed,
|
||||
} SubLicenseState;
|
||||
|
||||
public:
|
||||
SubLicenseKeySession(SubLicenseSessionMap& sub_license_oec_sessions,
|
||||
std::string& wrapped_private_device_key,
|
||||
SecurityLevel requested_security_level)
|
||||
: state_(kInitializing),
|
||||
wrapped_private_device_key_(wrapped_private_device_key),
|
||||
sub_license_oec_sessions_(sub_license_oec_sessions),
|
||||
requested_security_level_(requested_security_level) {}
|
||||
virtual ~SubLicenseKeySession() {}
|
||||
|
||||
KeySessionType Type() { return kSubLicense; }
|
||||
// This version of GenerateDerivedKeys is for devices using keyboxes. It is
|
||||
// not supported using sub licenses.
|
||||
bool GenerateDerivedKeys(const std::string& message) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// GenerateDerivedKeys is called for each open oemcrypto session and is only
|
||||
// called once.
|
||||
bool GenerateDerivedKeys(const std::string& message,
|
||||
const std::string& session_key) {
|
||||
std::string mac_deriv_message;
|
||||
std::string enc_deriv_message;
|
||||
GenerateMacContext(message, &mac_deriv_message);
|
||||
GenerateEncryptContext(message, &enc_deriv_message);
|
||||
|
||||
for (SubLicenseSessionMap::iterator it = sub_license_oec_sessions_.begin();
|
||||
it != sub_license_oec_sessions_.end(); it++) {
|
||||
LOGV("GenerateDerivedKeys: id=%ld", (uint32_t)it->second);
|
||||
OEMCryptoResult sts = OEMCrypto_DeriveKeysFromSessionKey(
|
||||
it->second, reinterpret_cast<const uint8_t*>(session_key.data()),
|
||||
session_key.size(),
|
||||
reinterpret_cast<const uint8_t*>(mac_deriv_message.data()),
|
||||
mac_deriv_message.size(),
|
||||
reinterpret_cast<const uint8_t*>(enc_deriv_message.data()),
|
||||
enc_deriv_message.size());
|
||||
|
||||
if (OEMCrypto_SUCCESS != sts) {
|
||||
LOGE("GenerateDerivedKeys: OEMCrypto_DeriveKeysFromSessionKey err=%d",
|
||||
sts);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Load the keys in |keys|. The initial keys are saved for key rotation.
|
||||
OEMCryptoResult LoadKeys(const std::string& message,
|
||||
const std::string& signature,
|
||||
const std::string& mac_key_iv,
|
||||
const std::string& mac_key,
|
||||
const std::vector<CryptoKey>& keys,
|
||||
const std::string& provider_session_token,
|
||||
CdmCipherMode* cipher_mode) {
|
||||
if (state_ == kInitializing) {
|
||||
state_ = kInitialLicenseLoaded;
|
||||
keys_ = keys;
|
||||
OEMCryptoResult sts = DoLoadKeys(message,
|
||||
signature,
|
||||
mac_key_iv,
|
||||
mac_key,
|
||||
keys,
|
||||
provider_session_token,
|
||||
cipher_mode);
|
||||
if (OEMCrypto_SUCCESS != sts) {
|
||||
state_ = kInitialLicenseFailed;
|
||||
}
|
||||
return sts;
|
||||
}
|
||||
return DoSubLicenseLoadKeys(message, signature, mac_key_iv, mac_key,
|
||||
keys[0], provider_session_token,
|
||||
cipher_mode);
|
||||
}
|
||||
|
||||
// Each oemcrypto session contains a single key. Find the right sub session
|
||||
// and save it's id as the selected oemcrypto session.
|
||||
OEMCryptoResult SelectKey(const std::string& key_id) {
|
||||
for (int i = 0; i < keys_.size(); ++i) {
|
||||
if (keys_[i].key_id() == key_id) {
|
||||
cached_sub_session_key_id_ = keys_[i].sub_session_key_id();
|
||||
}
|
||||
}
|
||||
return OEMCrypto_SUCCESS;
|
||||
}
|
||||
|
||||
// Decrypt performs the decryption using the selected oemcrypto session.
|
||||
// TODO(jfore): Support DecryptInChunks.
|
||||
OEMCryptoResult Decrypt(
|
||||
const CdmDecryptionParameters& params,
|
||||
OEMCrypto_DestBufferDesc& buffer_descriptor,
|
||||
OEMCrypto_CENCEncryptPatternDesc& pattern_descriptor) {
|
||||
SubLicenseSessionMap::iterator it =
|
||||
sub_license_oec_sessions_.find(cached_sub_session_key_id_);
|
||||
if (it == sub_license_oec_sessions_.end()) {
|
||||
return OEMCrypto_ERROR_INVALID_SESSION;
|
||||
}
|
||||
|
||||
return OEMCrypto_DecryptCENC(
|
||||
it->second, params.encrypt_buffer, params.encrypt_length,
|
||||
params.is_encrypted, &(*params.iv).front(), params.block_offset,
|
||||
&buffer_descriptor, &pattern_descriptor, params.subsample_flags);
|
||||
}
|
||||
|
||||
private:
|
||||
// Destroy each open oemcrypto session and relace them with new ones.
|
||||
OEMCryptoResult ResetCryptoSessions() {
|
||||
for (SubLicenseSessionMap::iterator it = sub_license_oec_sessions_.begin();
|
||||
it != sub_license_oec_sessions_.end(); it++) {
|
||||
OEMCryptoResult sts;
|
||||
sts = OEMCrypto_CloseSession(it->second);
|
||||
if (OEMCrypto_SUCCESS != sts) {
|
||||
return sts;
|
||||
}
|
||||
sts = OEMCrypto_OpenSession(&it->second, requested_security_level_);
|
||||
if (OEMCrypto_SUCCESS != sts) {
|
||||
return sts;
|
||||
}
|
||||
sts = OEMCrypto_LoadDeviceRSAKey(
|
||||
it->second,
|
||||
reinterpret_cast<const uint8_t*>(wrapped_private_device_key_.data()),
|
||||
wrapped_private_device_key_.size());
|
||||
if (OEMCrypto_SUCCESS != sts) {
|
||||
return sts;
|
||||
}
|
||||
}
|
||||
return OEMCrypto_SUCCESS;
|
||||
}
|
||||
|
||||
// DoLoadKeys loads a single key into each oemcrypto session.
|
||||
OEMCryptoResult DoLoadKeys(const std::string& message,
|
||||
const std::string& signature,
|
||||
const std::string& mac_key_iv,
|
||||
const std::string& mac_key,
|
||||
const std::vector<CryptoKey>& keys,
|
||||
const std::string& provider_session_token,
|
||||
CdmCipherMode* cipher_mode) {
|
||||
const uint8_t* msg = reinterpret_cast<const uint8_t*>(message.data());
|
||||
const uint8_t* enc_mac_key = NULL;
|
||||
const uint8_t* enc_mac_key_iv = NULL;
|
||||
if (mac_key.size() >= MAC_KEY_SIZE && mac_key_iv.size() >= KEY_IV_SIZE) {
|
||||
enc_mac_key = msg + GetOffset(message, mac_key);
|
||||
enc_mac_key_iv = msg + GetOffset(message, mac_key_iv);
|
||||
} else {
|
||||
LOGV("CryptoSession::LoadKeys: enc_mac_key not set");
|
||||
}
|
||||
uint8_t* pst = NULL;
|
||||
if (!provider_session_token.empty()) {
|
||||
pst = const_cast<uint8_t*>(msg) +
|
||||
GetOffset(message, provider_session_token);
|
||||
}
|
||||
|
||||
// TODO(jfore): Use C++ 11 range loop?
|
||||
for (int i = 0; i < keys.size(); i++) {
|
||||
OEMCrypto_KeyObject key_object;
|
||||
const CryptoKey& key_data = keys[i];
|
||||
key_object.key_id = msg + GetOffset(message, key_data.key_id());
|
||||
key_object.key_id_length = key_data.key_id().length();
|
||||
key_object.key_data_iv = msg + GetOffset(message, key_data.key_data_iv());
|
||||
key_object.key_data = msg + GetOffset(message, key_data.key_data());
|
||||
key_object.key_data_length = key_data.key_data().length();
|
||||
if (key_data.HasKeyControl()) {
|
||||
key_object.key_control_iv =
|
||||
msg + GetOffset(message, key_data.key_control_iv());
|
||||
key_object.key_control =
|
||||
msg + GetOffset(message, key_data.key_control());
|
||||
} else {
|
||||
LOGE("For key %s: XXX key has no control block. size=%d",
|
||||
key_data.key_id().c_str(), key_data.key_control().size());
|
||||
key_object.key_control_iv = NULL;
|
||||
key_object.key_control = NULL;
|
||||
}
|
||||
key_object.cipher_mode = key_data.cipher_mode() == kCipherModeCbc
|
||||
? OEMCrypto_CipherMode_CBC
|
||||
: OEMCrypto_CipherMode_CTR;
|
||||
*cipher_mode = key_data.cipher_mode();
|
||||
SubLicenseSessionMap::iterator oec_session_id =
|
||||
sub_license_oec_sessions_.find(key_data.sub_session_key_id());
|
||||
if (oec_session_id == sub_license_oec_sessions_.end()) {
|
||||
LOGE("CryptoSession::LoadKeys: Unrecognized sub session %s",
|
||||
key_data.sub_session_key_id().c_str());
|
||||
return OEMCrypto_ERROR_INVALID_SESSION;
|
||||
}
|
||||
|
||||
OEMCryptoResult sts;
|
||||
sts = OEMCrypto_LoadKeys(
|
||||
oec_session_id->second, msg, message.size(),
|
||||
reinterpret_cast<const uint8_t*>(signature.data()),
|
||||
signature.size(), enc_mac_key_iv, enc_mac_key, 1, &key_object,
|
||||
pst, provider_session_token.length());
|
||||
if (sts != OEMCrypto_SUCCESS) {
|
||||
return sts;
|
||||
}
|
||||
|
||||
sts = OEMCrypto_SelectKey(
|
||||
oec_session_id->second,
|
||||
reinterpret_cast<const uint8_t*>(key_data.key_id().data()),
|
||||
key_data.key_id().size());
|
||||
}
|
||||
keys_ = keys;
|
||||
return OEMCrypto_SUCCESS;
|
||||
}
|
||||
|
||||
// DoLoadKeys loads a single key into each oemcrypto session.
|
||||
OEMCryptoResult DoSubLicenseLoadKeys(
|
||||
const std::string& message, const std::string& signature,
|
||||
const std::string& mac_key_iv, const std::string& mac_key,
|
||||
const CryptoKey& key, const std::string& provider_session_token,
|
||||
CdmCipherMode* cipher_mode) {
|
||||
SubLicenseSessionMap::iterator it = sub_license_oec_sessions_.end();
|
||||
int key_index = 0;
|
||||
for (; key_index < keys_.size(); key_index++) {
|
||||
if (keys_[key_index].track_label() == key.track_label()) {
|
||||
it = sub_license_oec_sessions_.find(
|
||||
keys_[key_index].sub_session_key_id());
|
||||
CryptoKey tmp = key;
|
||||
tmp.set_sub_session_key_id(keys_[key_index].sub_session_key_id());
|
||||
tmp.set_sub_session_key(keys_[key_index].sub_session_key());
|
||||
keys_[key_index] = tmp;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (it == sub_license_oec_sessions_.end()) {
|
||||
return OEMCrypto_ERROR_INVALID_SESSION;
|
||||
}
|
||||
LOGV("GenerateDerivedKeys: id=%ld", (uint32_t)it->second);
|
||||
|
||||
std::string mac_deriv_message;
|
||||
std::string enc_deriv_message;
|
||||
GenerateMacContext(key.track_label(), &mac_deriv_message);
|
||||
GenerateEncryptContext(key.track_label(), &enc_deriv_message);
|
||||
|
||||
const uint8_t* msg = reinterpret_cast<const uint8_t*>(message.data());
|
||||
const uint8_t* enc_mac_key = NULL;
|
||||
const uint8_t* enc_mac_key_iv = NULL;
|
||||
if (mac_key.size() >= MAC_KEY_SIZE && mac_key_iv.size() >= KEY_IV_SIZE) {
|
||||
enc_mac_key = msg + GetOffset(message, mac_key);
|
||||
enc_mac_key_iv = msg + GetOffset(message, mac_key_iv);
|
||||
} else {
|
||||
LOGV("CryptoSession::LoadKeys: enc_mac_key not set");
|
||||
}
|
||||
uint8_t* pst = NULL;
|
||||
if (!provider_session_token.empty()) {
|
||||
pst = const_cast<uint8_t*>(msg) +
|
||||
GetOffset(message, provider_session_token);
|
||||
}
|
||||
|
||||
OEMCryptoResult sts;
|
||||
const std::string& sub_session_key = keys_[key_index].sub_session_key();
|
||||
LOGE("ssksize = %d", sub_session_key.size());
|
||||
sts = OEMCrypto_DeriveKeysFromSessionKey(
|
||||
it->second, reinterpret_cast<const uint8_t*>(sub_session_key.data()),
|
||||
sub_session_key.size(),
|
||||
reinterpret_cast<const uint8_t*>(mac_deriv_message.data()),
|
||||
mac_deriv_message.size(),
|
||||
reinterpret_cast<const uint8_t*>(enc_deriv_message.data()),
|
||||
enc_deriv_message.size());
|
||||
|
||||
if (OEMCrypto_SUCCESS != sts) {
|
||||
LOGE("GenerateDerivedKeys: OEMCrypto_DeriveKeysFromSessionKey err=%d",
|
||||
sts);
|
||||
return sts;
|
||||
}
|
||||
|
||||
OEMCrypto_KeyObject key_object;
|
||||
key_object.key_id = msg + GetOffset(message, keys_[key_index].key_id());
|
||||
key_object.key_id_length = keys_[key_index].key_id().length();
|
||||
key_object.key_data_iv = msg + GetOffset(
|
||||
message, keys_[key_index].key_data_iv());
|
||||
key_object.key_data = msg + GetOffset(message, keys_[key_index].key_data());
|
||||
key_object.key_data_length = keys_[key_index].key_data().length();
|
||||
if (key.HasKeyControl()) {
|
||||
key_object.key_control_iv =
|
||||
msg + GetOffset(message, keys_[key_index].key_control_iv());
|
||||
key_object.key_control = msg + GetOffset(
|
||||
message, keys_[key_index].key_control());
|
||||
}
|
||||
key_object.cipher_mode = keys_[key_index].cipher_mode() == kCipherModeCbc
|
||||
? OEMCrypto_CipherMode_CBC
|
||||
: OEMCrypto_CipherMode_CTR;
|
||||
|
||||
sts = OEMCrypto_LoadKeys(
|
||||
it->second, msg, message.size(),
|
||||
reinterpret_cast<const uint8_t*>(signature.data()),
|
||||
signature.size(), enc_mac_key_iv, enc_mac_key, 1, &key_object,
|
||||
pst, provider_session_token.length());
|
||||
if (sts != OEMCrypto_SUCCESS) {
|
||||
return sts;
|
||||
}
|
||||
|
||||
sts = OEMCrypto_SelectKey(
|
||||
it->second,
|
||||
reinterpret_cast<const uint8_t*>(keys_[key_index].key_id().data()),
|
||||
keys_[key_index].key_id().size());
|
||||
return sts;
|
||||
}
|
||||
|
||||
SubLicenseState state_;
|
||||
std::string cached_sub_session_key_id_;
|
||||
std::string wrapped_private_device_key_;
|
||||
std::string message_;
|
||||
std::string session_key_;
|
||||
std::vector<CryptoKey> keys_;
|
||||
SubLicenseSessionMap& sub_license_oec_sessions_;
|
||||
SecurityLevel requested_security_level_;
|
||||
};
|
||||
|
||||
CryptoSession::CryptoSession()
|
||||
: open_(false),
|
||||
update_usage_table_after_close_session_(false),
|
||||
@@ -349,6 +848,7 @@ CdmResponseType CryptoSession::Open(SecurityLevel requested_security_level) {
|
||||
OEMCrypto_GetRandom(reinterpret_cast<uint8_t*>(&request_id_base_),
|
||||
sizeof(request_id_base_));
|
||||
++request_id_index_;
|
||||
key_session_.reset(new DefaultKeySession(oec_session_id_));
|
||||
return NO_ERROR;
|
||||
}
|
||||
|
||||
@@ -424,49 +924,6 @@ bool CryptoSession::PrepareRenewalRequest(const std::string& message,
|
||||
return true;
|
||||
}
|
||||
|
||||
void CryptoSession::GenerateMacContext(const std::string& input_context,
|
||||
std::string* deriv_context) {
|
||||
if (!deriv_context) {
|
||||
LOGE("CryptoSession::GenerateMacContext : No output destination provided.");
|
||||
return;
|
||||
}
|
||||
|
||||
const std::string kSigningKeyLabel = "AUTHENTICATION";
|
||||
const size_t kSigningKeySizeBits = MAC_KEY_SIZE * 8;
|
||||
|
||||
deriv_context->assign(kSigningKeyLabel);
|
||||
deriv_context->append(1, '\0');
|
||||
deriv_context->append(input_context);
|
||||
deriv_context->append(EncodeUint32(kSigningKeySizeBits * 2));
|
||||
}
|
||||
|
||||
void CryptoSession::GenerateEncryptContext(const std::string& input_context,
|
||||
std::string* deriv_context) {
|
||||
if (!deriv_context) {
|
||||
LOGE(
|
||||
"CryptoSession::GenerateEncryptContext : No output destination "
|
||||
"provided.");
|
||||
return;
|
||||
}
|
||||
|
||||
const std::string kEncryptionKeyLabel = "ENCRYPTION";
|
||||
const size_t kEncryptionKeySizeBits = KEY_SIZE * 8;
|
||||
|
||||
deriv_context->assign(kEncryptionKeyLabel);
|
||||
deriv_context->append(1, '\0');
|
||||
deriv_context->append(input_context);
|
||||
deriv_context->append(EncodeUint32(kEncryptionKeySizeBits));
|
||||
}
|
||||
|
||||
size_t CryptoSession::GetOffset(std::string message, std::string field) {
|
||||
size_t pos = message.find(field);
|
||||
if (pos == std::string::npos) {
|
||||
LOGE("CryptoSession::GetOffset : Cannot find offset for %s", field.c_str());
|
||||
pos = 0;
|
||||
}
|
||||
return pos;
|
||||
}
|
||||
|
||||
CdmResponseType CryptoSession::LoadKeys(
|
||||
const std::string& message, const std::string& signature,
|
||||
const std::string& mac_key_iv, const std::string& mac_key,
|
||||
@@ -475,49 +932,9 @@ CdmResponseType CryptoSession::LoadKeys(
|
||||
LOGV("CryptoSession::LoadKeys: Lock");
|
||||
AutoLock auto_lock(crypto_lock_);
|
||||
|
||||
const uint8_t* msg = reinterpret_cast<const uint8_t*>(message.data());
|
||||
const uint8_t* enc_mac_key = NULL;
|
||||
const uint8_t* enc_mac_key_iv = NULL;
|
||||
if (mac_key.size() >= MAC_KEY_SIZE && mac_key_iv.size() >= KEY_IV_SIZE) {
|
||||
enc_mac_key = msg + GetOffset(message, mac_key);
|
||||
enc_mac_key_iv = msg + GetOffset(message, mac_key_iv);
|
||||
} else {
|
||||
LOGV("CryptoSession::LoadKeys: enc_mac_key not set");
|
||||
}
|
||||
std::vector<OEMCrypto_KeyObject> load_keys(keys.size());
|
||||
for (size_t i = 0; i < keys.size(); ++i) {
|
||||
const CryptoKey* ki = &keys[i];
|
||||
OEMCrypto_KeyObject* ko = &load_keys[i];
|
||||
ko->key_id = msg + GetOffset(message, ki->key_id());
|
||||
ko->key_id_length = ki->key_id().length();
|
||||
ko->key_data_iv = msg + GetOffset(message, ki->key_data_iv());
|
||||
ko->key_data = msg + GetOffset(message, ki->key_data());
|
||||
ko->key_data_length = ki->key_data().length();
|
||||
if (ki->HasKeyControl()) {
|
||||
ko->key_control_iv = msg + GetOffset(message, ki->key_control_iv());
|
||||
ko->key_control = msg + GetOffset(message, ki->key_control());
|
||||
} else {
|
||||
LOGE("For key %d: XXX key has no control block. size=%d", i,
|
||||
ki->key_control().size());
|
||||
ko->key_control_iv = NULL;
|
||||
ko->key_control = NULL;
|
||||
}
|
||||
ko->cipher_mode = ki->cipher_mode() == kCipherModeCbc
|
||||
? OEMCrypto_CipherMode_CBC
|
||||
: OEMCrypto_CipherMode_CTR;
|
||||
cipher_mode_ = ki->cipher_mode();
|
||||
}
|
||||
uint8_t* pst = NULL;
|
||||
if (!provider_session_token.empty()) {
|
||||
pst =
|
||||
const_cast<uint8_t*>(msg) + GetOffset(message, provider_session_token);
|
||||
}
|
||||
LOGV("LoadKeys: id=%ld", (uint32_t)oec_session_id_);
|
||||
OEMCryptoResult sts = OEMCrypto_LoadKeys(
|
||||
oec_session_id_, msg, message.size(),
|
||||
reinterpret_cast<const uint8_t*>(signature.data()), signature.size(),
|
||||
enc_mac_key_iv, enc_mac_key, keys.size(), &load_keys[0], pst,
|
||||
provider_session_token.length());
|
||||
OEMCryptoResult sts = key_session_->LoadKeys(
|
||||
message, signature, mac_key_iv, mac_key, keys, provider_session_token,
|
||||
&cipher_mode_);
|
||||
|
||||
if (OEMCrypto_SUCCESS == sts) {
|
||||
if (!provider_session_token.empty()) {
|
||||
@@ -559,6 +976,7 @@ bool CryptoSession::LoadCertificatePrivateKey(std::string& wrapped_key) {
|
||||
LOGE("LoadCertificatePrivateKey: OEMCrypto_LoadDeviceRSAKey error=%d", sts);
|
||||
return false;
|
||||
}
|
||||
wrapped_key_ = wrapped_key;
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -592,79 +1010,47 @@ bool CryptoSession::RefreshKeys(const std::string& message,
|
||||
}
|
||||
}
|
||||
LOGV("RefreshKeys: id=%ld", static_cast<uint32_t>(oec_session_id_));
|
||||
return (
|
||||
OEMCrypto_SUCCESS ==
|
||||
OEMCrypto_RefreshKeys(oec_session_id_, msg, message.size(),
|
||||
reinterpret_cast<const uint8_t*>(signature.data()),
|
||||
signature.size(), num_keys, &load_key_array[0]));
|
||||
OEMCryptoResult sts = OEMCrypto_RefreshKeys(
|
||||
oec_session_id_, msg, message.size(),
|
||||
reinterpret_cast<const uint8_t*>(signature.data()),
|
||||
signature.size(), num_keys, &load_key_array[0]);
|
||||
return sts == OEMCrypto_SUCCESS;
|
||||
}
|
||||
|
||||
bool CryptoSession::SelectKey(const std::string& key_id) {
|
||||
// Crypto session lock already locked.
|
||||
if (!cached_key_id_.empty() && cached_key_id_ == key_id) {
|
||||
// Already using the desired key.
|
||||
return true;
|
||||
CdmResponseType CryptoSession::SelectKey(const std::string& key_id) {
|
||||
OEMCryptoResult sts = key_session_->SelectKey(key_id);
|
||||
|
||||
switch (sts) {
|
||||
case OEMCrypto_SUCCESS:
|
||||
return NO_ERROR;
|
||||
case OEMCrypto_ERROR_KEY_EXPIRED:
|
||||
return NEED_KEY;
|
||||
case OEMCrypto_ERROR_INSUFFICIENT_HDCP:
|
||||
return INSUFFICIENT_OUTPUT_PROTECTION;
|
||||
case OEMCrypto_ERROR_INVALID_SESSION:
|
||||
return INVALID_SESSION_1;
|
||||
case OEMCrypto_ERROR_NO_DEVICE_KEY:
|
||||
return NO_DEVICE_KEY_1;
|
||||
case OEMCrypto_ERROR_NO_CONTENT_KEY:
|
||||
return NO_CONTENT_KEY_2;
|
||||
case OEMCrypto_ERROR_INSUFFICIENT_RESOURCES:
|
||||
return INSUFFICIENT_CRYPTO_RESOURCES_2;
|
||||
case OEMCrypto_ERROR_UNKNOWN_FAILURE:
|
||||
return UNKNOWN_SELECT_KEY_ERROR_1;
|
||||
case OEMCrypto_ERROR_CONTROL_INVALID:
|
||||
case OEMCrypto_ERROR_KEYBOX_INVALID:
|
||||
default:
|
||||
return UNKNOWN_SELECT_KEY_ERROR_2;
|
||||
}
|
||||
|
||||
cached_key_id_ = key_id;
|
||||
|
||||
const uint8_t* key_id_string =
|
||||
reinterpret_cast<const uint8_t*>(cached_key_id_.data());
|
||||
|
||||
OEMCryptoResult sts =
|
||||
OEMCrypto_SelectKey(oec_session_id_, key_id_string,
|
||||
cached_key_id_.size());
|
||||
if (OEMCrypto_SUCCESS != sts) {
|
||||
cached_key_id_.clear();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CryptoSession::GenerateDerivedKeys(const std::string& message) {
|
||||
std::string mac_deriv_message;
|
||||
std::string enc_deriv_message;
|
||||
GenerateMacContext(message, &mac_deriv_message);
|
||||
GenerateEncryptContext(message, &enc_deriv_message);
|
||||
|
||||
LOGV("GenerateDerivedKeys: id=%ld", (uint32_t)oec_session_id_);
|
||||
OEMCryptoResult sts = OEMCrypto_GenerateDerivedKeys(
|
||||
oec_session_id_,
|
||||
reinterpret_cast<const uint8_t*>(mac_deriv_message.data()),
|
||||
mac_deriv_message.size(),
|
||||
reinterpret_cast<const uint8_t*>(enc_deriv_message.data()),
|
||||
enc_deriv_message.size());
|
||||
|
||||
if (OEMCrypto_SUCCESS != sts) {
|
||||
LOGE("GenerateDerivedKeys: OEMCrypto_GenerateDerivedKeys error=%d", sts);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
return key_session_->GenerateDerivedKeys(message);
|
||||
}
|
||||
|
||||
bool CryptoSession::GenerateDerivedKeys(const std::string& message,
|
||||
const std::string& session_key) {
|
||||
std::string mac_deriv_message;
|
||||
std::string enc_deriv_message;
|
||||
GenerateMacContext(message, &mac_deriv_message);
|
||||
GenerateEncryptContext(message, &enc_deriv_message);
|
||||
|
||||
LOGV("GenerateDerivedKeys: id=%ld", (uint32_t)oec_session_id_);
|
||||
OEMCryptoResult sts = OEMCrypto_DeriveKeysFromSessionKey(
|
||||
oec_session_id_, reinterpret_cast<const uint8_t*>(session_key.data()),
|
||||
session_key.size(),
|
||||
reinterpret_cast<const uint8_t*>(mac_deriv_message.data()),
|
||||
mac_deriv_message.size(),
|
||||
reinterpret_cast<const uint8_t*>(enc_deriv_message.data()),
|
||||
enc_deriv_message.size());
|
||||
|
||||
if (OEMCrypto_SUCCESS != sts) {
|
||||
LOGE("GenerateDerivedKeys: OEMCrypto_DeriveKeysFromSessionKey err=%d", sts);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
return key_session_->GenerateDerivedKeys(message, session_key);
|
||||
}
|
||||
|
||||
bool CryptoSession::GenerateSignature(const std::string& message,
|
||||
@@ -801,7 +1187,7 @@ CdmResponseType CryptoSession::Decrypt(const CdmDecryptionParameters& params) {
|
||||
AutoLock auto_lock(crypto_lock_);
|
||||
// Check if key needs to be selected
|
||||
if (params.is_encrypted) {
|
||||
if (!SelectKey(*params.key_id)) {
|
||||
if (NO_ERROR != SelectKey(*params.key_id)) {
|
||||
return NEED_KEY;
|
||||
}
|
||||
}
|
||||
@@ -810,12 +1196,13 @@ CdmResponseType CryptoSession::Decrypt(const CdmDecryptionParameters& params) {
|
||||
params.is_encrypted, &(*params.iv).front(), params.block_offset,
|
||||
&buffer_descriptor, &pattern_descriptor, params.subsample_flags);
|
||||
|
||||
sts = key_session_->Decrypt(params, buffer_descriptor, pattern_descriptor);
|
||||
|
||||
if (sts == OEMCrypto_ERROR_BUFFER_TOO_LARGE) {
|
||||
// OEMCrypto_DecryptCENC rejected the buffer as too large, so chunk it up
|
||||
// into sections no more than 100 KiB. The exact chunk size needs to be
|
||||
// an even number of pattern repetitions long or else the pattern will get
|
||||
// out of sync.
|
||||
// OEMCrypto_DecryptCENC rejected the buffer as too large, so chunk it
|
||||
// up into sections no more than 100 KiB. The exact chunk size needs to
|
||||
// be an even number of pattern repetitions long or else the pattern
|
||||
// will get out of sync.
|
||||
const size_t pattern_length =
|
||||
(pattern_descriptor.encrypt + pattern_descriptor.skip) *
|
||||
kAes128BlockSize;
|
||||
@@ -1015,8 +1402,10 @@ CdmResponseType CryptoSession::DeleteUsageInformation(
|
||||
reinterpret_cast<const uint8_t*>(provider_session_token.c_str()),
|
||||
provider_session_token.length());
|
||||
if (OEMCrypto_SUCCESS != status) {
|
||||
LOGE("CryptoSession::DeleteUsageInformation: Delete Usage Table error =%ld",
|
||||
status);
|
||||
LOGE(
|
||||
"CryptoSession::DeleteUsageInformation: Delete Usage Table error "
|
||||
"=%ld",
|
||||
status);
|
||||
response = UNKNOWN_ERROR;
|
||||
}
|
||||
status = OEMCrypto_UpdateUsageTable();
|
||||
@@ -1239,7 +1628,9 @@ bool CryptoSession::GetHdcpCapabilities(HdcpCapability* current,
|
||||
LOGV("GetHdcpCapabilities: id=%ld", (uint32_t)oec_session_id_);
|
||||
if (!initialized_) return false;
|
||||
if (current == NULL || max == NULL) {
|
||||
LOGE("CryptoSession::GetHdcpCapabilities: |current|, |max| cannot be NULL");
|
||||
LOGE(
|
||||
"CryptoSession::GetHdcpCapabilities: |current|, |max| cannot be "
|
||||
"NULL");
|
||||
return false;
|
||||
}
|
||||
OEMCryptoResult status = OEMCrypto_GetHDCPCapability(
|
||||
@@ -1323,7 +1714,7 @@ CdmResponseType CryptoSession::GenericEncrypt(const std::string& in_buffer,
|
||||
}
|
||||
|
||||
AutoLock auto_lock(crypto_lock_);
|
||||
if (!SelectKey(key_id)) {
|
||||
if (NO_ERROR != SelectKey(key_id)) {
|
||||
return KEY_ERROR_1;
|
||||
}
|
||||
|
||||
@@ -1364,7 +1755,7 @@ CdmResponseType CryptoSession::GenericDecrypt(const std::string& in_buffer,
|
||||
}
|
||||
|
||||
AutoLock auto_lock(crypto_lock_);
|
||||
if (!SelectKey(key_id)) {
|
||||
if (NO_ERROR != SelectKey(key_id)) {
|
||||
return KEY_ERROR_2;
|
||||
}
|
||||
|
||||
@@ -1405,7 +1796,7 @@ CdmResponseType CryptoSession::GenericSign(const std::string& message,
|
||||
size_t length = signature->size();
|
||||
|
||||
AutoLock auto_lock(crypto_lock_);
|
||||
if (!SelectKey(key_id)) {
|
||||
if (NO_ERROR != SelectKey(key_id)) {
|
||||
return KEY_ERROR_3;
|
||||
}
|
||||
|
||||
@@ -1452,7 +1843,7 @@ CdmResponseType CryptoSession::GenericVerify(const std::string& message,
|
||||
}
|
||||
|
||||
AutoLock auto_lock(crypto_lock_);
|
||||
if (!SelectKey(key_id)) {
|
||||
if (NO_ERROR != SelectKey(key_id)) {
|
||||
return KEY_ERROR_4;
|
||||
}
|
||||
|
||||
@@ -1473,6 +1864,65 @@ CdmResponseType CryptoSession::GenericVerify(const std::string& message,
|
||||
return NO_ERROR;
|
||||
}
|
||||
|
||||
CdmResponseType CryptoSession::AddSubSession(
|
||||
const std::string& sub_session_key_id) {
|
||||
size_t exists = sub_license_oec_sessions_.count(sub_session_key_id);
|
||||
if (exists > 0) {
|
||||
// TODO(jfore): Should this be an error if the key exists? If so add a new
|
||||
// error. If not, perhaps this should just print info message.
|
||||
LOGE("AddSubSession: SubSession already exists for id: %s",
|
||||
sub_session_key_id.c_str());
|
||||
return UNKNOWN_ERROR;
|
||||
}
|
||||
|
||||
CryptoSessionId sid;
|
||||
OEMCryptoResult sts = OEMCrypto_OpenSession(&sid, requested_security_level_);
|
||||
if (OEMCrypto_ERROR_TOO_MANY_SESSIONS == sts) {
|
||||
LOGE("OEMCrypto_Open failed: %d, open sessions: %ld, initialized: %d",
|
||||
sts, session_count_, (int)initialized_);
|
||||
return INSUFFICIENT_CRYPTO_RESOURCES;
|
||||
} else if (OEMCrypto_SUCCESS != sts) {
|
||||
LOGE("OEMCrypto_Open failed: %d, open sessions: %ld, initialized: %d",
|
||||
sts, session_count_, (int)initialized_);
|
||||
return UNKNOWN_ERROR;
|
||||
}
|
||||
|
||||
sts = OEMCrypto_LoadDeviceRSAKey(
|
||||
sid, reinterpret_cast<const uint8_t*>(wrapped_key_.data()),
|
||||
wrapped_key_.size());
|
||||
|
||||
sub_license_oec_sessions_[sub_session_key_id] = sid;
|
||||
if (key_session_->Type() != KeySession::kSubLicense) {
|
||||
key_session_.reset(new SubLicenseKeySession(
|
||||
sub_license_oec_sessions_, wrapped_key_, requested_security_level_));
|
||||
}
|
||||
return NO_ERROR;
|
||||
}
|
||||
|
||||
bool CryptoSession::GenerateSubSessionNonce(
|
||||
const std::string& sub_session_key_id, bool* exists, uint32_t* nonce) {
|
||||
if (!exists || !nonce) {
|
||||
LOGE("input parameter is null");
|
||||
return false;
|
||||
}
|
||||
|
||||
LOGV("CryptoSession::GenerateSubSessionNonce: Lock");
|
||||
AutoLock auto_lock(crypto_lock_);
|
||||
|
||||
SubLicenseSessionMap::iterator it =
|
||||
sub_license_oec_sessions_.find(sub_session_key_id);
|
||||
if (it == sub_license_oec_sessions_.end()) {
|
||||
// A subsession does not exist. Indicate that and return success.
|
||||
*exists = false;
|
||||
return true;
|
||||
}
|
||||
*exists = true;
|
||||
|
||||
OEMCryptoResult result;
|
||||
result = OEMCrypto_GenerateNonce(it->second, nonce);
|
||||
return OEMCrypto_SUCCESS == result;
|
||||
}
|
||||
|
||||
OEMCrypto_Algorithm CryptoSession::GenericSigningAlgorithm(
|
||||
CdmSigningAlgorithm algorithm) {
|
||||
if (kSigningAlgorithmHmacSha256 == algorithm) {
|
||||
@@ -1514,8 +1964,8 @@ OEMCryptoResult CryptoSession::CopyBufferInChunks(
|
||||
const size_t additional_offset =
|
||||
params.encrypt_length - remaining_encrypt_length;
|
||||
|
||||
// Update the remaining length of the original buffer only after calculating
|
||||
// the new values.
|
||||
// Update the remaining length of the original buffer only after
|
||||
// calculating the new values.
|
||||
remaining_encrypt_length -= chunk_size;
|
||||
|
||||
// Update the destination buffer with the new offset.
|
||||
@@ -1583,8 +2033,8 @@ OEMCryptoResult CryptoSession::DecryptInChunks(
|
||||
// calculating the new values.
|
||||
remaining_encrypt_length -= chunk_size;
|
||||
|
||||
// Update the destination buffer with the new offset. Because OEMCrypto can
|
||||
// modify the OEMCrypto_DestBufferDesc during the call to
|
||||
// Update the destination buffer with the new offset. Because OEMCrypto
|
||||
// can modify the OEMCrypto_DestBufferDesc during the call to
|
||||
// OEMCrypto_DecryptCENC, (and is known to do so on some platforms) a new
|
||||
// OEMCrypto_DestBufferDesc must be allocated for each call.
|
||||
OEMCrypto_DestBufferDesc buffer_descriptor = full_buffer_descriptor;
|
||||
@@ -1655,8 +2105,8 @@ OEMCryptoResult CryptoSession::DecryptInChunks(
|
||||
// For cbcs, we must look for the last encrypted block, which is
|
||||
// probably not the last block of the subsample. Luckily, since the
|
||||
// buffer size is guaranteed to be an even number of pattern
|
||||
// repetitions long, we can use the pattern to know how many blocks to
|
||||
// look back.
|
||||
// repetitions long, we can use the pattern to know how many blocks
|
||||
// to look back.
|
||||
block_end = buffer_end - kAes128BlockSize * pattern_descriptor.skip;
|
||||
}
|
||||
|
||||
|
||||
@@ -35,9 +35,9 @@ const int kDefaultNumJsonTokens = 128;
|
||||
namespace wvcdm {
|
||||
|
||||
// Protobuf generated classes.
|
||||
using video_widevine::WidevineCencHeader;
|
||||
using video_widevine::WidevineCencHeader_Algorithm;
|
||||
using video_widevine::WidevineCencHeader_Algorithm_AESCTR;
|
||||
using video_widevine::WidevinePsshData;
|
||||
using video_widevine::WidevinePsshData_Algorithm;
|
||||
using video_widevine::WidevinePsshData_Algorithm_AESCTR;
|
||||
|
||||
InitializationData::InitializationData(const std::string& type,
|
||||
const CdmInitData& data)
|
||||
@@ -70,6 +70,22 @@ InitializationData::InitializationData(const std::string& type,
|
||||
}
|
||||
}
|
||||
|
||||
// Parse the pssh data and return the embedded key data if it exists.
|
||||
std::vector<video_widevine::SubLicense>
|
||||
InitializationData::ExtractEmbeddedKeys() const {
|
||||
std::vector<video_widevine::SubLicense> keys;
|
||||
WidevinePsshData cenc_header;
|
||||
if (!is_cenc_ || !cenc_header.ParseFromString(data_) ||
|
||||
cenc_header.sub_licenses().size() == 0)
|
||||
return keys;
|
||||
|
||||
keys.reserve(cenc_header.sub_licenses().size());
|
||||
for (int i = 0; i < cenc_header.sub_licenses().size(); ++i) {
|
||||
keys.push_back(cenc_header.sub_licenses(i));
|
||||
}
|
||||
return keys;
|
||||
}
|
||||
|
||||
// Parse a blob of multiple concatenated PSSH atoms to extract the first
|
||||
// Widevine PSSH.
|
||||
bool InitializationData::ExtractWidevinePssh(const CdmInitData& init_data,
|
||||
@@ -339,8 +355,9 @@ bool InitializationData::ConstructWidevineInitData(
|
||||
return false;
|
||||
}
|
||||
if (method != kHlsMethodAes128 && method != kHlsMethodSampleAes) {
|
||||
LOGV("InitializationData::ConstructWidevineInitData: Invalid method"
|
||||
" parameter");
|
||||
LOGV(
|
||||
"InitializationData::ConstructWidevineInitData: Invalid method"
|
||||
" parameter");
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -466,10 +483,10 @@ bool InitializationData::ConstructWidevineInitData(
|
||||
}
|
||||
|
||||
// Now format as Widevine init data protobuf
|
||||
WidevineCencHeader cenc_header;
|
||||
WidevinePsshData cenc_header;
|
||||
// TODO(rfrias): The algorithm is a deprecated field, but proto changes
|
||||
// have not yet been pushed to production. Set until then.
|
||||
cenc_header.set_algorithm(WidevineCencHeader_Algorithm_AESCTR);
|
||||
cenc_header.set_algorithm(WidevinePsshData_Algorithm_AESCTR);
|
||||
for (size_t i = 0; i < key_ids.size(); ++i) {
|
||||
cenc_header.add_key_id(key_ids[i]);
|
||||
}
|
||||
|
||||
@@ -48,17 +48,41 @@ using video_widevine::ClientIdentification_NameValue;
|
||||
using video_widevine::DrmDeviceCertificate;
|
||||
using video_widevine::EncryptedClientIdentification;
|
||||
using video_widevine::License;
|
||||
using video_widevine::License_KeyContainer;
|
||||
using video_widevine::LicenseError;
|
||||
using video_widevine::LicenseIdentification;
|
||||
using video_widevine::LicenseRequest;
|
||||
using video_widevine::LicenseRequest_ContentIdentification;
|
||||
using video_widevine::LicenseRequest_ContentIdentification_CencDeprecated;
|
||||
using video_widevine::LicenseRequest_ContentIdentification_WebmDeprecated;
|
||||
using video_widevine::LicenseRequest_ContentIdentification_ExistingLicense;
|
||||
using video_widevine::LicenseRequest_ContentIdentification_WebmDeprecated;
|
||||
using video_widevine::License_KeyContainer;
|
||||
using video_widevine::SignedDrmDeviceCertificate;
|
||||
using video_widevine::SignedMessage;
|
||||
|
||||
static std::vector<CryptoKey> ExtractSubSessionKeys(const License& license) {
|
||||
std::vector<CryptoKey> key_array;
|
||||
|
||||
// Extract sub session key(s)
|
||||
for (int i = 0; i < license.key_size(); ++i) {
|
||||
CryptoKey key;
|
||||
switch (license.key(i).type()) {
|
||||
case License_KeyContainer::SUB_SESSION:
|
||||
key.set_key_data(license.key(i).key());
|
||||
key.set_key_data_iv(license.key(i).iv());
|
||||
key.set_key_id(license.key(i).id());
|
||||
key.set_track_label(license.key(i).track_label());
|
||||
key_array.push_back(key);
|
||||
break;
|
||||
|
||||
default:
|
||||
// Ignore all but SUB_SESSION key types.
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return key_array;
|
||||
}
|
||||
|
||||
static std::vector<CryptoKey> ExtractContentKeys(const License& license) {
|
||||
std::vector<CryptoKey> key_array;
|
||||
|
||||
@@ -87,6 +111,7 @@ static std::vector<CryptoKey> ExtractContentKeys(const License& license) {
|
||||
if (license.has_protection_scheme()) {
|
||||
four_cc = license.protection_scheme();
|
||||
}
|
||||
key.set_track_label(license.key(i).track_label());
|
||||
switch (four_cc) {
|
||||
// b/30713238: Android N assumed that the "protection scheme" Four
|
||||
// CC code, after being extracted from the protobuf, was host byte
|
||||
@@ -115,11 +140,28 @@ static std::vector<CryptoKey> ExtractContentKeys(const License& license) {
|
||||
}
|
||||
break;
|
||||
default:
|
||||
// Ignore SIGNING key types as they are not content related
|
||||
// Ignore SIGNING and SUB_SESSION key types as they are not content
|
||||
// related.
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<CryptoKey> sub_session_keys = ExtractSubSessionKeys(license);
|
||||
// Match the track label from the key arrays and add sub_license_key_id to
|
||||
// the content key array.
|
||||
LOGI("Received %d subsession keys", sub_session_keys.size());
|
||||
if (!sub_session_keys.empty()) {
|
||||
for (int i = 0; i < key_array.size(); ++i) {
|
||||
if (key_array[i].track_label().empty()) continue;
|
||||
for (int x = 0; x < sub_session_keys.size(); ++x) {
|
||||
if (sub_session_keys[x].track_label() == key_array[i].track_label()) {
|
||||
key_array[i].set_sub_session_key_id(sub_session_keys[x].key_id());
|
||||
key_array[i].set_sub_session_key(sub_session_keys[x].key_data());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return key_array;
|
||||
}
|
||||
|
||||
@@ -221,8 +263,8 @@ CdmResponseType CdmLicense::PrepareKeyRequest(
|
||||
status = PrepareClientId(app_parameters, &license_request);
|
||||
if (NO_ERROR != status) return status;
|
||||
|
||||
status = PrepareContentId(init_data, license_type, request_id,
|
||||
&license_request);
|
||||
status =
|
||||
PrepareContentId(init_data, license_type, request_id, &license_request);
|
||||
if (NO_ERROR != status) return status;
|
||||
|
||||
license_request.set_type(LicenseRequest::NEW);
|
||||
@@ -237,6 +279,37 @@ CdmResponseType CdmLicense::PrepareKeyRequest(
|
||||
}
|
||||
license_request.set_key_control_nonce(nonce);
|
||||
LOGD("PrepareKeyRequest: nonce=%u", nonce);
|
||||
|
||||
// Prepare the request for any embedded keys that may exist in the
|
||||
// initialization data.
|
||||
std::vector<video_widevine::SubLicense> embedded_key_data =
|
||||
init_data.ExtractEmbeddedKeys();
|
||||
for (int i = 0; i < embedded_key_data.size(); ++i) {
|
||||
bool exists = false;
|
||||
if (!crypto_session_->GenerateSubSessionNonce(
|
||||
embedded_key_data[i].sub_session_key_id(), &exists, &nonce)) {
|
||||
return LICENSE_REQUEST_NONCE_GENERATION_ERROR;
|
||||
}
|
||||
if (exists) {
|
||||
SignedMessage signed_sub_license;
|
||||
License_KeyContainer keyc;
|
||||
|
||||
// Parse the sub license for this track to extract the label.
|
||||
if (!signed_sub_license.ParseFromString(embedded_key_data[i].key_msg()) ||
|
||||
!keyc.ParseFromString(signed_sub_license.msg()) ||
|
||||
keyc.track_label().empty()) {
|
||||
return LICENSE_REQUEST_INVALID_SUBLICENSE;
|
||||
}
|
||||
|
||||
LicenseRequest::SubSessionData* sub_session_data =
|
||||
license_request.add_sub_session_data();
|
||||
sub_session_data->set_sub_session_key_id(
|
||||
embedded_key_data[i].sub_session_key_id());
|
||||
sub_session_data->set_nonce(nonce);
|
||||
sub_session_data->set_track_label(keyc.track_label());
|
||||
}
|
||||
}
|
||||
|
||||
license_request.set_protocol_version(video_widevine::VERSION_2_1);
|
||||
|
||||
// License request is complete. Serialize it.
|
||||
@@ -438,7 +511,6 @@ CdmResponseType CdmLicense::HandleKeyResponse(
|
||||
LOGE("CdmLicense::HandleKeyResponse: no session keys present");
|
||||
return SESSION_KEYS_NOT_FOUND;
|
||||
}
|
||||
|
||||
if (!crypto_session_->GenerateDerivedKeys(key_request_,
|
||||
signed_response.session_key()))
|
||||
return GENERATE_DERIVED_KEYS_ERROR;
|
||||
@@ -492,7 +564,8 @@ CdmResponseType CdmLicense::HandleKeyResponse(
|
||||
renew_with_client_id_ = license.policy().always_include_client_id();
|
||||
}
|
||||
|
||||
CdmResponseType resp = crypto_session_->LoadKeys(
|
||||
CdmResponseType resp = NO_ERROR;
|
||||
resp = crypto_session_->LoadKeys(
|
||||
signed_response.msg(), signed_response.signature(), mac_key_iv, mac_key,
|
||||
key_array, provider_session_token_);
|
||||
|
||||
@@ -587,6 +660,45 @@ CdmResponseType CdmLicense::HandleKeyUpdateResponse(
|
||||
}
|
||||
}
|
||||
|
||||
CdmResponseType CdmLicense::HandleSubLicense(
|
||||
const InitializationData& init_data) {
|
||||
std::vector<video_widevine::SubLicense> subkeys =
|
||||
init_data.ExtractEmbeddedKeys();
|
||||
std::set<KeyId> loaded_keys;
|
||||
// Build a license with the rotated keys.
|
||||
License license;
|
||||
for (int i = 0; i < subkeys.size(); ++i) {
|
||||
SignedMessage sm;
|
||||
if (!sm.ParseFromString(subkeys[i].key_msg())) {
|
||||
return LICENSE_REQUEST_INVALID_SUBLICENSE;
|
||||
}
|
||||
License_KeyContainer keyc;
|
||||
if (!keyc.ParseFromString(sm.msg())) {
|
||||
return LICENSE_REQUEST_INVALID_SUBLICENSE;
|
||||
}
|
||||
std::vector<CryptoKey> keys;
|
||||
keys.resize(1);
|
||||
keys[0].set_key_id(keyc.id());
|
||||
keys[0].set_key_data(keyc.key());
|
||||
keys[0].set_key_data_iv(keyc.iv());
|
||||
keys[0].set_key_control(keyc.key_control().key_control_block());
|
||||
keys[0].set_key_control_iv(keyc.key_control().iv());
|
||||
keys[0].set_track_label(keyc.track_label());
|
||||
CdmResponseType result = crypto_session_->LoadKeys(
|
||||
sm.msg(), sm.signature(), std::string(), std::string(), keys,
|
||||
std::string());
|
||||
if (result != KEY_ADDED) {
|
||||
return result;
|
||||
}
|
||||
loaded_keys.insert(keyc.id());
|
||||
*license.add_key() = keyc;
|
||||
}
|
||||
loaded_keys_.swap(loaded_keys);
|
||||
policy_engine_->UpdateLicenseKeys(license);
|
||||
|
||||
return KEY_MESSAGE;
|
||||
}
|
||||
|
||||
bool CdmLicense::RestoreOfflineLicense(
|
||||
const CdmKeyMessage& license_request,
|
||||
const CdmKeyResponse& license_response,
|
||||
@@ -994,8 +1106,7 @@ CdmResponseType CdmLicense::PrepareContentId(
|
||||
|
||||
template <typename T>
|
||||
bool CdmLicense::SetTypeAndId(CdmLicenseType license_type,
|
||||
const std::string& request_id,
|
||||
T* content_id) {
|
||||
const std::string& request_id, T* content_id) {
|
||||
switch (license_type) {
|
||||
case kLicenseTypeOffline:
|
||||
content_id->set_license_type(video_widevine::OFFLINE);
|
||||
|
||||
@@ -101,6 +101,7 @@ message License {
|
||||
CONTENT = 2;
|
||||
KEY_CONTROL = 3;
|
||||
OPERATOR_SESSION = 4;
|
||||
SUB_SESSION = 5;
|
||||
}
|
||||
|
||||
// The SecurityLevel enumeration allows the server to communicate the level
|
||||
@@ -199,6 +200,9 @@ message License {
|
||||
// supports anti rollback of the user table. Content provider can query the
|
||||
// client capabilities to determine if the client support this feature.
|
||||
optional bool anti_rollback_usage_table = 11 [default = false];
|
||||
// Optional not limited to commonly known track types such as SD, HD.
|
||||
// It can be some provider defined label to identify the track.
|
||||
optional string track_label = 12;
|
||||
}
|
||||
|
||||
optional LicenseIdentification id = 1;
|
||||
@@ -266,6 +270,19 @@ message LicenseRequest {
|
||||
//}
|
||||
}
|
||||
|
||||
message SubSessionData {
|
||||
// Required. The key ID for the corresponding SUB_SESSION_KEY. The
|
||||
// value must match the sub_session_key_id field for a
|
||||
// corresponding SubLicense message from the PSSH.
|
||||
optional string sub_session_key_id = 1;
|
||||
// Required. The nonce for the track.
|
||||
optional uint32 nonce = 2;
|
||||
// Required for initial license request used for each CONTENT key_container
|
||||
// to know which nonce to use for building its key control block.
|
||||
// Not needed for renewal license request.
|
||||
optional string track_label = 3;
|
||||
}
|
||||
|
||||
enum RequestType {
|
||||
NEW = 1;
|
||||
RENEWAL = 2;
|
||||
@@ -289,6 +306,9 @@ message LicenseRequest {
|
||||
optional uint32 key_control_nonce = 7;
|
||||
// Encrypted ClientIdentification message, used for privacy purposes.
|
||||
optional EncryptedClientIdentification encrypted_client_id = 8;
|
||||
// Optional sub session context information. Required for using
|
||||
// SubLicenses from the PSSH.
|
||||
repeated SubSessionData sub_session_data = 9;
|
||||
}
|
||||
|
||||
message LicenseError {
|
||||
@@ -302,6 +322,7 @@ message LicenseError {
|
||||
// or similar circumstances.
|
||||
SERVICE_UNAVAILABLE = 3;
|
||||
}
|
||||
|
||||
optional Error error_code = 1;
|
||||
}
|
||||
|
||||
@@ -406,7 +427,7 @@ message ProvisioningOptions {
|
||||
optional CertificateType certificate_type = 1 [default = WIDEVINE_DRM];
|
||||
|
||||
// Contains the application-specific name used to identify the certificate
|
||||
// authority for signing the generated certificate. This is required iff the
|
||||
// authority for signing the generated certificate. This is required if the
|
||||
// certificate type is X509.
|
||||
optional string certificate_authority = 2;
|
||||
}
|
||||
@@ -665,19 +686,33 @@ message ProvisionedDeviceInfo {
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// widevine_header.proto
|
||||
// widevine_pssh.proto
|
||||
// ----------------------------------------------------------------------------
|
||||
// Description:
|
||||
// Public protocol buffer definitions for Widevine Cenc Header
|
||||
// protocol.
|
||||
|
||||
message WidevineCencHeader {
|
||||
// Each SubLicense message represents a single content key. These keys can be
|
||||
// added to Widevine CENC initialization data to support both content grouping
|
||||
// and key rotation.
|
||||
message SubLicense {
|
||||
// Required. The key_id of a SUB_SESSION_KEY received in the master license.
|
||||
// SUB_SESSION_KEY is defined in the Widevine License Protocol.
|
||||
optional string sub_session_key_id = 1;
|
||||
|
||||
// Required. The key_msg contains the bytes of a serialized SignedMessage
|
||||
// proto. Internally the message field will contain a serialized KeyContainer
|
||||
// holding a single content key.
|
||||
optional bytes key_msg = 2;
|
||||
}
|
||||
|
||||
message WidevinePsshData {
|
||||
enum Algorithm {
|
||||
UNENCRYPTED = 0;
|
||||
AESCTR = 1;
|
||||
};
|
||||
// Replaced with protection_scheme.
|
||||
optional Algorithm algorithm = 1 [deprecated=true];
|
||||
optional Algorithm algorithm = 1;
|
||||
repeated bytes key_id = 2;
|
||||
|
||||
// Content provider name.
|
||||
@@ -702,10 +737,22 @@ message WidevineCencHeader {
|
||||
// serialized SignedMessage.
|
||||
optional bytes grouped_license = 8;
|
||||
|
||||
// Protection scheme identifying the encryption algorithm. Represented as one
|
||||
// of the following 4CC values: 'cenc' (AES-CTR), 'cbc1' (AES-CBC),
|
||||
// 'cens' (AES-CTR subsample), 'cbcs' (AES-CBC subsample).
|
||||
// Protection scheme identifying the encryption algorithm. The protection
|
||||
// scheme is represented as a uint32 value. The uint32 contains 4 bytes each
|
||||
// representing a single ascii character in one of the 4CC protection scheme
|
||||
// values.
|
||||
// 'cenc' (AES-CTR) protection_scheme = 0x63656E63,
|
||||
// 'cbc1' (AES-CBC) protection_scheme = 0x63626331,
|
||||
// 'cens' (AES-CTR subsample) protection_scheme = 0x63656E73,
|
||||
// 'cbcs' (AES-CBC subsample) protection_scheme = 0x63626373.
|
||||
optional uint32 protection_scheme = 9;
|
||||
|
||||
// Optional. For media using key rotation, this represents the duration
|
||||
// of each crypto period in seconds.
|
||||
optional uint32 crypto_period_seconds = 10;
|
||||
|
||||
// Required when using content keys that are embedded in content.
|
||||
repeated SubLicense sub_licenses = 11;
|
||||
}
|
||||
|
||||
// Signed device certificate definition.
|
||||
|
||||
@@ -14,20 +14,17 @@ extern "C" OEMCrypto_ProvisioningMethod OEMCrypto_GetProvisioningMethod() {
|
||||
}
|
||||
|
||||
extern "C" OEMCryptoResult OEMCrypto_GetOEMPublicCertificate(
|
||||
OEMCrypto_SESSION session, uint8_t* public_cert,
|
||||
size_t* public_cert_length) {
|
||||
OEMCrypto_SESSION, uint8_t* /* public_cert */,
|
||||
size_t* /* public_cert_length */) {
|
||||
return OEMCrypto_ERROR_NOT_IMPLEMENTED;
|
||||
}
|
||||
|
||||
extern "C" OEMCryptoResult OEMCrypto_RewrapDeviceRSAKey30(
|
||||
OEMCrypto_SESSION session,
|
||||
const uint32_t *nonce,
|
||||
const uint8_t* encrypted_message_key,
|
||||
size_t encrypted_message_key_length,
|
||||
const uint8_t* enc_rsa_key,
|
||||
size_t enc_rsa_key_length,
|
||||
const uint8_t* enc_rsa_key_iv,
|
||||
uint8_t* wrapped_rsa_key,
|
||||
size_t* wrapped_rsa_key_length) {
|
||||
OEMCrypto_SESSION, const uint32_t* /* nonce */,
|
||||
const uint8_t* /* encrypted_message_key */,
|
||||
size_t /* encrypted_message_key_length */,
|
||||
const uint8_t* /* enc_rsa_key */, size_t /* enc_rsa_key_length */,
|
||||
const uint8_t* /* enc_rsa_key_iv */, uint8_t* /* wrapped_rsa_key */,
|
||||
size_t* /* wrapped_rsa_key_length */) {
|
||||
return OEMCrypto_ERROR_NOT_IMPLEMENTED;
|
||||
}
|
||||
|
||||
@@ -159,6 +159,14 @@ void PolicyEngine::SetLicense(const License& license) {
|
||||
UpdateLicense(license);
|
||||
}
|
||||
|
||||
void PolicyEngine::UpdateLicenseKeys(const video_widevine::License& license) {
|
||||
// Use the current policy and set the new keys.
|
||||
video_widevine::License loadable = license;
|
||||
loadable.mutable_policy()->CopyFrom(policy_);
|
||||
license_keys_->SetFromLicense(loadable);
|
||||
NotifyKeysChange(kKeyStatusUsable);
|
||||
}
|
||||
|
||||
void PolicyEngine::SetLicenseForRelease(const License& license) {
|
||||
license_id_.Clear();
|
||||
license_id_.CopyFrom(license.id());
|
||||
|
||||
@@ -10,14 +10,28 @@
|
||||
#include <iostream>
|
||||
#include <vector>
|
||||
|
||||
#include <modp_b64.h>
|
||||
#include <modp_b64w.h>
|
||||
|
||||
#include "log.h"
|
||||
|
||||
namespace wvcdm {
|
||||
|
||||
static bool CharToDigit(char ch, unsigned char* digit) {
|
||||
static const char kBase64Codes[] =
|
||||
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
|
||||
|
||||
// Gets the low |n| bits of |in|.
|
||||
#define GET_LOW_BITS(in, n) ((in) & ((1 << (n)) - 1))
|
||||
// Gets the given (zero-indexed) bits [a, b) of |in|.
|
||||
#define GET_BITS(in, a, b) GET_LOW_BITS((in) >> (a), (b) - (a))
|
||||
// Calculates a/b using round-up division (only works for positive numbers).
|
||||
#define CEIL_DIVIDE(a, b) ((((a) - 1) / (b)) + 1)
|
||||
|
||||
int DecodeBase64Char(char c) {
|
||||
const char* it = strchr(kBase64Codes, c);
|
||||
if (it == NULL)
|
||||
return -1;
|
||||
return it - kBase64Codes;
|
||||
}
|
||||
|
||||
bool DecodeHexChar(char ch, unsigned char* digit) {
|
||||
if (ch >= '0' && ch <= '9') {
|
||||
*digit = ch - '0';
|
||||
} else {
|
||||
@@ -43,8 +57,8 @@ std::vector<uint8_t> a2b_hex(const std::string& byte) {
|
||||
for (unsigned int i = 0; i < count / 2; ++i) {
|
||||
unsigned char msb = 0; // most significant 4 bits
|
||||
unsigned char lsb = 0; // least significant 4 bits
|
||||
if (!CharToDigit(byte[i * 2], &msb) ||
|
||||
!CharToDigit(byte[i * 2 + 1], &lsb)) {
|
||||
if (!DecodeHexChar(byte[i * 2], &msb) ||
|
||||
!DecodeHexChar(byte[i * 2 + 1], &lsb)) {
|
||||
LOGE("Invalid hex value %c%c at index %d", byte[i * 2], byte[i * 2 + 1],
|
||||
i);
|
||||
return array;
|
||||
@@ -80,23 +94,50 @@ std::string b2a_hex(const std::string& byte) {
|
||||
}
|
||||
|
||||
// Encode for standard base64 encoding (RFC4648).
|
||||
// https://en.wikipedia.org/wiki/Base64
|
||||
// Text | M | a | n |
|
||||
// ASCI | 77 (0x4d) | 97 (0x61) | 110 (0x6e) |
|
||||
// Bits | 0 1 0 0 1 1 0 1 0 1 1 0 0 0 0 1 0 1 1 0 1 1 1 0 |
|
||||
// Index | 19 | 22 | 5 | 46 |
|
||||
// Base64 | T | W | F | u |
|
||||
// | <----------------- 24-bits -----------------> |
|
||||
std::string Base64Encode(const std::vector<uint8_t>& bin_input) {
|
||||
if (bin_input.empty()) {
|
||||
return std::string();
|
||||
}
|
||||
|
||||
int in_size = bin_input.size();
|
||||
std::string b64_output(modp_b64_encode_len(in_size), 0);
|
||||
// |temp| stores a 24-bit block that is treated as an array where insertions
|
||||
// occur from high to low.
|
||||
uint32_t temp = 0;
|
||||
size_t out_index = 0;
|
||||
const size_t out_size = CEIL_DIVIDE(bin_input.size(), 3) * 4;
|
||||
std::string result(out_size, '\0');
|
||||
for (size_t i = 0; i < bin_input.size(); i++) {
|
||||
// "insert" 8-bits of data
|
||||
temp |= (bin_input[i] << ((2 - (i % 3)) * 8));
|
||||
|
||||
int out_size = modp_b64_encode(
|
||||
&b64_output[0], reinterpret_cast<const char*>(&bin_input[0]), in_size);
|
||||
if (out_size == -1) {
|
||||
LOGE("Base64Encode failed");
|
||||
return std::string();
|
||||
if (i % 3 == 2) {
|
||||
result[out_index++] = kBase64Codes[GET_BITS(temp, 18, 24)];
|
||||
result[out_index++] = kBase64Codes[GET_BITS(temp, 12, 18)];
|
||||
result[out_index++] = kBase64Codes[GET_BITS(temp, 6, 12)];
|
||||
result[out_index++] = kBase64Codes[GET_BITS(temp, 0, 6)];
|
||||
temp = 0;
|
||||
}
|
||||
}
|
||||
|
||||
b64_output.resize(out_size);
|
||||
return b64_output;
|
||||
if (bin_input.size() % 3 == 1) {
|
||||
result[out_index++] = kBase64Codes[GET_BITS(temp, 18, 24)];
|
||||
result[out_index++] = kBase64Codes[GET_BITS(temp, 12, 18)];
|
||||
result[out_index++] = '=';
|
||||
result[out_index++] = '=';
|
||||
} else if (bin_input.size() % 3 == 2) {
|
||||
result[out_index++] = kBase64Codes[GET_BITS(temp, 18, 24)];
|
||||
result[out_index++] = kBase64Codes[GET_BITS(temp, 12, 18)];
|
||||
result[out_index++] = kBase64Codes[GET_BITS(temp, 6, 12)];
|
||||
result[out_index++] = '=';
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// Filename-friendly base64 encoding (RFC4648), commonly referred to
|
||||
@@ -111,18 +152,14 @@ std::string Base64SafeEncode(const std::vector<uint8_t>& bin_input) {
|
||||
return std::string();
|
||||
}
|
||||
|
||||
int in_size = bin_input.size();
|
||||
std::string b64_output(modp_b64w_encode_len(in_size), 0);
|
||||
|
||||
int out_size = modp_b64w_encode(
|
||||
&b64_output[0], reinterpret_cast<const char*>(&bin_input[0]), in_size);
|
||||
if (out_size == -1) {
|
||||
LOGE("Base64SafeEncode failed");
|
||||
return std::string();
|
||||
std::string ret = Base64Encode(bin_input);
|
||||
for (size_t i = 0; i < ret.size(); i++) {
|
||||
if (ret[i] == '+')
|
||||
ret[i] = '-';
|
||||
else if (ret[i] == '/')
|
||||
ret[i] = '_';
|
||||
}
|
||||
|
||||
b64_output.resize(out_size);
|
||||
return b64_output;
|
||||
return ret;
|
||||
}
|
||||
|
||||
std::string Base64SafeEncodeNoPad(const std::vector<uint8_t>& bin_input) {
|
||||
@@ -138,17 +175,57 @@ std::vector<uint8_t> Base64Decode(const std::string& b64_input) {
|
||||
return std::vector<uint8_t>();
|
||||
}
|
||||
|
||||
int in_size = b64_input.size();
|
||||
std::vector<uint8_t> bin_output(modp_b64_decode_len(in_size), 0);
|
||||
int out_size = modp_b64_decode(reinterpret_cast<char*>(&bin_output[0]),
|
||||
b64_input.data(), in_size);
|
||||
if (out_size == -1) {
|
||||
LOGE("Base64Decode failed");
|
||||
return std::vector<uint8_t>(0);
|
||||
const size_t out_size_max = CEIL_DIVIDE(b64_input.size() * 3, 4);
|
||||
std::vector<uint8_t> result(out_size_max, '\0');
|
||||
|
||||
// |temp| stores 24-bits of data that is treated as an array where insertions
|
||||
// occur from high to low.
|
||||
uint32_t temp = 0;
|
||||
size_t out_index = 0;
|
||||
size_t i;
|
||||
for (i = 0; i < b64_input.size(); i++) {
|
||||
if (b64_input[i] == '=') {
|
||||
// Verify an '=' only appears at the end. We want i to remain at the
|
||||
// first '=', so we need an inner loop.
|
||||
for (size_t j = i; j < b64_input.size(); j++) {
|
||||
if (b64_input[j] != '=') {
|
||||
LOGE("base64Decode failed");
|
||||
return std::vector<uint8_t>();
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
const int decoded = DecodeBase64Char(b64_input[i]);
|
||||
if (decoded < 0) {
|
||||
LOGE("base64Decode failed");
|
||||
return std::vector<uint8_t>();
|
||||
}
|
||||
// "insert" 6-bits of data
|
||||
temp |= (decoded << ((3 - (i % 4)) * 6));
|
||||
|
||||
if (i % 4 == 3) {
|
||||
result[out_index++] = GET_BITS(temp, 16, 24);
|
||||
result[out_index++] = GET_BITS(temp, 8, 16);
|
||||
result[out_index++] = GET_BITS(temp, 0, 8);
|
||||
temp = 0;
|
||||
}
|
||||
}
|
||||
|
||||
bin_output.resize(out_size);
|
||||
return bin_output;
|
||||
switch (i % 4) {
|
||||
case 1:
|
||||
LOGE("base64Decode failed");
|
||||
return std::vector<uint8_t>();
|
||||
case 2:
|
||||
result[out_index++] = GET_BITS(temp, 16, 24);
|
||||
break;
|
||||
case 3:
|
||||
result[out_index++] = GET_BITS(temp, 16, 24);
|
||||
result[out_index++] = GET_BITS(temp, 8, 16);
|
||||
break;
|
||||
}
|
||||
result.resize(out_index);
|
||||
return result;
|
||||
}
|
||||
|
||||
// Decode for Filename-friendly base64 encoding (RFC4648), commonly referred
|
||||
@@ -158,22 +235,16 @@ std::vector<uint8_t> Base64SafeDecode(const std::string& b64_input) {
|
||||
return std::vector<uint8_t>();
|
||||
}
|
||||
|
||||
std::string b64_padded(b64_input);
|
||||
while (b64_padded.size() % 4 != 0) {
|
||||
b64_padded = b64_padded + "=";
|
||||
// Make a copy so we can modify it to replace the web-safe special characters
|
||||
// with the normal ones.
|
||||
std::string input_copy = b64_input;
|
||||
for (size_t i = 0; i < input_copy.size(); i++) {
|
||||
if (input_copy[i] == '-')
|
||||
input_copy[i] = '+';
|
||||
else if (input_copy[i] == '_')
|
||||
input_copy[i] = '/';
|
||||
}
|
||||
|
||||
int in_size = b64_padded.size();
|
||||
std::vector<uint8_t> bin_output(modp_b64w_decode_len(in_size), 0);
|
||||
int out_size = modp_b64w_decode(reinterpret_cast<char*>(&bin_output[0]),
|
||||
b64_padded.data(), in_size);
|
||||
if (out_size == -1) {
|
||||
LOGE("Base64SafeDecode failed");
|
||||
return std::vector<uint8_t>(0);
|
||||
}
|
||||
|
||||
bin_output.resize(out_size);
|
||||
return bin_output;
|
||||
return Base64Decode(input_copy);
|
||||
}
|
||||
|
||||
std::string HexEncode(const uint8_t* in_buffer, unsigned int size) {
|
||||
|
||||
Reference in New Issue
Block a user