This reverts commit f6f5099604.
Reason for revert: Feature missed deadline
Bug: 135283522
Change-Id: Ic86930ee3444c5a6aa1d78ae3a12a9030c29ef92
1882 lines
71 KiB
C++
1882 lines
71 KiB
C++
// Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary
|
|
// source code may only be used and distributed under the Widevine
|
|
// License Agreement.
|
|
//
|
|
// Reference implementation of OEMCrypto APIs
|
|
//
|
|
#include "oemcrypto_session.h"
|
|
|
|
#include <assert.h>
|
|
#include <string.h>
|
|
#include <algorithm>
|
|
#include <iostream>
|
|
#include <vector>
|
|
|
|
#include <openssl/aes.h>
|
|
#include <openssl/bio.h>
|
|
#include <openssl/cmac.h>
|
|
#include <openssl/crypto.h>
|
|
#include <openssl/err.h>
|
|
#include <openssl/evp.h>
|
|
#include <openssl/hmac.h>
|
|
#include <openssl/rand.h>
|
|
#include <openssl/rsa.h>
|
|
#include <openssl/sha.h>
|
|
#include <openssl/x509.h>
|
|
|
|
#include "advance_iv_ctr.h"
|
|
#include "disallow_copy_and_assign.h"
|
|
#include "keys.h"
|
|
#include "log.h"
|
|
#include "odk.h"
|
|
#include "oemcrypto_engine_ref.h"
|
|
#include "oemcrypto_key_ref.h"
|
|
#include "oemcrypto_rsa_key_shared.h"
|
|
#include "oemcrypto_types.h"
|
|
#include "platform.h"
|
|
#include "string_conversions.h"
|
|
#include "wvcrc32.h"
|
|
|
|
static const int kPssSaltLength = 20;
|
|
|
|
namespace {
|
|
|
|
// Increment counter for AES-CTR. The CENC spec specifies we increment only
|
|
// the low 64 bits of the IV counter, and leave the high 64 bits alone.
|
|
void ctr128_inc64(uint8_t* counter) {
|
|
uint32_t n = 16;
|
|
do {
|
|
if (++counter[--n] != 0) return;
|
|
} while (n > 8);
|
|
}
|
|
|
|
void advance_dest_buffer(OEMCrypto_DestBufferDesc* dest_buffer, size_t bytes) {
|
|
switch (dest_buffer->type) {
|
|
case OEMCrypto_BufferType_Clear:
|
|
dest_buffer->buffer.clear.address += bytes;
|
|
dest_buffer->buffer.clear.address_length -= bytes;
|
|
break;
|
|
|
|
case OEMCrypto_BufferType_Secure:
|
|
dest_buffer->buffer.secure.offset += bytes;
|
|
break;
|
|
|
|
case OEMCrypto_BufferType_Direct:
|
|
// Nothing to do for this buffer type.
|
|
break;
|
|
}
|
|
}
|
|
|
|
} // namespace
|
|
|
|
namespace wvoec_ref {
|
|
|
|
/***************************************/
|
|
|
|
class ContentKeysContext : public SessionContextKeys {
|
|
public:
|
|
explicit ContentKeysContext() {}
|
|
~ContentKeysContext() override {}
|
|
size_t size() override { return session_keys_.size(); }
|
|
bool Insert(const KeyId& key_id, const Key& key_data) override;
|
|
Key* Find(const KeyId& key_id) override;
|
|
Key* FirstKey() override;
|
|
void Remove(const KeyId& key_id) override;
|
|
void UpdateDuration(const KeyControlBlock& control) override;
|
|
|
|
OEMCrypto_LicenseType type() override { return OEMCrypto_ContentLicense; }
|
|
|
|
bool SetContentKey(const KeyId& entitlement_id, const KeyId& content_key_id,
|
|
const std::vector<uint8_t>& content_key) override;
|
|
EntitlementKey* GetEntitlementKey(const KeyId& entitlement_id) override;
|
|
|
|
private:
|
|
SessionKeyTable session_keys_;
|
|
CORE_DISALLOW_COPY_AND_ASSIGN(ContentKeysContext);
|
|
};
|
|
|
|
bool ContentKeysContext::Insert(const KeyId& key_id, const Key& key_data) {
|
|
return session_keys_.Insert(key_id, key_data);
|
|
}
|
|
|
|
Key* ContentKeysContext::Find(const KeyId& key_id) {
|
|
return session_keys_.Find(key_id);
|
|
}
|
|
|
|
Key* ContentKeysContext::FirstKey() { return session_keys_.FirstKey(); }
|
|
|
|
void ContentKeysContext::Remove(const KeyId& key_id) {
|
|
session_keys_.Remove(key_id);
|
|
}
|
|
|
|
void ContentKeysContext::UpdateDuration(const KeyControlBlock& control) {
|
|
session_keys_.UpdateDuration(control);
|
|
}
|
|
|
|
bool ContentKeysContext::SetContentKey(
|
|
const KeyId& entitlement_id, const KeyId& content_key_id,
|
|
const std::vector<uint8_t>& content_key) {
|
|
// Unsupported action for this type.
|
|
return false;
|
|
}
|
|
|
|
EntitlementKey* ContentKeysContext::GetEntitlementKey(
|
|
const KeyId& entitlement_id) {
|
|
// Unsupported action for this type.
|
|
return nullptr;
|
|
}
|
|
|
|
/***************************************/
|
|
|
|
class EntitlementKeysContext : public SessionContextKeys {
|
|
public:
|
|
EntitlementKeysContext() {}
|
|
~EntitlementKeysContext() override {}
|
|
size_t size() override { return session_keys_.size(); }
|
|
bool Insert(const KeyId& key_id, const Key& key_data) override;
|
|
Key* Find(const KeyId& key_id) override;
|
|
Key* FirstKey() override;
|
|
void Remove(const KeyId& key_id) override;
|
|
void UpdateDuration(const KeyControlBlock& control) override;
|
|
bool SetContentKey(const KeyId& entitlement_id, const KeyId& content_key_id,
|
|
const std::vector<uint8_t>& content_key) override;
|
|
EntitlementKey* GetEntitlementKey(const KeyId& entitlement_id) override;
|
|
|
|
OEMCrypto_LicenseType type() override { return OEMCrypto_EntitlementLicense; }
|
|
|
|
private:
|
|
EntitlementKeyTable session_keys_;
|
|
CORE_DISALLOW_COPY_AND_ASSIGN(EntitlementKeysContext);
|
|
};
|
|
|
|
bool EntitlementKeysContext::Insert(const KeyId& key_id, const Key& key_data) {
|
|
return session_keys_.Insert(key_id, key_data);
|
|
}
|
|
|
|
Key* EntitlementKeysContext::Find(const KeyId& key_id) {
|
|
return session_keys_.Find(key_id);
|
|
}
|
|
|
|
Key* EntitlementKeysContext::FirstKey() { return session_keys_.FirstKey(); }
|
|
|
|
void EntitlementKeysContext::Remove(const KeyId& key_id) {
|
|
session_keys_.Remove(key_id);
|
|
}
|
|
|
|
void EntitlementKeysContext::UpdateDuration(const KeyControlBlock& control) {
|
|
session_keys_.UpdateDuration(control);
|
|
}
|
|
|
|
bool EntitlementKeysContext::SetContentKey(
|
|
const KeyId& entitlement_id, const KeyId& content_key_id,
|
|
const std::vector<uint8_t>& content_key) {
|
|
return session_keys_.SetContentKey(entitlement_id, content_key_id,
|
|
content_key);
|
|
}
|
|
|
|
EntitlementKey* EntitlementKeysContext::GetEntitlementKey(
|
|
const KeyId& entitlement_id) {
|
|
return session_keys_.GetEntitlementKey(entitlement_id);
|
|
}
|
|
|
|
/***************************************/
|
|
|
|
SessionContext::SessionContext(CryptoEngine* ce, SessionId sid,
|
|
const RSA_shared_ptr& rsa_key)
|
|
: valid_(true),
|
|
ce_(ce),
|
|
id_(sid),
|
|
current_content_key_(nullptr),
|
|
session_keys_(nullptr),
|
|
license_request_hash_(),
|
|
rsa_key_(rsa_key),
|
|
allowed_schemes_(kSign_RSASSA_PSS),
|
|
decrypt_started_(false),
|
|
timer_limits_(),
|
|
clock_values_(),
|
|
usage_entry_(nullptr),
|
|
srm_requirements_status_(NoSRMVersion),
|
|
usage_entry_status_(kNoUsageEntry),
|
|
compute_hash_(false),
|
|
current_hash_(0),
|
|
bad_frame_number_(0),
|
|
hash_error_(OEMCrypto_SUCCESS),
|
|
state_nonce_created_(false),
|
|
state_request_signed_(false),
|
|
state_response_loaded_(false) {
|
|
ODK_InitializeSessionValues(&timer_limits_, &clock_values_, &nonce_values_,
|
|
CryptoEngine::kApiVersion, sid);
|
|
}
|
|
|
|
SessionContext::~SessionContext() {}
|
|
|
|
// Internal utility function to derive key using CMAC-128
|
|
bool SessionContext::DeriveKey(const std::vector<uint8_t>& key,
|
|
const std::vector<uint8_t>& context, int counter,
|
|
std::vector<uint8_t>* out) {
|
|
if (key.empty() || counter > 4 || context.empty() || out == nullptr) {
|
|
LOGE("[DeriveKey(): OEMCrypto_ERROR_INVALID_CONTEXT]");
|
|
return false;
|
|
}
|
|
|
|
const EVP_CIPHER* cipher = EVP_aes_128_cbc();
|
|
CMAC_CTX* cmac_ctx = CMAC_CTX_new();
|
|
|
|
if (!cmac_ctx) {
|
|
LOGE("[DeriveKey(): OEMCrypto_ERROR_CMAC_FAILURE]");
|
|
return false;
|
|
}
|
|
|
|
if (!CMAC_Init(cmac_ctx, &key[0], key.size(), cipher, 0)) {
|
|
LOGE("[DeriveKey(): OEMCrypto_ERROR_CMAC_FAILURE]");
|
|
CMAC_CTX_free(cmac_ctx);
|
|
return false;
|
|
}
|
|
|
|
std::vector<uint8_t> message;
|
|
message.push_back(counter);
|
|
message.insert(message.end(), context.begin(), context.end());
|
|
|
|
if (!CMAC_Update(cmac_ctx, &message[0], message.size())) {
|
|
LOGE("[DeriveKey(): OEMCrypto_ERROR_CMAC_FAILURE]");
|
|
CMAC_CTX_free(cmac_ctx);
|
|
return false;
|
|
}
|
|
|
|
size_t reslen;
|
|
uint8_t res[128];
|
|
if (!CMAC_Final(cmac_ctx, res, &reslen)) {
|
|
LOGE("[DeriveKey(): OEMCrypto_ERROR_CMAC_FAILURE]");
|
|
CMAC_CTX_free(cmac_ctx);
|
|
return false;
|
|
}
|
|
|
|
out->assign(res, res + reslen);
|
|
|
|
CMAC_CTX_free(cmac_ctx);
|
|
|
|
return true;
|
|
}
|
|
|
|
bool SessionContext::DeriveKeys(const std::vector<uint8_t>& master_key,
|
|
const std::vector<uint8_t>& mac_key_context,
|
|
const std::vector<uint8_t>& enc_key_context) {
|
|
// Generate derived key for mac key
|
|
std::vector<uint8_t> mac_key_server;
|
|
std::vector<uint8_t> mac_key_client;
|
|
std::vector<uint8_t> mac_key_part2;
|
|
if (!DeriveKey(master_key, mac_key_context, 1, &mac_key_server)) {
|
|
return false;
|
|
}
|
|
if (!DeriveKey(master_key, mac_key_context, 2, &mac_key_part2)) {
|
|
return false;
|
|
}
|
|
mac_key_server.insert(mac_key_server.end(), mac_key_part2.begin(),
|
|
mac_key_part2.end());
|
|
|
|
if (!DeriveKey(master_key, mac_key_context, 3, &mac_key_client)) {
|
|
return false;
|
|
}
|
|
if (!DeriveKey(master_key, mac_key_context, 4, &mac_key_part2)) {
|
|
return false;
|
|
}
|
|
mac_key_client.insert(mac_key_client.end(), mac_key_part2.begin(),
|
|
mac_key_part2.end());
|
|
|
|
// Generate derived key for encryption key
|
|
std::vector<uint8_t> enc_key;
|
|
if (!DeriveKey(master_key, enc_key_context, 1, &enc_key)) {
|
|
return false;
|
|
}
|
|
|
|
set_mac_key_server(mac_key_server);
|
|
set_mac_key_client(mac_key_client);
|
|
set_encryption_key(enc_key);
|
|
return true;
|
|
}
|
|
|
|
bool SessionContext::RSADeriveKeys(
|
|
const std::vector<uint8_t>& enc_session_key,
|
|
const std::vector<uint8_t>& mac_key_context,
|
|
const std::vector<uint8_t>& enc_key_context) {
|
|
if (!rsa_key()) {
|
|
LOGE("[RSADeriveKeys(): no RSA key set]");
|
|
return false;
|
|
}
|
|
const size_t actual_key_size = static_cast<size_t>(RSA_size(rsa_key()));
|
|
if (enc_session_key.size() != actual_key_size) {
|
|
LOGE(
|
|
"[RSADeriveKeys(): encrypted session key wrong size: %zu, expected "
|
|
"%zu]",
|
|
enc_session_key.size(), actual_key_size);
|
|
dump_boringssl_error();
|
|
return false;
|
|
}
|
|
session_key_.resize(RSA_size(rsa_key()));
|
|
const int decrypted_size =
|
|
RSA_private_decrypt(enc_session_key.size(), &enc_session_key[0],
|
|
&session_key_[0], rsa_key(), RSA_PKCS1_OAEP_PADDING);
|
|
if (-1 == decrypted_size) {
|
|
LOGE("[RSADeriveKeys(): error decrypting session key.]");
|
|
dump_boringssl_error();
|
|
return false;
|
|
}
|
|
session_key_.resize(decrypted_size);
|
|
if (decrypted_size != static_cast<int>(wvoec::KEY_SIZE)) {
|
|
LOGE("[RSADeriveKeys(): error. Session key is wrong size: %d.]",
|
|
decrypted_size);
|
|
dump_boringssl_error();
|
|
session_key_.clear();
|
|
return false;
|
|
}
|
|
return DeriveKeys(session_key_, mac_key_context, enc_key_context);
|
|
}
|
|
|
|
OEMCryptoResult SessionContext::PrepAndSignLicenseRequest(
|
|
uint8_t* message, size_t message_length, size_t* core_message_length,
|
|
uint8_t* signature, size_t* signature_length) {
|
|
if (signature_length == nullptr || core_message_length == nullptr) {
|
|
return OEMCrypto_ERROR_INVALID_CONTEXT;
|
|
}
|
|
const size_t required_signature_size = CertSignatureSize();
|
|
OEMCryptoResult result = ODK_PrepareCoreLicenseRequest(
|
|
message, message_length, core_message_length, &nonce_values_);
|
|
if (*signature_length < required_signature_size ||
|
|
result == OEMCrypto_ERROR_SHORT_BUFFER) {
|
|
*signature_length = required_signature_size;
|
|
return OEMCrypto_ERROR_SHORT_BUFFER;
|
|
}
|
|
if (result != OEMCrypto_SUCCESS) {
|
|
LOGE("ODK error: %d", static_cast<int>(result));
|
|
return result;
|
|
}
|
|
if (message == nullptr || message_length < *core_message_length ||
|
|
signature == nullptr) {
|
|
LOGE("OEMCrypto_ERROR_INVALID_CONTEXT");
|
|
return OEMCrypto_ERROR_INVALID_CONTEXT;
|
|
}
|
|
if (state_request_signed_) {
|
|
LOGE("Attempt to sign two license requests");
|
|
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
|
}
|
|
// For backwards compatibility, we only sign the message body, and we compute
|
|
// a SHA256 of the core message.
|
|
SHA256(message, *core_message_length, license_request_hash_);
|
|
const uint8_t* message_body = message + *core_message_length;
|
|
const size_t message_body_length = message_length - *core_message_length;
|
|
result = GenerateCertSignature(message_body, message_body_length, signature,
|
|
signature_length);
|
|
if (result == OEMCrypto_SUCCESS) {
|
|
state_request_signed_ = true;
|
|
result = ODK_InitializeClockValues(&clock_values_, ce_->SystemTime());
|
|
}
|
|
return result;
|
|
}
|
|
|
|
OEMCryptoResult SessionContext::PrepAndSignRenewalRequest(
|
|
uint8_t* message, size_t message_length, size_t* core_message_length,
|
|
uint8_t* signature, size_t* signature_length) {
|
|
if (signature_length == nullptr || core_message_length == nullptr) {
|
|
return OEMCrypto_ERROR_INVALID_CONTEXT;
|
|
}
|
|
// If we have signed a request, but have not loaded it, something is wrong.
|
|
// On the other hand, we can sign a license release using the mac keys from
|
|
// the usage table. So it is OK if we have never signed a license request.
|
|
if (state_request_signed_ && !state_response_loaded_) {
|
|
LOGE("Attempt to sign renewal before load");
|
|
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
|
}
|
|
const size_t required_signature_size = SHA256_DIGEST_LENGTH;
|
|
const uint64_t now = ce_->SystemTime();
|
|
const OEMCryptoResult result = ODK_PrepareCoreRenewalRequest(
|
|
message, message_length, core_message_length, &nonce_values_,
|
|
&clock_values_, now);
|
|
if (*signature_length < required_signature_size ||
|
|
result == OEMCrypto_ERROR_SHORT_BUFFER) {
|
|
*signature_length = required_signature_size;
|
|
return OEMCrypto_ERROR_SHORT_BUFFER;
|
|
}
|
|
if (result != OEMCrypto_SUCCESS) {
|
|
LOGE("ODK error: %d", static_cast<int>(result));
|
|
return result;
|
|
}
|
|
if (message == nullptr || message_length < *core_message_length ||
|
|
signature == nullptr) {
|
|
LOGE("OEMCrypto_ERROR_INVALID_CONTEXT");
|
|
return OEMCrypto_ERROR_INVALID_CONTEXT;
|
|
}
|
|
// If we are talking to an old license server, then we only sign the message
|
|
// body.
|
|
if (nonce_values_.api_major_version < 16) {
|
|
const uint8_t* message_body = message + *core_message_length;
|
|
const size_t message_body_length = message_length - *core_message_length;
|
|
return GenerateSignature(message_body, message_body_length, signature,
|
|
signature_length);
|
|
} else {
|
|
return GenerateSignature(message, message_length, signature,
|
|
signature_length);
|
|
}
|
|
}
|
|
|
|
OEMCryptoResult SessionContext::PrepAndSignProvisioningRequest(
|
|
uint8_t* message, size_t message_length, size_t* core_message_length,
|
|
uint8_t* signature, size_t* signature_length) {
|
|
if (signature_length == nullptr || core_message_length == nullptr) {
|
|
return OEMCrypto_ERROR_INVALID_CONTEXT;
|
|
}
|
|
if (state_request_signed_) {
|
|
LOGE("Attempt to sign prov request after license request");
|
|
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
|
}
|
|
const size_t required_signature_size = ROTSignatureSize();
|
|
if (required_signature_size == 0) return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
|
const std::vector<uint8_t> device_id = ce_->DeviceRootId();
|
|
OEMCryptoResult result = ODK_PrepareCoreProvisioningRequest(
|
|
message, message_length, core_message_length, &nonce_values_,
|
|
device_id.data(), device_id.size());
|
|
if (*signature_length < required_signature_size ||
|
|
result == OEMCrypto_ERROR_SHORT_BUFFER) {
|
|
*signature_length = required_signature_size;
|
|
return OEMCrypto_ERROR_SHORT_BUFFER;
|
|
}
|
|
if (result != OEMCrypto_SUCCESS) {
|
|
LOGE("ODK error: %d", static_cast<int>(result));
|
|
return result;
|
|
}
|
|
if (message == nullptr || message_length == 0 || signature == nullptr) {
|
|
LOGE("OEMCrypto_ERROR_INVALID_CONTEXT");
|
|
return OEMCrypto_ERROR_INVALID_CONTEXT;
|
|
}
|
|
if (ce_->config_provisioning_method() == OEMCrypto_Keybox) {
|
|
result =
|
|
GenerateSignature(message, message_length, signature, signature_length);
|
|
} else if (ce_->config_provisioning_method() == OEMCrypto_OEMCertificate) {
|
|
result = GenerateCertSignature(message, message_length, signature,
|
|
signature_length);
|
|
} else {
|
|
LOGE("Bad prov method = %d",
|
|
static_cast<int>(ce_->config_provisioning_method()));
|
|
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
|
}
|
|
if (result == OEMCrypto_SUCCESS) state_request_signed_ = true;
|
|
return result;
|
|
}
|
|
|
|
// Utility function to generate a message signature
|
|
OEMCryptoResult SessionContext::GenerateSignature(const uint8_t* message,
|
|
size_t message_length,
|
|
uint8_t* signature,
|
|
size_t* signature_length) {
|
|
if (message == nullptr || message_length == 0 || signature == nullptr ||
|
|
signature_length == nullptr) {
|
|
LOGE("OEMCrypto_ERROR_INVALID_CONTEXT");
|
|
return OEMCrypto_ERROR_INVALID_CONTEXT;
|
|
}
|
|
if (mac_key_client_.size() != wvoec::MAC_KEY_SIZE) {
|
|
return OEMCrypto_ERROR_INVALID_CONTEXT;
|
|
}
|
|
if (*signature_length != SHA256_DIGEST_LENGTH) {
|
|
*signature_length = SHA256_DIGEST_LENGTH;
|
|
return OEMCrypto_ERROR_SHORT_BUFFER;
|
|
}
|
|
unsigned int md_len = *signature_length;
|
|
if (HMAC(EVP_sha256(), &mac_key_client_[0], wvoec::MAC_KEY_SIZE, message,
|
|
message_length, signature, &md_len)) {
|
|
*signature_length = md_len;
|
|
return OEMCrypto_SUCCESS;
|
|
}
|
|
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
|
}
|
|
|
|
// This is ussd when the device is a cast receiver.
|
|
size_t SessionContext::RSASignatureSize() {
|
|
if (!rsa_key()) {
|
|
LOGE("no RSA key set");
|
|
return 0;
|
|
}
|
|
return static_cast<size_t>(RSA_size(rsa_key()));
|
|
}
|
|
|
|
size_t SessionContext::CertSignatureSize() {
|
|
// TODO(b/67735947): Add ECC cert support.
|
|
if (!rsa_key()) {
|
|
LOGE("No private key set");
|
|
return 0;
|
|
}
|
|
return static_cast<size_t>(RSA_size(rsa_key()));
|
|
}
|
|
|
|
size_t SessionContext::ROTSignatureSize() {
|
|
if (ce_->config_provisioning_method() == OEMCrypto_Keybox)
|
|
return SHA256_DIGEST_LENGTH;
|
|
if (ce_->config_provisioning_method() == OEMCrypto_OEMCertificate)
|
|
return CertSignatureSize();
|
|
LOGE("Bad prov method = %d",
|
|
static_cast<int>(ce_->config_provisioning_method()));
|
|
return 0;
|
|
}
|
|
|
|
OEMCryptoResult SessionContext::GenerateCertSignature(
|
|
const uint8_t* message, size_t message_length, uint8_t* signature,
|
|
size_t* signature_length) {
|
|
// TODO(b/67735947): Add ECC cert support.
|
|
if (message == nullptr || message_length == 0 || signature == nullptr ||
|
|
signature_length == 0) {
|
|
LOGE("OEMCrypto_ERROR_INVALID_CONTEXT");
|
|
return OEMCrypto_ERROR_INVALID_CONTEXT;
|
|
}
|
|
if (!rsa_key()) {
|
|
LOGE("No RSA key set");
|
|
return OEMCrypto_ERROR_INVALID_RSA_KEY;
|
|
}
|
|
if (*signature_length < static_cast<size_t>(RSA_size(rsa_key()))) {
|
|
*signature_length = CertSignatureSize();
|
|
return OEMCrypto_ERROR_SHORT_BUFFER;
|
|
}
|
|
if (allowed_schemes_ != kSign_RSASSA_PSS) {
|
|
LOGE("Message signing not allowed");
|
|
return OEMCrypto_ERROR_INVALID_RSA_KEY;
|
|
}
|
|
|
|
// Hash the message using SHA1.
|
|
uint8_t hash[SHA_DIGEST_LENGTH];
|
|
if (!SHA1(message, message_length, hash)) {
|
|
LOGE("Error creating signature hash");
|
|
dump_boringssl_error();
|
|
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
|
}
|
|
|
|
// Add PSS padding.
|
|
std::vector<uint8_t> padded_digest(*signature_length);
|
|
int status = RSA_padding_add_PKCS1_PSS_mgf1(
|
|
rsa_key(), &padded_digest[0], hash, EVP_sha1(), nullptr, kPssSaltLength);
|
|
if (status == -1) {
|
|
LOGE("Error padding hash");
|
|
dump_boringssl_error();
|
|
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
|
}
|
|
|
|
// Encrypt PSS padded digest.
|
|
status = RSA_private_encrypt(*signature_length, &padded_digest[0], signature,
|
|
rsa_key(), RSA_NO_PADDING);
|
|
if (status == -1) {
|
|
LOGE("Error in private encrypt");
|
|
dump_boringssl_error();
|
|
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
|
}
|
|
return OEMCrypto_SUCCESS;
|
|
}
|
|
|
|
OEMCryptoResult SessionContext::GenerateRSASignature(
|
|
const uint8_t* message, size_t message_length, uint8_t* signature,
|
|
size_t* signature_length, RSA_Padding_Scheme padding_scheme) {
|
|
if (message == nullptr || message_length == 0 || signature == nullptr ||
|
|
signature_length == 0) {
|
|
LOGE("OEMCrypto_ERROR_INVALID_CONTEXT");
|
|
return OEMCrypto_ERROR_INVALID_CONTEXT;
|
|
}
|
|
if (!rsa_key()) {
|
|
LOGE("No RSA key set");
|
|
return OEMCrypto_ERROR_INVALID_RSA_KEY;
|
|
}
|
|
if (*signature_length < static_cast<size_t>(RSA_size(rsa_key()))) {
|
|
*signature_length = RSA_size(rsa_key());
|
|
return OEMCrypto_ERROR_SHORT_BUFFER;
|
|
}
|
|
if (((padding_scheme & allowed_schemes_) != padding_scheme) ||
|
|
(padding_scheme != kSign_PKCS1_Block1)) {
|
|
LOGE("padding_scheme not allowed");
|
|
return OEMCrypto_ERROR_INVALID_RSA_KEY;
|
|
}
|
|
// This is the maximum digest size possible for PKCS1 block type 1,
|
|
// as used for a CAST receiver.
|
|
const size_t max_digest_size = 83u;
|
|
if (message_length > max_digest_size) {
|
|
LOGE("RSA digest too large");
|
|
return OEMCrypto_ERROR_SIGNATURE_FAILURE;
|
|
}
|
|
// Pad the message with PKCS1 padding, and then encrypt.
|
|
const int status = RSA_private_encrypt(message_length, message, signature,
|
|
rsa_key(), RSA_PKCS1_PADDING);
|
|
if (status < 0) {
|
|
LOGE("Error in RSA private encrypt. status = %d", status);
|
|
dump_boringssl_error();
|
|
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
|
}
|
|
*signature_length = static_cast<size_t>(RSA_size(rsa_key()));
|
|
return OEMCrypto_SUCCESS;
|
|
}
|
|
|
|
// Validate message signature
|
|
bool SessionContext::ValidateMessage(const uint8_t* given_message,
|
|
size_t message_length,
|
|
const uint8_t* given_signature,
|
|
size_t signature_length) {
|
|
if (signature_length != SHA256_DIGEST_LENGTH) {
|
|
return false;
|
|
}
|
|
uint8_t computed_signature[SHA256_DIGEST_LENGTH];
|
|
memset(computed_signature, 0, SHA256_DIGEST_LENGTH);
|
|
unsigned int md_len = SHA256_DIGEST_LENGTH;
|
|
if (!HMAC(EVP_sha256(), mac_key_server_.data(), mac_key_server_.size(),
|
|
given_message, message_length, computed_signature, &md_len)) {
|
|
LOGE("ValidateMessage: Could not compute signature");
|
|
return false;
|
|
}
|
|
if (CRYPTO_memcmp(given_signature, computed_signature, signature_length)) {
|
|
LOGE("Invalid signature given: %s",
|
|
wvcdm::HexEncode(given_signature, signature_length).c_str());
|
|
LOGE("Invalid signature computed: %s",
|
|
wvcdm::HexEncode(computed_signature, signature_length).c_str());
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
OEMCryptoResult SessionContext::CheckStatusOnline(uint32_t nonce,
|
|
uint32_t control) {
|
|
if (!(control & wvoec::kControlNonceEnabled)) {
|
|
LOGE("LoadKeys: Server provided Nonce_Required but Nonce_Enabled = 0");
|
|
// Server error. Continue, and assume nonce required.
|
|
}
|
|
if (!CheckNonce(nonce)) return OEMCrypto_ERROR_INVALID_NONCE;
|
|
switch (usage_entry_status_) {
|
|
case kNoUsageEntry:
|
|
LOGE("LoadKeys: Session did not create usage entry");
|
|
return OEMCrypto_ERROR_INVALID_CONTEXT;
|
|
case kUsageEntryLoaded:
|
|
LOGE("LoadKeys: Session reloaded existing entry");
|
|
return OEMCrypto_ERROR_INVALID_CONTEXT;
|
|
case kUsageEntryNew:
|
|
return OEMCrypto_SUCCESS;
|
|
default: // invalid status.
|
|
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
|
}
|
|
}
|
|
|
|
OEMCryptoResult SessionContext::CheckStatusOffline(uint32_t nonce,
|
|
uint32_t control) {
|
|
if (control & wvoec::kControlNonceEnabled) {
|
|
LOGE("KCB: Server provided NonceOrEntry but Nonce_Enabled = 1");
|
|
// Server error. Continue, and assume nonce required.
|
|
}
|
|
switch (usage_entry_status_) {
|
|
case kNoUsageEntry:
|
|
LOGE("LoadKeys: Session did not create or load usage entry");
|
|
return OEMCrypto_ERROR_INVALID_CONTEXT;
|
|
case kUsageEntryLoaded:
|
|
// Repeat load. Calling function will verify pst and keys.
|
|
return OEMCrypto_SUCCESS;
|
|
case kUsageEntryNew:
|
|
// First load. Verify nonce.
|
|
if (!CheckNonce(nonce)) return OEMCrypto_ERROR_INVALID_NONCE;
|
|
return OEMCrypto_SUCCESS;
|
|
default: // invalid status.
|
|
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
|
}
|
|
}
|
|
|
|
OEMCryptoResult SessionContext::CheckNonceOrEntry(
|
|
const KeyControlBlock& key_control_block) {
|
|
switch (key_control_block.control_bits() & wvoec::kControlReplayMask) {
|
|
case wvoec::kControlNonceRequired: // Online license. Nonce always
|
|
// required.
|
|
return CheckStatusOnline(key_control_block.nonce(),
|
|
key_control_block.control_bits());
|
|
break;
|
|
case wvoec::kControlNonceOrEntry: // Offline license. Nonce required on
|
|
// first use.
|
|
return CheckStatusOffline(key_control_block.nonce(),
|
|
key_control_block.control_bits());
|
|
break;
|
|
default:
|
|
if ((key_control_block.control_bits() & wvoec::kControlNonceEnabled) &&
|
|
(!CheckNonce(key_control_block.nonce()))) {
|
|
LOGE("LoadKeys: BAD Nonce");
|
|
return OEMCrypto_ERROR_INVALID_NONCE;
|
|
}
|
|
}
|
|
return OEMCrypto_SUCCESS;
|
|
}
|
|
|
|
OEMCryptoResult SessionContext::LoadLicense(const uint8_t* message,
|
|
size_t message_length,
|
|
size_t core_message_length,
|
|
const uint8_t* signature,
|
|
size_t signature_length) {
|
|
// Check state before we check signature. State is change in
|
|
// LoadKeysNoSignature.
|
|
if (state_response_loaded_) {
|
|
return OEMCrypto_ERROR_LICENSE_RELOAD;
|
|
}
|
|
ODK_ParsedLicense parsed_response;
|
|
const bool initial_license_load = (usage_entry_status_ != kUsageEntryLoaded);
|
|
const OEMCryptoResult result = ODK_ParseLicense(
|
|
message, message_length, core_message_length, initial_license_load,
|
|
usage_entry_present(), license_request_hash_, &timer_limits_,
|
|
&clock_values_, &nonce_values_, &parsed_response);
|
|
if (result != OEMCrypto_SUCCESS) {
|
|
LOGE("ODK Error %d", static_cast<int>(result));
|
|
return result;
|
|
}
|
|
// Validate message signature
|
|
if (!ValidateMessage(message, message_length, signature, signature_length)) {
|
|
return OEMCrypto_ERROR_SIGNATURE_FAILURE;
|
|
}
|
|
const uint8_t* message_body = message + core_message_length;
|
|
const size_t message_body_length = message_length - core_message_length;
|
|
return LoadKeysNoSignature(
|
|
message_body, message_body_length, parsed_response.enc_mac_keys_iv,
|
|
parsed_response.enc_mac_keys, parsed_response.key_array_length,
|
|
parsed_response.key_array, parsed_response.pst,
|
|
parsed_response.srm_restriction_data,
|
|
static_cast<OEMCrypto_LicenseType>(parsed_response.license_type));
|
|
}
|
|
|
|
OEMCryptoResult SessionContext::LoadKeys(
|
|
const uint8_t* message, size_t message_length, const uint8_t* signature,
|
|
size_t signature_length, OEMCrypto_Substring enc_mac_keys_iv,
|
|
OEMCrypto_Substring enc_mac_keys, size_t num_keys,
|
|
const OEMCrypto_KeyObject* key_array, OEMCrypto_Substring pst,
|
|
OEMCrypto_Substring srm_restriction_data,
|
|
OEMCrypto_LicenseType license_type) {
|
|
// Check state before we check signature. State is change in
|
|
// LoadKeysNoSignature.
|
|
if (state_response_loaded_) {
|
|
return OEMCrypto_ERROR_LICENSE_RELOAD;
|
|
}
|
|
// Validate message signature
|
|
if (!ValidateMessage(message, message_length, signature, signature_length)) {
|
|
return OEMCrypto_ERROR_SIGNATURE_FAILURE;
|
|
}
|
|
OEMCryptoResult result = LoadKeysNoSignature(
|
|
message, message_length, enc_mac_keys_iv, enc_mac_keys, num_keys,
|
|
key_array, pst, srm_restriction_data, license_type);
|
|
if (result != OEMCrypto_SUCCESS) return result;
|
|
Key* key = session_keys_->FirstKey();
|
|
uint32_t duration = key ? key->control().duration() : 0;
|
|
result = ODK_InitializeV15Values(&timer_limits_, &clock_values_,
|
|
&nonce_values_, duration, ce_->SystemTime());
|
|
// TODO(b/140765227): clear session on errors
|
|
return result;
|
|
}
|
|
|
|
OEMCryptoResult SessionContext::LoadKeysNoSignature(
|
|
const uint8_t* message, size_t message_length,
|
|
OEMCrypto_Substring enc_mac_keys_iv, OEMCrypto_Substring enc_mac_keys,
|
|
size_t num_keys, const OEMCrypto_KeyObject* key_array,
|
|
OEMCrypto_Substring pst, OEMCrypto_Substring srm_restriction_data,
|
|
OEMCrypto_LicenseType license_type) {
|
|
if (state_response_loaded_) {
|
|
return OEMCrypto_ERROR_LICENSE_RELOAD;
|
|
}
|
|
state_response_loaded_ = true;
|
|
if (num_keys < 1) return OEMCrypto_ERROR_INVALID_CONTEXT;
|
|
|
|
if (session_keys_ == nullptr) {
|
|
switch (license_type) {
|
|
case OEMCrypto_ContentLicense:
|
|
session_keys_.reset(new ContentKeysContext());
|
|
break;
|
|
|
|
case OEMCrypto_EntitlementLicense:
|
|
session_keys_.reset(new EntitlementKeysContext());
|
|
break;
|
|
|
|
default:
|
|
return OEMCrypto_ERROR_INVALID_CONTEXT;
|
|
}
|
|
} else {
|
|
if (session_keys_->type() != license_type) {
|
|
return OEMCrypto_ERROR_INVALID_CONTEXT;
|
|
}
|
|
}
|
|
if (srm_restriction_data.length != 0) {
|
|
const std::string kSRMVerificationString = "HDCPDATA";
|
|
if (memcmp(message + srm_restriction_data.offset,
|
|
kSRMVerificationString.c_str(), kSRMVerificationString.size())) {
|
|
LOGE("SRM Requirement Data has bad verification string: %8s",
|
|
message + srm_restriction_data.offset);
|
|
return OEMCrypto_ERROR_INVALID_CONTEXT;
|
|
}
|
|
const uint32_t minimum_version = htonl(*reinterpret_cast<const uint32_t*>(
|
|
message + srm_restriction_data.offset + 8));
|
|
uint16_t current_version = 0;
|
|
if (OEMCrypto_SUCCESS != ce_->current_srm_version(¤t_version)) {
|
|
LOGW("[LoadKeys: SRM Version not available");
|
|
srm_requirements_status_ = InvalidSRMVersion;
|
|
} else if (current_version < minimum_version) {
|
|
LOGW("[LoadKeys: SRM Version is too small %u, required: %u",
|
|
current_version, minimum_version);
|
|
srm_requirements_status_ = InvalidSRMVersion;
|
|
} else if (ce_->srm_forbidden_device_attached()) {
|
|
LOGW("[LoadKeys: SRM forbidden device attached]");
|
|
srm_requirements_status_ = InvalidSRMVersion;
|
|
} else {
|
|
LOGI("[LoadKeys: SRM Versions is %u, required: %u]", current_version,
|
|
minimum_version);
|
|
srm_requirements_status_ = ValidSRMVersion;
|
|
}
|
|
}
|
|
|
|
// Decrypt and install keys in key object
|
|
// Each key will have a key control block. They will all have the same nonce.
|
|
OEMCryptoResult status = OEMCrypto_SUCCESS;
|
|
std::vector<uint8_t> key_id;
|
|
std::vector<uint8_t> enc_key_data;
|
|
std::vector<uint8_t> key_data_iv;
|
|
std::vector<uint8_t> key_control;
|
|
std::vector<uint8_t> key_control_iv;
|
|
for (unsigned int i = 0; i < num_keys; i++) {
|
|
key_id.assign(
|
|
message + key_array[i].key_id.offset,
|
|
message + key_array[i].key_id.offset + key_array[i].key_id.length);
|
|
enc_key_data.assign(
|
|
message + key_array[i].key_data.offset,
|
|
message + key_array[i].key_data.offset + key_array[i].key_data.length);
|
|
key_data_iv.assign(
|
|
message + key_array[i].key_data_iv.offset,
|
|
message + key_array[i].key_data_iv.offset + wvoec::KEY_IV_SIZE);
|
|
if (key_array[i].key_control.length == 0) {
|
|
status = OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
|
break;
|
|
}
|
|
key_control.assign(
|
|
message + key_array[i].key_control.offset,
|
|
message + key_array[i].key_control.offset + wvoec::KEY_CONTROL_SIZE);
|
|
key_control_iv.assign(
|
|
message + key_array[i].key_control_iv.offset,
|
|
message + key_array[i].key_control_iv.offset + wvoec::KEY_IV_SIZE);
|
|
|
|
OEMCryptoResult result = InstallKey(key_id, enc_key_data, key_data_iv,
|
|
key_control, key_control_iv);
|
|
if (result != OEMCrypto_SUCCESS) {
|
|
status = result;
|
|
break;
|
|
}
|
|
}
|
|
if (status != OEMCrypto_SUCCESS) return status;
|
|
|
|
// enc_mac_key can be nullptr if license renewal is not supported
|
|
if (enc_mac_keys.length != 0) {
|
|
// V2.1 license protocol: update mac keys after processing license response
|
|
const std::vector<uint8_t> enc_mac_keys_str = std::vector<uint8_t>(
|
|
message + enc_mac_keys.offset,
|
|
message + enc_mac_keys.offset + 2 * wvoec::MAC_KEY_SIZE);
|
|
const std::vector<uint8_t> enc_mac_key_iv_str = std::vector<uint8_t>(
|
|
message + enc_mac_keys_iv.offset,
|
|
message + enc_mac_keys_iv.offset + wvoec::KEY_IV_SIZE);
|
|
|
|
if (!UpdateMacKeys(enc_mac_keys_str, enc_mac_key_iv_str)) {
|
|
LOGE("Failed to update mac keys.");
|
|
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
|
}
|
|
} else {
|
|
// If the mac keys are not updated, we will not need them again.
|
|
mac_key_server_.resize(0);
|
|
mac_key_client_.resize(0);
|
|
}
|
|
|
|
if (usage_entry_) {
|
|
OEMCryptoResult result = OEMCrypto_SUCCESS;
|
|
switch (usage_entry_status_) {
|
|
case kNoUsageEntry:
|
|
if (pst.length > 0) {
|
|
LOGE("LoadKeys: PST specified but no usage entry loaded");
|
|
return OEMCrypto_ERROR_INVALID_CONTEXT;
|
|
}
|
|
break; // no extra check.
|
|
case kUsageEntryNew:
|
|
result = usage_entry_->SetPST(message + pst.offset, pst.length);
|
|
if (result != OEMCrypto_SUCCESS) {
|
|
return result;
|
|
}
|
|
if (!usage_entry_->SetMacKeys(mac_key_server_, mac_key_client_)) {
|
|
LOGE("LoadKeys: Usage table can't set keys.");
|
|
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
|
}
|
|
break;
|
|
case kUsageEntryLoaded:
|
|
if (!usage_entry_->VerifyPST(message + pst.offset, pst.length)) {
|
|
return OEMCrypto_ERROR_WRONG_PST;
|
|
}
|
|
if (!usage_entry_->VerifyMacKeys(mac_key_server_, mac_key_client_)) {
|
|
LOGE("LoadKeys: Usage table entry mac keys do not match.");
|
|
return OEMCrypto_ERROR_WRONG_KEYS;
|
|
}
|
|
if (usage_entry_->Inactive()) return OEMCrypto_ERROR_LICENSE_INACTIVE;
|
|
break;
|
|
}
|
|
}
|
|
encryption_key_.clear();
|
|
return OEMCrypto_SUCCESS;
|
|
}
|
|
|
|
OEMCryptoResult SessionContext::LoadEntitledContentKeys(
|
|
const uint8_t* message, size_t message_length, size_t key_array_length,
|
|
const OEMCrypto_EntitledContentKeyObject* key_array) {
|
|
if (!key_array) {
|
|
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
|
}
|
|
if (session_keys_ == nullptr ||
|
|
session_keys_->type() != OEMCrypto_EntitlementLicense) {
|
|
return OEMCrypto_ERROR_INVALID_CONTEXT;
|
|
}
|
|
for (size_t i = 0; i < key_array_length; ++i) {
|
|
const OEMCrypto_EntitledContentKeyObject* key_data = &key_array[i];
|
|
std::vector<uint8_t> entitlement_key_id;
|
|
entitlement_key_id.assign(message + key_data->entitlement_key_id.offset,
|
|
message + key_data->entitlement_key_id.offset +
|
|
key_data->entitlement_key_id.length);
|
|
|
|
EntitlementKey* entitlement_key =
|
|
session_keys_->GetEntitlementKey(entitlement_key_id);
|
|
if (entitlement_key == nullptr) {
|
|
return OEMCrypto_KEY_NOT_ENTITLED;
|
|
}
|
|
std::vector<uint8_t> content_key;
|
|
std::vector<uint8_t> iv;
|
|
std::vector<uint8_t> encrypted_content_key;
|
|
std::vector<uint8_t> content_key_id;
|
|
|
|
iv.assign(message + key_data->content_key_data_iv.offset,
|
|
message + key_data->content_key_data_iv.offset + 16);
|
|
encrypted_content_key.assign(message + key_data->content_key_data.offset,
|
|
message + key_data->content_key_data.offset +
|
|
key_data->content_key_data.length);
|
|
content_key_id.assign(message + key_data->content_key_id.offset,
|
|
message + key_data->content_key_id.offset +
|
|
key_data->content_key_id.length);
|
|
if (!DecryptMessage(entitlement_key->entitlement_key(), iv,
|
|
encrypted_content_key, &content_key,
|
|
256 /* key size */)) {
|
|
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
|
}
|
|
if (!session_keys_->SetContentKey(entitlement_key_id, content_key_id,
|
|
content_key)) {
|
|
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
|
}
|
|
}
|
|
return OEMCrypto_SUCCESS;
|
|
}
|
|
|
|
OEMCryptoResult SessionContext::InstallKey(
|
|
const KeyId& key_id, const std::vector<uint8_t>& key_data,
|
|
const std::vector<uint8_t>& key_data_iv,
|
|
const std::vector<uint8_t>& key_control,
|
|
const std::vector<uint8_t>& key_control_iv) {
|
|
// Decrypt encrypted key_data using derived encryption key and offered iv
|
|
std::vector<uint8_t> content_key;
|
|
std::vector<uint8_t> key_control_str;
|
|
|
|
if (!DecryptMessage(encryption_key_, key_data_iv, key_data, &content_key,
|
|
128 /* key size */)) {
|
|
LOGE("[Installkey(): Could not decrypt key data]");
|
|
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
|
}
|
|
|
|
// Key control must be supplied by license server
|
|
if (key_control.empty()) {
|
|
LOGE("[Installkey(): WARNING: No Key Control]");
|
|
return OEMCrypto_ERROR_INVALID_CONTEXT;
|
|
}
|
|
if (key_control_iv.empty()) {
|
|
LOGE("[Installkey(): ERROR: No Key Control IV]");
|
|
return OEMCrypto_ERROR_INVALID_CONTEXT;
|
|
}
|
|
if (!DecryptMessage(content_key, key_control_iv, key_control,
|
|
&key_control_str, 128 /* key size */)) {
|
|
LOGE("[Installkey(): ERROR: Could not decrypt content key]");
|
|
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
|
}
|
|
|
|
KeyControlBlock key_control_block(key_control_str);
|
|
if (!key_control_block.valid()) {
|
|
LOGE("Error parsing key control");
|
|
return OEMCrypto_ERROR_INVALID_CONTEXT;
|
|
}
|
|
if ((key_control_block.control_bits() &
|
|
wvoec::kControlRequireAntiRollbackHardware) &&
|
|
!ce_->config_is_anti_rollback_hw_present()) {
|
|
LOGE("Anti-rollback hardware is required but hardware not present");
|
|
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
|
}
|
|
const uint8_t minimum_patch_level = (key_control_block.control_bits() &
|
|
wvoec::kControlSecurityPatchLevelMask) >>
|
|
wvoec::kControlSecurityPatchLevelShift;
|
|
if (minimum_patch_level > OEMCrypto_Security_Patch_Level()) {
|
|
LOGE("[InstallKey(): security_patch_level = %u, minimum_patch_level = %u]",
|
|
OEMCrypto_Security_Patch_Level(), minimum_patch_level);
|
|
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
|
}
|
|
OEMCryptoResult result = CheckNonceOrEntry(key_control_block);
|
|
if (result != OEMCrypto_SUCCESS) {
|
|
LOGE("LoadKeys: Failed Nonce/PST check");
|
|
return result;
|
|
}
|
|
if (key_control_block.control_bits() & wvoec::kControlSRMVersionRequired) {
|
|
if (srm_requirements_status_ == NoSRMVersion) {
|
|
LOGE("[LoadKeys: control bit says SRM version required]");
|
|
return OEMCrypto_ERROR_INVALID_CONTEXT;
|
|
}
|
|
if (srm_requirements_status_ == InvalidSRMVersion) {
|
|
// If the SRM version is too small, treat this key as local display only.
|
|
key_control_block.RequireLocalDisplay();
|
|
}
|
|
}
|
|
|
|
Key key(content_key, key_control_block);
|
|
if (session_keys_ == nullptr) {
|
|
return OEMCrypto_ERROR_INVALID_CONTEXT;
|
|
}
|
|
session_keys_->Insert(key_id, key);
|
|
return OEMCrypto_SUCCESS;
|
|
}
|
|
|
|
bool SessionContext::InstallRSAEncryptedKey(
|
|
const uint8_t* encrypted_message_key, size_t encrypted_message_key_length) {
|
|
encryption_key_.resize(RSA_size(rsa_key()));
|
|
const int decrypted_size = RSA_private_decrypt(
|
|
encrypted_message_key_length, encrypted_message_key, &encryption_key_[0],
|
|
rsa_key(), RSA_PKCS1_OAEP_PADDING);
|
|
if (-1 == decrypted_size) {
|
|
LOGE("[RSADeriveKeys(): error decrypting session key.]");
|
|
dump_boringssl_error();
|
|
return false;
|
|
}
|
|
encryption_key_.resize(decrypted_size);
|
|
if (decrypted_size != static_cast<int>(wvoec::KEY_SIZE)) {
|
|
LOGE("[RSADeriveKeys(): error. Session key is wrong size: %d.]",
|
|
decrypted_size);
|
|
dump_boringssl_error();
|
|
encryption_key_.clear();
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
OEMCryptoResult SessionContext::LoadRenewal(const uint8_t* message,
|
|
size_t message_length,
|
|
size_t core_message_length,
|
|
const uint8_t* signature,
|
|
size_t signature_length) {
|
|
if (session_keys_ == nullptr) {
|
|
return OEMCrypto_ERROR_INVALID_CONTEXT;
|
|
}
|
|
if (!ValidateMessage(message, message_length, signature, signature_length)) {
|
|
LOGE("Signature was invalid");
|
|
return OEMCrypto_ERROR_SIGNATURE_FAILURE;
|
|
}
|
|
|
|
// The reference implementation does not use a hardware timer.
|
|
uint64_t* timer_value = nullptr;
|
|
const OEMCryptoResult result = ODK_ParseRenewal(
|
|
message, message_length, core_message_length, &nonce_values_,
|
|
ce_->SystemTime(), &timer_limits_, &clock_values_, timer_value);
|
|
if (result == ODK_SET_TIMER || result == ODK_DISABLE_TIMER)
|
|
return OEMCrypto_SUCCESS;
|
|
if (result == ODK_TIMER_EXPIRED) return OEMCrypto_ERROR_KEY_EXPIRED;
|
|
// All other errors are returned to the caller.
|
|
return result;
|
|
}
|
|
|
|
OEMCryptoResult SessionContext::RefreshKey(
|
|
const KeyId& key_id, const std::vector<uint8_t>& key_control,
|
|
const std::vector<uint8_t>& key_control_iv) {
|
|
if (session_keys_ == nullptr) {
|
|
return OEMCrypto_ERROR_INVALID_CONTEXT;
|
|
}
|
|
if (key_control.empty()) {
|
|
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
|
}
|
|
std::vector<uint8_t> decrypted_key_control;
|
|
if (key_id.empty()) {
|
|
// Key control is not encrypted if key id is NULL
|
|
decrypted_key_control = key_control;
|
|
} else {
|
|
Key* content_key = session_keys_->Find(key_id);
|
|
if (nullptr == content_key) {
|
|
LOGE("Key ID not found.");
|
|
return OEMCrypto_ERROR_NO_CONTENT_KEY;
|
|
}
|
|
const std::vector<uint8_t> content_key_value = content_key->value();
|
|
// Decrypt encrypted key control block
|
|
if (key_control_iv.empty()) {
|
|
decrypted_key_control = key_control;
|
|
} else {
|
|
if (!DecryptMessage(content_key_value, key_control_iv, key_control,
|
|
&decrypted_key_control, 128 /* key size */)) {
|
|
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
|
}
|
|
}
|
|
}
|
|
KeyControlBlock key_control_block(decrypted_key_control);
|
|
if (!key_control_block.valid()) {
|
|
LOGE("Parse key control error.");
|
|
return OEMCrypto_ERROR_INVALID_CONTEXT;
|
|
}
|
|
uint32_t new_key_duration = key_control_block.duration();
|
|
uint64_t* timer_value = nullptr;
|
|
const OEMCryptoResult result =
|
|
ODK_RefreshV15Values(&timer_limits_, &clock_values_, &nonce_values_,
|
|
ce_->SystemTime(), new_key_duration, timer_value);
|
|
if (result == ODK_SET_TIMER || result == ODK_DISABLE_TIMER)
|
|
return OEMCrypto_SUCCESS;
|
|
if (result == ODK_TIMER_EXPIRED) return OEMCrypto_ERROR_KEY_EXPIRED;
|
|
return result;
|
|
}
|
|
|
|
bool SessionContext::DecryptRSAKey(const uint8_t* enc_rsa_key,
|
|
size_t enc_rsa_key_length,
|
|
const uint8_t* enc_rsa_key_iv,
|
|
uint8_t* pkcs8_rsa_key) {
|
|
if (enc_rsa_key_length % AES_BLOCK_SIZE != 0) {
|
|
LOGE("[DecryptRSAKey(): bad buffer size]");
|
|
return false;
|
|
}
|
|
// Decrypt rsa key with keybox.
|
|
uint8_t iv_buffer[wvoec::KEY_IV_SIZE];
|
|
memcpy(iv_buffer, enc_rsa_key_iv, wvoec::KEY_IV_SIZE);
|
|
AES_KEY aes_key;
|
|
AES_set_decrypt_key(&encryption_key_[0], 128, &aes_key);
|
|
AES_cbc_encrypt(enc_rsa_key, pkcs8_rsa_key, enc_rsa_key_length, &aes_key,
|
|
iv_buffer, AES_DECRYPT);
|
|
return true;
|
|
}
|
|
|
|
bool SessionContext::EncryptRSAKey(const uint8_t* pkcs8_rsa_key,
|
|
size_t enc_rsa_key_length,
|
|
const uint8_t* enc_rsa_key_iv,
|
|
uint8_t* enc_rsa_key) {
|
|
if (enc_rsa_key_length % AES_BLOCK_SIZE != 0) {
|
|
LOGE("[EncryptRSAKey(): bad buffer size]");
|
|
return false;
|
|
}
|
|
// Encrypt rsa key with keybox.
|
|
uint8_t iv_buffer[wvoec::KEY_IV_SIZE];
|
|
memcpy(iv_buffer, enc_rsa_key_iv, wvoec::KEY_IV_SIZE);
|
|
AES_KEY aes_key;
|
|
AES_set_encrypt_key(&encryption_key_[0], 128, &aes_key);
|
|
AES_cbc_encrypt(pkcs8_rsa_key, enc_rsa_key, enc_rsa_key_length, &aes_key,
|
|
iv_buffer, AES_ENCRYPT);
|
|
return true;
|
|
}
|
|
|
|
bool SessionContext::LoadRSAKey(const uint8_t* pkcs8_rsa_key,
|
|
size_t rsa_key_length) {
|
|
rsa_key_.reset();
|
|
if (rsa_key_length < 8) {
|
|
LOGE("[LoadRSAKey(): Very Short Buffer]");
|
|
return false;
|
|
}
|
|
if ((memcmp(pkcs8_rsa_key, "SIGN", 4) == 0)) {
|
|
uint32_t schemes_n;
|
|
memcpy((uint8_t*)&schemes_n, pkcs8_rsa_key + 4, sizeof(uint32_t));
|
|
allowed_schemes_ = htonl(schemes_n);
|
|
pkcs8_rsa_key += 8;
|
|
rsa_key_length -= 8;
|
|
} else {
|
|
allowed_schemes_ = kSign_RSASSA_PSS;
|
|
}
|
|
return rsa_key_.LoadPkcs8RsaKey(pkcs8_rsa_key, rsa_key_length);
|
|
}
|
|
|
|
OEMCryptoResult SessionContext::CheckKeyUse(const std::string& log_string,
|
|
uint32_t use_type,
|
|
OEMCryptoBufferType buffer_type) {
|
|
const KeyControlBlock& control = current_content_key()->control();
|
|
if (use_type && (!(control.control_bits() & use_type))) {
|
|
LOGE("[%s(): control bit says not allowed", log_string.c_str());
|
|
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
|
}
|
|
if (control.control_bits() & wvoec::kControlDataPathSecure) {
|
|
if (!ce_->config_closed_platform() &&
|
|
buffer_type == OEMCrypto_BufferType_Clear) {
|
|
LOGE("[%s(): Secure key with insecure buffer]", log_string.c_str());
|
|
return OEMCrypto_ERROR_DECRYPT_FAILED;
|
|
}
|
|
}
|
|
if (!ce_->config_local_display_only()) {
|
|
// Only look at HDCP restrictions if the display can be non-local.
|
|
if (control.control_bits() & wvoec::kControlHDCPRequired) {
|
|
uint8_t required_hdcp =
|
|
(control.control_bits() & wvoec::kControlHDCPVersionMask) >>
|
|
wvoec::kControlHDCPVersionShift;
|
|
if (ce_->srm_forbidden_device_attached()) {
|
|
required_hdcp = HDCP_NO_DIGITAL_OUTPUT;
|
|
}
|
|
// For reference implementation, we pretend we can handle the current
|
|
// HDCP version.
|
|
if (required_hdcp > ce_->config_current_hdcp_capability() ||
|
|
ce_->config_current_hdcp_capability() == 0) {
|
|
return OEMCrypto_ERROR_INSUFFICIENT_HDCP;
|
|
}
|
|
}
|
|
}
|
|
// Return an error if analog displays should be disabled.
|
|
if ((control.control_bits() & wvoec::kControlDisableAnalogOutput) &&
|
|
ce_->analog_display_active()) {
|
|
LOGE("[%s(): control bit says disable analog", log_string.c_str());
|
|
return OEMCrypto_ERROR_ANALOG_OUTPUT;
|
|
}
|
|
// Check if CGMS is required.
|
|
if (control.control_bits() & wvoec::kControlCGMSMask) {
|
|
if (ce_->analog_display_active() && !ce_->cgms_a_active()) {
|
|
LOGE("[%s(): control bit says CGMS required", log_string.c_str());
|
|
return OEMCrypto_ERROR_ANALOG_OUTPUT;
|
|
}
|
|
}
|
|
if (!decrypt_started_) {
|
|
// The reference implementation does not have a hardware timer.
|
|
uint64_t* timer_expiration = nullptr;
|
|
const OEMCryptoResult result = ODK_AttemptFirstPlayback(
|
|
ce_->SystemTime(), &timer_limits_, &clock_values_, timer_expiration);
|
|
if (result == ODK_TIMER_EXPIRED) return OEMCrypto_ERROR_KEY_EXPIRED;
|
|
if (usage_entry_ != nullptr) usage_entry_->ForbidReport();
|
|
} else {
|
|
// Continued playback.
|
|
const OEMCryptoResult result = ODK_UpdateLastPlaybackTime(
|
|
ce_->SystemTime(), &timer_limits_, &clock_values_);
|
|
if (result == ODK_TIMER_EXPIRED) return OEMCrypto_ERROR_KEY_EXPIRED;
|
|
if (usage_entry_ != nullptr) usage_entry_->set_recent_decrypt(true);
|
|
}
|
|
decrypt_started_ = true; // First playback for session.
|
|
return OEMCrypto_SUCCESS;
|
|
}
|
|
|
|
OEMCryptoResult SessionContext::Generic_Encrypt(const uint8_t* in_buffer,
|
|
size_t buffer_length,
|
|
const uint8_t* iv,
|
|
OEMCrypto_Algorithm algorithm,
|
|
uint8_t* out_buffer) {
|
|
// Check there is a content key
|
|
if (current_content_key() == nullptr) {
|
|
LOGE("[Generic_Encrypt(): OEMCrypto_ERROR_NO_CONTENT_KEY]");
|
|
return OEMCrypto_ERROR_NO_CONTENT_KEY;
|
|
}
|
|
const std::vector<uint8_t>& key = current_content_key()->value();
|
|
// Set the AES key.
|
|
if (static_cast<int>(key.size()) != AES_BLOCK_SIZE) {
|
|
LOGE("[Generic_Encrypt(): CONTENT_KEY has wrong size: %zu", key.size());
|
|
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
|
}
|
|
OEMCryptoResult result =
|
|
CheckKeyUse("Generic_Encrypt", wvoec::kControlAllowEncrypt,
|
|
OEMCrypto_BufferType_Clear);
|
|
if (result != OEMCrypto_SUCCESS) return result;
|
|
if (algorithm != OEMCrypto_AES_CBC_128_NO_PADDING) {
|
|
LOGE("[Generic_Encrypt(): algorithm bad");
|
|
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
|
}
|
|
if (buffer_length % AES_BLOCK_SIZE != 0) {
|
|
LOGE("[Generic_Encrypt(): buffers size bad");
|
|
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
|
}
|
|
const uint8_t* key_u8 = &key[0];
|
|
AES_KEY aes_key;
|
|
if (AES_set_encrypt_key(key_u8, AES_BLOCK_SIZE * 8, &aes_key) != 0) {
|
|
LOGE("[Generic_Encrypt(): FAILURE]");
|
|
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
|
}
|
|
uint8_t iv_buffer[wvoec::KEY_IV_SIZE];
|
|
memcpy(iv_buffer, iv, wvoec::KEY_IV_SIZE);
|
|
AES_cbc_encrypt(in_buffer, out_buffer, buffer_length, &aes_key, iv_buffer,
|
|
AES_ENCRYPT);
|
|
return OEMCrypto_SUCCESS;
|
|
}
|
|
|
|
OEMCryptoResult SessionContext::Generic_Decrypt(const uint8_t* in_buffer,
|
|
size_t buffer_length,
|
|
const uint8_t* iv,
|
|
OEMCrypto_Algorithm algorithm,
|
|
uint8_t* out_buffer) {
|
|
// Check there is a content key
|
|
if (current_content_key() == nullptr) {
|
|
LOGE("[Generic_Decrypt(): OEMCrypto_ERROR_NO_CONTENT_KEY]");
|
|
return OEMCrypto_ERROR_NO_CONTENT_KEY;
|
|
}
|
|
const std::vector<uint8_t>& key = current_content_key()->value();
|
|
// Set the AES key.
|
|
if (static_cast<int>(key.size()) != AES_BLOCK_SIZE) {
|
|
LOGE("[Generic_Decrypt(): CONTENT_KEY has wrong size");
|
|
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
|
}
|
|
OEMCryptoResult result =
|
|
CheckKeyUse("Generic_Decrypt", wvoec::kControlAllowDecrypt,
|
|
OEMCrypto_BufferType_Clear);
|
|
if (result != OEMCrypto_SUCCESS) return result;
|
|
|
|
if (algorithm != OEMCrypto_AES_CBC_128_NO_PADDING) {
|
|
LOGE("[Generic_Decrypt(): bad algorithm");
|
|
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
|
}
|
|
if (buffer_length % AES_BLOCK_SIZE != 0) {
|
|
LOGE("[Generic_Decrypt(): bad buffer size");
|
|
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
|
}
|
|
const uint8_t* key_u8 = &key[0];
|
|
AES_KEY aes_key;
|
|
if (AES_set_decrypt_key(key_u8, AES_BLOCK_SIZE * 8, &aes_key) != 0) {
|
|
LOGE("[Generic_Decrypt(): FAILURE]");
|
|
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
|
}
|
|
uint8_t iv_buffer[wvoec::KEY_IV_SIZE];
|
|
memcpy(iv_buffer, iv, wvoec::KEY_IV_SIZE);
|
|
AES_cbc_encrypt(in_buffer, out_buffer, buffer_length, &aes_key, iv_buffer,
|
|
AES_DECRYPT);
|
|
return OEMCrypto_SUCCESS;
|
|
}
|
|
|
|
OEMCryptoResult SessionContext::Generic_Sign(const uint8_t* in_buffer,
|
|
size_t buffer_length,
|
|
OEMCrypto_Algorithm algorithm,
|
|
uint8_t* signature,
|
|
size_t* signature_length) {
|
|
// Check there is a content key
|
|
if (current_content_key() == nullptr) {
|
|
LOGE("[Generic_Sign(): OEMCrypto_ERROR_NO_CONTENT_KEY]");
|
|
return OEMCrypto_ERROR_NO_CONTENT_KEY;
|
|
}
|
|
if (*signature_length < SHA256_DIGEST_LENGTH) {
|
|
*signature_length = SHA256_DIGEST_LENGTH;
|
|
LOGE("[Generic_Sign(): bad signature length");
|
|
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
|
}
|
|
const std::vector<uint8_t>& key = current_content_key()->value();
|
|
if (static_cast<int>(key.size()) != SHA256_DIGEST_LENGTH) {
|
|
LOGE("[Generic_Sign(): CONTENT_KEY has wrong size: %zu", key.size());
|
|
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
|
}
|
|
OEMCryptoResult result = CheckKeyUse("Generic_Sign", wvoec::kControlAllowSign,
|
|
OEMCrypto_BufferType_Clear);
|
|
if (result != OEMCrypto_SUCCESS) return result;
|
|
if (algorithm != OEMCrypto_HMAC_SHA256) {
|
|
LOGE("[Generic_Sign(): bad algorithm");
|
|
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
|
}
|
|
unsigned int md_len = *signature_length;
|
|
if (HMAC(EVP_sha256(), &key[0], key.size(), in_buffer, buffer_length,
|
|
signature, &md_len)) {
|
|
*signature_length = md_len;
|
|
return OEMCrypto_SUCCESS;
|
|
}
|
|
LOGE("[Generic_Sign(): hmac failed");
|
|
dump_boringssl_error();
|
|
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
|
}
|
|
|
|
OEMCryptoResult SessionContext::Generic_Verify(const uint8_t* in_buffer,
|
|
size_t buffer_length,
|
|
OEMCrypto_Algorithm algorithm,
|
|
const uint8_t* signature,
|
|
size_t signature_length) {
|
|
// Check there is a content key
|
|
if (current_content_key() == nullptr) {
|
|
LOGE("[Decrypt_Verify(): OEMCrypto_ERROR_NO_CONTENT_KEY]");
|
|
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
|
}
|
|
if (signature_length < SHA256_DIGEST_LENGTH) {
|
|
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
|
}
|
|
const std::vector<uint8_t>& key = current_content_key()->value();
|
|
if (static_cast<int>(key.size()) != SHA256_DIGEST_LENGTH) {
|
|
LOGE("[Generic_Verify(): CONTENT_KEY has wrong size: %zu", key.size());
|
|
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
|
}
|
|
OEMCryptoResult result = CheckKeyUse(
|
|
"Generic_Verify", wvoec::kControlAllowVerify, OEMCrypto_BufferType_Clear);
|
|
if (result != OEMCrypto_SUCCESS) return result;
|
|
if (algorithm != OEMCrypto_HMAC_SHA256) {
|
|
LOGE("[Generic_Verify(): bad algorithm");
|
|
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
|
}
|
|
unsigned int md_len = signature_length;
|
|
uint8_t computed_signature[SHA256_DIGEST_LENGTH];
|
|
if (HMAC(EVP_sha256(), &key[0], key.size(), in_buffer, buffer_length,
|
|
computed_signature, &md_len)) {
|
|
if (0 ==
|
|
CRYPTO_memcmp(signature, computed_signature, SHA256_DIGEST_LENGTH)) {
|
|
return OEMCrypto_SUCCESS;
|
|
} else {
|
|
return OEMCrypto_ERROR_SIGNATURE_FAILURE;
|
|
}
|
|
}
|
|
LOGE("[Generic_Verify(): HMAC failed");
|
|
dump_boringssl_error();
|
|
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
|
}
|
|
|
|
bool SessionContext::UpdateMacKeys(const std::vector<uint8_t>& enc_mac_keys,
|
|
const std::vector<uint8_t>& iv) {
|
|
// Decrypt mac key from enc_mac_key using device_keya
|
|
std::vector<uint8_t> mac_keys;
|
|
if (!DecryptMessage(encryption_key_, iv, enc_mac_keys, &mac_keys,
|
|
128 /* key size */)) {
|
|
return false;
|
|
}
|
|
mac_key_server_ = std::vector<uint8_t>(
|
|
mac_keys.begin(), mac_keys.begin() + wvoec::MAC_KEY_SIZE);
|
|
mac_key_client_ = std::vector<uint8_t>(mac_keys.begin() + wvoec::MAC_KEY_SIZE,
|
|
mac_keys.end());
|
|
return true;
|
|
}
|
|
|
|
bool SessionContext::QueryKeyControlBlock(const KeyId& key_id, uint32_t* data) {
|
|
if (session_keys_ == nullptr) {
|
|
return false;
|
|
}
|
|
const Key* content_key = session_keys_->Find(key_id);
|
|
if (content_key == nullptr) {
|
|
LOGE("[QueryKeyControlBlock(): No key matches key id]");
|
|
return false;
|
|
}
|
|
data[0] = 0; // verification optional.
|
|
data[1] = htonl(content_key->control().duration());
|
|
data[2] = 0; // nonce optional.
|
|
data[3] = htonl(content_key->control().control_bits());
|
|
return true;
|
|
}
|
|
|
|
OEMCryptoResult SessionContext::SelectContentKey(
|
|
const KeyId& key_id, OEMCryptoCipherMode cipher_mode) {
|
|
if (session_keys_ == nullptr) {
|
|
LOGE("Select Key: no session keys");
|
|
return OEMCrypto_ERROR_INVALID_CONTEXT;
|
|
}
|
|
Key* content_key = session_keys_->Find(key_id);
|
|
if (content_key == nullptr) {
|
|
LOGE("No key matches key id");
|
|
return OEMCrypto_ERROR_NO_CONTENT_KEY;
|
|
}
|
|
content_key->set_ctr_mode(cipher_mode == OEMCrypto_CipherMode_CTR);
|
|
current_content_key_ = content_key;
|
|
return OEMCrypto_SUCCESS;
|
|
}
|
|
|
|
OEMCryptoResult SessionContext::CreateNewUsageEntry(
|
|
uint32_t* usage_entry_number) {
|
|
if (usage_entry_) {
|
|
// Can only load one entry per session.
|
|
return OEMCrypto_ERROR_MULTIPLE_USAGE_ENTRIES;
|
|
}
|
|
OEMCryptoResult result = ce_->usage_table().CreateNewUsageEntry(
|
|
this, &usage_entry_, usage_entry_number);
|
|
if (usage_entry_) {
|
|
usage_entry_status_ = kUsageEntryNew;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
OEMCryptoResult SessionContext::LoadUsageEntry(
|
|
uint32_t index, const std::vector<uint8_t>& buffer) {
|
|
if (usage_entry_) {
|
|
// Can only load one entry per session.
|
|
return OEMCrypto_ERROR_MULTIPLE_USAGE_ENTRIES;
|
|
}
|
|
const OEMCryptoResult result = ce_->usage_table().LoadUsageEntry(
|
|
this, &usage_entry_, index, buffer, &clock_values_);
|
|
if ((result != OEMCrypto_SUCCESS) &&
|
|
(result != OEMCrypto_WARNING_GENERATION_SKEW))
|
|
return result;
|
|
if (!usage_entry_) return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
|
|
|
usage_entry_status_ = kUsageEntryLoaded;
|
|
// Copy the mac keys to the current session.
|
|
mac_key_server_ = std::vector<uint8_t>(
|
|
usage_entry_->mac_key_server(),
|
|
usage_entry_->mac_key_server() + wvoec::MAC_KEY_SIZE);
|
|
mac_key_client_ = std::vector<uint8_t>(
|
|
usage_entry_->mac_key_client(),
|
|
usage_entry_->mac_key_client() + wvoec::MAC_KEY_SIZE);
|
|
return result;
|
|
}
|
|
|
|
OEMCryptoResult SessionContext::UpdateUsageEntry(uint8_t* header_buffer,
|
|
size_t* header_buffer_length,
|
|
uint8_t* entry_buffer,
|
|
size_t* entry_buffer_length) {
|
|
if (!usage_entry_) {
|
|
LOGE("UpdateUsageEntry: Session has no entry");
|
|
return OEMCrypto_ERROR_INVALID_CONTEXT;
|
|
}
|
|
return ce_->usage_table().UpdateUsageEntry(
|
|
this, usage_entry_.get(), header_buffer, header_buffer_length,
|
|
entry_buffer, entry_buffer_length, &clock_values_);
|
|
}
|
|
|
|
OEMCryptoResult SessionContext::DeactivateUsageEntry(
|
|
const std::vector<uint8_t>& pst) {
|
|
if (!usage_entry_) return OEMCrypto_ERROR_INVALID_CONTEXT;
|
|
usage_entry_->ForbidReport();
|
|
return ODK_DeactivateUsageEntry(&clock_values_);
|
|
}
|
|
|
|
OEMCryptoResult SessionContext::ReportUsage(const std::vector<uint8_t>& pst,
|
|
uint8_t* buffer,
|
|
size_t* buffer_length) {
|
|
if (!usage_entry_) return OEMCrypto_ERROR_INVALID_CONTEXT;
|
|
return usage_entry_->ReportUsage(pst, buffer, buffer_length);
|
|
}
|
|
|
|
OEMCryptoResult SessionContext::MoveEntry(uint32_t new_index) {
|
|
if (!usage_entry_) return OEMCrypto_ERROR_INVALID_CONTEXT;
|
|
return ce_->usage_table().MoveEntry(usage_entry_.get(), new_index);
|
|
}
|
|
|
|
// Internal utility function to decrypt the message
|
|
bool SessionContext::DecryptMessage(const std::vector<uint8_t>& key,
|
|
const std::vector<uint8_t>& iv,
|
|
const std::vector<uint8_t>& message,
|
|
std::vector<uint8_t>* decrypted,
|
|
uint32_t key_size) {
|
|
if (key.empty() || iv.empty() || message.empty() || !decrypted) {
|
|
LOGE("[DecryptMessage(): OEMCrypto_ERROR_INVALID_CONTEXT]");
|
|
return false;
|
|
}
|
|
if (message.size() % AES_BLOCK_SIZE != 0) {
|
|
LOGE("[DecryptMessage(): bad buffer size]");
|
|
return false;
|
|
}
|
|
|
|
decrypted->resize(message.size());
|
|
uint8_t iv_buffer[16];
|
|
memcpy(iv_buffer, &iv[0], 16);
|
|
AES_KEY aes_key;
|
|
AES_set_decrypt_key(&key[0], key_size, &aes_key);
|
|
AES_cbc_encrypt(&message[0], &(decrypted->front()), message.size(), &aes_key,
|
|
iv_buffer, AES_DECRYPT);
|
|
return true;
|
|
}
|
|
|
|
OEMCryptoResult SessionContext::DecryptSamples(
|
|
const OEMCrypto_SampleDescription* samples, size_t samples_length,
|
|
const OEMCrypto_CENCEncryptPatternDesc* pattern) {
|
|
// Iterate through all the samples and decrypt each one
|
|
for (size_t sample_index = 0; sample_index < samples_length; ++sample_index) {
|
|
const OEMCrypto_SampleDescription& sample = samples[sample_index];
|
|
|
|
// Iterate through all the subsamples and decrypt each one. A production
|
|
// implementation may be able to do something more efficient, like
|
|
// decrypting all the encrypted portions in one pass.
|
|
const uint8_t* subsample_source = sample.buffers.input_data;
|
|
OEMCrypto_DestBufferDesc subsample_dest = sample.buffers.output_descriptor;
|
|
uint8_t subsample_iv[wvoec::KEY_IV_SIZE];
|
|
static_assert(sizeof(sample.iv) == wvoec::KEY_IV_SIZE,
|
|
"The IV in OEMCrypto_SampleDescription is the wrong length.");
|
|
// Per its type, sizeof(subsample_iv) == wvoec::KEY_IV_SIZE
|
|
memcpy(subsample_iv, sample.iv, wvoec::KEY_IV_SIZE);
|
|
for (size_t subsample_index = 0; subsample_index < sample.subsamples_length;
|
|
++subsample_index) {
|
|
const OEMCrypto_SubSampleDescription& subsample =
|
|
sample.subsamples[subsample_index];
|
|
const size_t subsample_length =
|
|
subsample.num_bytes_clear + subsample.num_bytes_encrypted;
|
|
|
|
OEMCryptoResult result = ce_->SetDestination(
|
|
subsample_dest, subsample_length, subsample.subsample_flags);
|
|
if (result != OEMCrypto_SUCCESS) {
|
|
LOGE("SetDestination status: %d", static_cast<int>(result));
|
|
return result;
|
|
}
|
|
|
|
result = DecryptSubsample(subsample, subsample_source, ce_->destination(),
|
|
subsample_dest.type, subsample_iv, pattern);
|
|
if (result != OEMCrypto_SUCCESS) {
|
|
LOGE("DecryptSubsample status: %d", static_cast<int>(result));
|
|
return result;
|
|
}
|
|
|
|
result = ce_->PushDestination(subsample_dest, subsample.subsample_flags);
|
|
if (result != OEMCrypto_SUCCESS) {
|
|
LOGE("PushDestination status: %d", static_cast<int>(result));
|
|
return result;
|
|
}
|
|
|
|
// Advance the source buffer, the dest buffer, and (if necessary) the IV
|
|
subsample_source += subsample_length;
|
|
advance_dest_buffer(&subsample_dest, subsample_length);
|
|
if (subsample.num_bytes_encrypted > 0 &&
|
|
current_content_key()->ctr_mode()) {
|
|
wvcdm::AdvanceIvCtr(&subsample_iv, subsample.block_offset +
|
|
subsample.num_bytes_encrypted);
|
|
}
|
|
} // Subsample loop
|
|
} // Sample loop
|
|
|
|
return OEMCrypto_SUCCESS;
|
|
}
|
|
|
|
OEMCryptoResult SessionContext::DecryptSubsample(
|
|
const OEMCrypto_SubSampleDescription& subsample, const uint8_t* cipher_data,
|
|
uint8_t* clear_data, OEMCryptoBufferType buffer_type,
|
|
const uint8_t (&iv)[wvoec::KEY_IV_SIZE],
|
|
const OEMCrypto_CENCEncryptPatternDesc* pattern) {
|
|
// Handle the clear portion of the subsample.
|
|
if (subsample.num_bytes_clear > 0) {
|
|
if (buffer_type != OEMCrypto_BufferType_Direct) {
|
|
memmove(clear_data, cipher_data, subsample.num_bytes_clear);
|
|
}
|
|
// For the reference implementation, we quietly drop the clear direct video.
|
|
}
|
|
|
|
// Handle the encrypted portion of the subsample.
|
|
OEMCryptoResult result = OEMCrypto_SUCCESS;
|
|
if (subsample.num_bytes_encrypted > 0) {
|
|
const uint8_t* source = cipher_data + subsample.num_bytes_clear;
|
|
uint8_t* dest = clear_data + subsample.num_bytes_clear;
|
|
result = ChooseDecrypt(iv, subsample.block_offset, pattern, source,
|
|
subsample.num_bytes_encrypted, dest, buffer_type);
|
|
}
|
|
|
|
// Compute hash for FDPT.
|
|
if (compute_hash_) {
|
|
if (current_content_key() == nullptr ||
|
|
(current_content_key()->control().control_bits() &
|
|
wvoec::kControlAllowHashVerification) == 0) {
|
|
LOGE("[DecryptCENC(): OEMCrypto_ERROR_UNKNOWN_FAILURE]");
|
|
hash_error_ = OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
|
compute_hash_ = false;
|
|
current_hash_ = 0;
|
|
current_frame_number_ = 0;
|
|
} else {
|
|
if (OEMCrypto_FirstSubsample & subsample.subsample_flags) {
|
|
current_hash_ = wvcrc32Init();
|
|
}
|
|
current_hash_ = wvcrc32Cont(
|
|
clear_data, subsample.num_bytes_clear + subsample.num_bytes_encrypted,
|
|
current_hash_);
|
|
if (OEMCrypto_LastSubsample & subsample.subsample_flags) {
|
|
if (current_hash_ != given_hash_) {
|
|
LOGE("CRC for frame %u is %08x, should be %08x\n",
|
|
current_frame_number_, current_hash_, given_hash_);
|
|
// Update bad_frame_number_ only if this is the first bad frame.
|
|
if (hash_error_ == OEMCrypto_SUCCESS) {
|
|
bad_frame_number_ = current_frame_number_;
|
|
hash_error_ = OEMCrypto_ERROR_BAD_HASH;
|
|
}
|
|
}
|
|
compute_hash_ = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Return the result of the previous ChooseDecrypt() call after computing the
|
|
// hash.
|
|
return result;
|
|
}
|
|
|
|
OEMCryptoResult SessionContext::ChooseDecrypt(
|
|
const uint8_t* iv, size_t block_offset,
|
|
const OEMCrypto_CENCEncryptPatternDesc* pattern, const uint8_t* cipher_data,
|
|
size_t cipher_data_length, uint8_t* clear_data,
|
|
OEMCryptoBufferType buffer_type) {
|
|
// Check there is a content key
|
|
if (current_content_key() == nullptr) {
|
|
LOGE("[DecryptCTR(): OEMCrypto_ERROR_NO_CONTENT_KEY]");
|
|
return OEMCrypto_ERROR_DECRYPT_FAILED;
|
|
}
|
|
|
|
OEMCryptoResult result = CheckKeyUse("DecryptCENC", 0, buffer_type);
|
|
if (result != OEMCrypto_SUCCESS) return result;
|
|
|
|
const std::vector<uint8_t>& content_key = current_content_key()->value();
|
|
|
|
// Set the AES key.
|
|
if (static_cast<int>(content_key.size()) != AES_BLOCK_SIZE) {
|
|
LOGE("[DecryptCTR(): CONTENT_KEY has wrong size: %zu", content_key.size());
|
|
return OEMCrypto_ERROR_DECRYPT_FAILED;
|
|
}
|
|
const uint8_t* key_u8 = &content_key[0];
|
|
|
|
if (buffer_type == OEMCrypto_BufferType_Direct) {
|
|
// For reference implementation, we quietly drop the decrypted direct video.
|
|
return OEMCrypto_SUCCESS;
|
|
}
|
|
|
|
if (!current_content_key()->ctr_mode()) {
|
|
if (block_offset > 0 || pattern->encrypt == 0) {
|
|
return OEMCrypto_ERROR_INVALID_CONTEXT;
|
|
}
|
|
return PatternDecryptCBC(key_u8, iv, pattern, cipher_data,
|
|
cipher_data_length, clear_data);
|
|
} else {
|
|
if (pattern->skip != 0 || pattern->encrypt != 0) {
|
|
return OEMCrypto_ERROR_INVALID_CONTEXT;
|
|
}
|
|
return DecryptCTR(key_u8, iv, block_offset, cipher_data, cipher_data_length,
|
|
clear_data);
|
|
}
|
|
}
|
|
|
|
OEMCryptoResult SessionContext::PatternDecryptCBC(
|
|
const uint8_t* key, const uint8_t* initial_iv,
|
|
const OEMCrypto_CENCEncryptPatternDesc* pattern, const uint8_t* cipher_data,
|
|
size_t cipher_data_length, uint8_t* clear_data) {
|
|
AES_KEY aes_key;
|
|
AES_set_decrypt_key(&key[0], AES_BLOCK_SIZE * 8, &aes_key);
|
|
uint8_t iv[AES_BLOCK_SIZE];
|
|
uint8_t next_iv[AES_BLOCK_SIZE];
|
|
memcpy(iv, &initial_iv[0], AES_BLOCK_SIZE);
|
|
|
|
const size_t pattern_length = pattern->encrypt + pattern->skip;
|
|
if (pattern_length <= 0) return OEMCrypto_ERROR_INVALID_CONTEXT;
|
|
|
|
size_t l = 0;
|
|
size_t pattern_offset = 0;
|
|
while (l < cipher_data_length) {
|
|
const size_t size =
|
|
std::min(cipher_data_length - l, static_cast<size_t>(AES_BLOCK_SIZE));
|
|
const bool skip_block = (pattern_offset >= pattern->encrypt);
|
|
pattern_offset = (pattern_offset + 1) % pattern_length;
|
|
if (skip_block || (size < AES_BLOCK_SIZE)) {
|
|
// If we are decrypting in-place, then this block is already correct and
|
|
// can be skipped.
|
|
if (clear_data != cipher_data) {
|
|
memcpy(&clear_data[l], &cipher_data[l], size);
|
|
}
|
|
} else {
|
|
uint8_t aes_output[AES_BLOCK_SIZE];
|
|
// Save the iv for the next block, in case cipher_data is in the same
|
|
// buffer as clear_data.
|
|
memcpy(next_iv, &cipher_data[l], AES_BLOCK_SIZE);
|
|
AES_decrypt(&cipher_data[l], aes_output, &aes_key);
|
|
for (size_t n = 0; n < AES_BLOCK_SIZE; n++) {
|
|
clear_data[l + n] = aes_output[n] ^ iv[n];
|
|
}
|
|
memcpy(iv, next_iv, AES_BLOCK_SIZE);
|
|
}
|
|
l += size;
|
|
}
|
|
return OEMCrypto_SUCCESS;
|
|
}
|
|
|
|
OEMCryptoResult SessionContext::DecryptCTR(const uint8_t* key_u8,
|
|
const uint8_t* iv,
|
|
size_t block_offset,
|
|
const uint8_t* cipher_data,
|
|
size_t cipher_data_length,
|
|
uint8_t* clear_data) {
|
|
if (block_offset >= AES_BLOCK_SIZE) return OEMCrypto_ERROR_INVALID_CONTEXT;
|
|
|
|
// Local copy (will be modified).
|
|
// Allocated as 64-bit ints to enforce 64-bit alignment for later access as a
|
|
// 64-bit value.
|
|
uint64_t aes_iv[2];
|
|
assert(sizeof(aes_iv) == AES_BLOCK_SIZE);
|
|
// The double-cast is needed to comply with strict aliasing rules.
|
|
uint8_t* aes_iv_u8 =
|
|
reinterpret_cast<uint8_t*>(reinterpret_cast<void*>(aes_iv));
|
|
memcpy(aes_iv_u8, &iv[0], AES_BLOCK_SIZE);
|
|
|
|
// The CENC spec specifies we increment only the low 64 bits of the IV
|
|
// counter, and leave the high 64 bits alone. This is different from the
|
|
// OpenSSL implementation, which increments the entire 128 bit iv. That is
|
|
// why we implement the CTR loop ourselves.
|
|
size_t l = 0;
|
|
if (block_offset > 0 && l < cipher_data_length) {
|
|
// Encrypt the IV.
|
|
uint8_t ecount_buf[AES_BLOCK_SIZE];
|
|
|
|
AES_KEY aes_key;
|
|
if (AES_set_encrypt_key(key_u8, AES_BLOCK_SIZE * 8, &aes_key) != 0) {
|
|
LOGE("[DecryptCTR(): FAILURE]");
|
|
return OEMCrypto_ERROR_DECRYPT_FAILED;
|
|
}
|
|
AES_encrypt(aes_iv_u8, ecount_buf, &aes_key);
|
|
for (int n = block_offset; n < AES_BLOCK_SIZE && l < cipher_data_length;
|
|
++n, ++l) {
|
|
clear_data[l] = cipher_data[l] ^ ecount_buf[n];
|
|
}
|
|
ctr128_inc64(aes_iv_u8);
|
|
block_offset = 0;
|
|
}
|
|
|
|
uint64_t remaining = cipher_data_length - l;
|
|
int out_len = 0;
|
|
|
|
while (remaining) {
|
|
EVP_CIPHER_CTX* evp_cipher_ctx = EVP_CIPHER_CTX_new();
|
|
EVP_CIPHER_CTX_set_padding(evp_cipher_ctx, 0);
|
|
if (!EVP_DecryptInit_ex(evp_cipher_ctx, EVP_aes_128_ctr(), nullptr, key_u8,
|
|
aes_iv_u8)) {
|
|
LOGE("[DecryptCTR(): EVP_INIT ERROR]");
|
|
EVP_CIPHER_CTX_free(evp_cipher_ctx);
|
|
return OEMCrypto_ERROR_DECRYPT_FAILED;
|
|
}
|
|
|
|
// Test the MSB of the counter portion of the initialization vector. If the
|
|
// value is 0xFF the counter is near wrapping. In this case we calculate
|
|
// the number of bytes we can safely decrypt before the counter wraps.
|
|
uint64_t decrypt_length = 0;
|
|
if (aes_iv_u8[8] == 0xFF) {
|
|
uint64_t bottom_64_bits = wvcdm::ntohll64(aes_iv[1]);
|
|
uint64_t bytes_before_iv_wrap = (~bottom_64_bits + 1) * AES_BLOCK_SIZE;
|
|
decrypt_length =
|
|
bytes_before_iv_wrap < remaining ? bytes_before_iv_wrap : remaining;
|
|
} else {
|
|
decrypt_length = remaining;
|
|
}
|
|
|
|
if (!EVP_DecryptUpdate(evp_cipher_ctx, &clear_data[l], &out_len,
|
|
&cipher_data[l], decrypt_length)) {
|
|
LOGE("[DecryptCTR(): EVP_UPDATE_ERROR]");
|
|
EVP_CIPHER_CTX_free(evp_cipher_ctx);
|
|
return OEMCrypto_ERROR_DECRYPT_FAILED;
|
|
}
|
|
l += decrypt_length;
|
|
remaining = cipher_data_length - l;
|
|
|
|
int final;
|
|
if (!EVP_DecryptFinal_ex(evp_cipher_ctx,
|
|
&clear_data[cipher_data_length - remaining],
|
|
&final)) {
|
|
LOGE("[DecryptCTR(): EVP_FINAL_ERROR]");
|
|
EVP_CIPHER_CTX_free(evp_cipher_ctx);
|
|
return OEMCrypto_ERROR_DECRYPT_FAILED;
|
|
}
|
|
EVP_CIPHER_CTX_free(evp_cipher_ctx);
|
|
|
|
// If remaining is not zero, reset the iv before the second pass.
|
|
if (remaining) {
|
|
memcpy(aes_iv_u8, &iv[0], AES_BLOCK_SIZE);
|
|
memset(&aes_iv_u8[8], 0, AES_BLOCK_SIZE / 2);
|
|
}
|
|
}
|
|
return OEMCrypto_SUCCESS;
|
|
}
|
|
|
|
OEMCryptoResult SessionContext::SetDecryptHash(uint32_t frame_number,
|
|
const uint8_t* hash,
|
|
size_t hash_length) {
|
|
if (hash_length < sizeof(uint32_t)) {
|
|
LOGE("[SetDecryptHash(): short buffer]");
|
|
return OEMCrypto_ERROR_SHORT_BUFFER;
|
|
}
|
|
if (hash_length > sizeof(uint32_t)) {
|
|
LOGE("[SetDecryptHash(): long buffer]");
|
|
return OEMCrypto_ERROR_BUFFER_TOO_LARGE;
|
|
}
|
|
compute_hash_ = true;
|
|
current_frame_number_ = frame_number;
|
|
given_hash_ = *reinterpret_cast<const uint32_t*>(hash);
|
|
return OEMCrypto_SUCCESS;
|
|
}
|
|
|
|
OEMCryptoResult SessionContext::GetHashErrorCode(
|
|
uint32_t* failed_frame_number) {
|
|
if (failed_frame_number == nullptr) return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
|
if (hash_error_ != OEMCrypto_SUCCESS)
|
|
*failed_frame_number = bad_frame_number_;
|
|
return hash_error_;
|
|
}
|
|
|
|
bool SessionContext::set_nonce(uint32_t nonce) {
|
|
if (state_nonce_created_) return false;
|
|
if (nonce == 0) return false;
|
|
state_nonce_created_ = true;
|
|
ODK_SetNonceValues(&nonce_values_, nonce);
|
|
return true;
|
|
}
|
|
|
|
} // namespace wvoec_ref
|