Catch null pst in OEMCrypto

This is a copy of
https://widevine-internal-review.googlesource.com/#/c/11030

It is an error for the key control block to have a nonzero replay
control flag and a null pst.  This CL adds unit tests to
oemcrypto_test to verify that oemcrypto checkes this.  A unit test is
also added for verifying that an offline license has a valid nonce the
first time it is loaded.

It also updates the reference implementation (mock) to check that the
pst is not empty when the replay control flag is nonzero.

It also updates the level 3 implementation to check that the pst is
not empty when the replay control flag is nonzero.

This change is compiled into the arm library, but because of
compilation errors, is not included in x86 or mips.

    Current Library Version:
    arm:  Level3 Library Aug 27 2014 18:42:40

bug: 16525204 OEMCrypto unit test for reloading offline license
bug: 16844305 Mock OEMCrypto does not catch null pst
Change-Id: Icdb090e80fc92522c187b26f30e5ba082f26363b
This commit is contained in:
Fred Gylys-Colwell
2014-09-03 11:46:10 -07:00
parent 5800ecc15e
commit e51f8ba7a1
5 changed files with 95 additions and 12 deletions

View File

@@ -344,6 +344,13 @@ bool SessionContext::CheckNonceOrEntry(const KeyControlBlock& key_control_block,
switch (key_control_block.control_bits() & kControlReplayMask) {
case kControlNonceRequired: // Online license. Nonce always required.
if (!CheckNonce(key_control_block.nonce())) return false;
if (pst.size() == 0) {
LOGE("KCB: PST null for kControlNonceRequired.");
return false;
}
if (!(key_control_block.control_bits() & kControlNonceEnabled)) {
LOGE("KCB: Server provided Nonce_Required but Nonce_Enabled = 0.");
}
if (!usage_entry_) {
if (ce_->usage_table()->FindEntry(pst)) {
LOGE("KCB: Cannot create duplicate entries in usage table.");
@@ -353,6 +360,13 @@ bool SessionContext::CheckNonceOrEntry(const KeyControlBlock& key_control_block,
}
break; // Offline license. Nonce required on first use.
case kControlNonceOrEntry:
if (key_control_block.control_bits() & kControlNonceEnabled) {
LOGE("KCB: Server provided NonceOrEntry but Nonce_Enabled = 1.");
}
if (pst.size() == 0) {
LOGE("KCB: PST null for kControlNonceOrEntry.");
return false;
}
if (!usage_entry_) {
usage_entry_ = ce_->usage_table()->FindEntry(pst);
if (usage_entry_) {
@@ -405,7 +419,8 @@ OEMCryptoResult SessionContext::LoadKeys(
std::vector<uint8_t> key_data_iv;
std::vector<uint8_t> key_control;
std::vector<uint8_t> key_control_iv;
std::vector<uint8_t> pstv(pst, pst + pst_length);
std::vector<uint8_t> pstv;
if (pst_length > 0) pstv.assign(pst, pst + pst_length);
for (unsigned int i = 0; i < num_keys; i++) {
key_id.assign(key_array[i].key_id,
key_array[i].key_id + key_array[i].key_id_length);

View File

@@ -318,14 +318,21 @@ OEMCryptoResult OEMCrypto_LoadKeys(OEMCrypto_SESSION session,
return OEMCrypto_ERROR_INVALID_CONTEXT;
}
// Later on, we use pst_length to verify the the pst is valid. This makes
// sure that we aren't given a null string but told it has postiive length.
if ((pst == NULL && pst_length > 0) || (pst != NULL && pst_length == 0)) {
LOGE("[OEMCrypto_LoadKeys(): OEMCrypto_ERROR_INVALID_ONCTEXT - null pst.]");
return OEMCrypto_ERROR_INVALID_CONTEXT;
}
// Range check
if (!RangeCheck(message, message_length, enc_mac_keys,
2 * wvcdm::MAC_KEY_SIZE, true) ||
!RangeCheck(message, message_length, enc_mac_key_iv, wvcdm::KEY_IV_SIZE,
true) ||
!RangeCheck(message, message_length, pst, pst_length, true)) {
LOGE("[OEMCrypto_LoadKeys(): OEMCrypto_ERROR_SIGNATURE_FAILURE - range check.]");
return OEMCrypto_ERROR_SIGNATURE_FAILURE;
LOGE("[OEMCrypto_LoadKeys(): OEMCrypto_ERROR_INVALID_CONTEXT - range check.]");
return OEMCrypto_ERROR_INVALID_CONTEXT;
}
for (unsigned int i = 0; i < num_keys; i++) {

View File

@@ -4525,9 +4525,7 @@ class DISABLED_UsageTableTest : public DISABLED_GenericDRMTest,
void LoadOfflineLicense(Session& s, const std::string& pst) {
s.open();
s.GenerateDerivedKeys();
s.FillSimpleMessage(
0, wvoec_mock::kControlNonceEnabled | wvoec_mock::kControlNonceOrEntry,
s.get_nonce(), pst);
s.FillSimpleMessage(0, wvoec_mock::kControlNonceOrEntry, s.get_nonce(), pst);
s.EncryptAndSign();
s.LoadTestKeys(pst, new_mac_keys_);
ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_UpdateUsageTable());
@@ -4613,6 +4611,27 @@ TEST_P(DISABLED_UsageTableTest, RepeatOnlineLicense) {
}
}
// A license with non-zero replay control bits needs a valid pst..
TEST_P(DISABLED_UsageTableTest, OnlineEmptyPST) {
if (OEMCrypto_SupportsUsageTable()) {
ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_UpdateUsageTable());
Session s;
s.open();
s.GenerateDerivedKeys();
s.FillSimpleMessage(
0, wvoec_mock::kControlNonceEnabled | wvoec_mock::kControlNonceRequired,
s.get_nonce());
s.EncryptAndSign();
OEMCryptoResult sts = OEMCrypto_LoadKeys(
s.session_id(), s.message_ptr(), sizeof(MessageData), &s.signature()[0],
s.signature().size(), s.encrypted_license().mac_key_iv,
s.encrypted_license().mac_keys, kNumKeys, s.key_array(),
NULL, 0);
ASSERT_NE(OEMCrypto_SUCCESS, sts);
s.close();
}
}
TEST_P(DISABLED_UsageTableTest, EmptyTable) {
if (OEMCrypto_SupportsUsageTable()) {
ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_UpdateUsageTable());
@@ -5178,8 +5197,7 @@ TEST_P(DISABLED_UsageTableTest, BadReloadOfflineLicense) {
Session s2;
s2.open();
s2.GenerateDerivedKeys();
s2.FillSimpleMessage(
0, wvoec_mock::kControlNonceEnabled | wvoec_mock::kControlNonceOrEntry,
s2.FillSimpleMessage(0, wvoec_mock::kControlNonceOrEntry,
s2.get_nonce(), pst);
s2.EncryptAndSign();
uint8_t* pst_ptr = s2.encrypted_license().pst;
@@ -5201,6 +5219,50 @@ TEST_P(DISABLED_UsageTableTest, BadReloadOfflineLicense) {
}
}
// An offline license should not load on the first call if the nonce is bad.
TEST_P(DISABLED_UsageTableTest, OfflineBadNonce) {
if (OEMCrypto_SupportsUsageTable()) {
std::string pst = "my_pst";
ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_UpdateUsageTable());
Session s;
s.open();
s.GenerateDerivedKeys();
s.FillSimpleMessage(
0, wvoec_mock::kControlNonceEnabled | wvoec_mock::kControlNonceOrEntry,
42, pst);
s.EncryptAndSign();
uint8_t* pst_ptr = s.encrypted_license().pst;
OEMCryptoResult sts = OEMCrypto_LoadKeys(
s.session_id(), s.message_ptr(), sizeof(MessageData), &s.signature()[0],
s.signature().size(), s.encrypted_license().mac_key_iv,
s.encrypted_license().mac_keys, kNumKeys, s.key_array(),
pst_ptr, pst.length());
ASSERT_NE(OEMCrypto_SUCCESS, sts);
s.close();
}
}
// An offline license needs a valid pst.
TEST_P(DISABLED_UsageTableTest, OfflineEmptyPST) {
if (OEMCrypto_SupportsUsageTable()) {
ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_UpdateUsageTable());
Session s;
s.open();
s.GenerateDerivedKeys();
s.FillSimpleMessage(
0, wvoec_mock::kControlNonceEnabled | wvoec_mock::kControlNonceOrEntry,
s.get_nonce());
s.EncryptAndSign();
OEMCryptoResult sts = OEMCrypto_LoadKeys(
s.session_id(), s.message_ptr(), sizeof(MessageData), &s.signature()[0],
s.signature().size(), s.encrypted_license().mac_key_iv,
s.encrypted_license().mac_keys, kNumKeys, s.key_array(),
NULL, 0);
ASSERT_NE(OEMCrypto_SUCCESS, sts);
s.close();
}
}
TEST_P(DISABLED_UsageTableTest, DeactivateOfflineLicense) {
if (OEMCrypto_SupportsUsageTable()) {
std::string pst = "my_pst";
@@ -5248,8 +5310,7 @@ TEST_P(DISABLED_UsageTableTest, BadRange) {
Session s;
s.open();
s.GenerateDerivedKeys();
s.FillSimpleMessage(
0, wvoec_mock::kControlNonceEnabled | wvoec_mock::kControlNonceOrEntry,
s.FillSimpleMessage(0, wvoec_mock::kControlNonceOrEntry,
s.get_nonce(), pst);
s.EncryptAndSign();
uint8_t* pst_ptr = s.license().pst; // Bad: not in encrypted_license.