diff --git a/libwvdrmengine/docs/WidevineModularDRMSecurityIntegrationGuideforCENC.pdf b/libwvdrmengine/docs/WidevineModularDRMSecurityIntegrationGuideforCENC.pdf index 5028ad1e..37a208d3 100644 Binary files a/libwvdrmengine/docs/WidevineModularDRMSecurityIntegrationGuideforCENC.pdf and b/libwvdrmengine/docs/WidevineModularDRMSecurityIntegrationGuideforCENC.pdf differ diff --git a/libwvdrmengine/level3/arm/libwvlevel3.a b/libwvdrmengine/level3/arm/libwvlevel3.a index b9b5e23f..71d767d4 100644 Binary files a/libwvdrmengine/level3/arm/libwvlevel3.a and b/libwvdrmengine/level3/arm/libwvlevel3.a differ diff --git a/libwvdrmengine/oemcrypto/mock/src/oemcrypto_engine_mock.cpp b/libwvdrmengine/oemcrypto/mock/src/oemcrypto_engine_mock.cpp index 07b2e053..dbcf37a3 100644 --- a/libwvdrmengine/oemcrypto/mock/src/oemcrypto_engine_mock.cpp +++ b/libwvdrmengine/oemcrypto/mock/src/oemcrypto_engine_mock.cpp @@ -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 key_data_iv; std::vector key_control; std::vector key_control_iv; - std::vector pstv(pst, pst + pst_length); + std::vector 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); diff --git a/libwvdrmengine/oemcrypto/mock/src/oemcrypto_mock.cpp b/libwvdrmengine/oemcrypto/mock/src/oemcrypto_mock.cpp index 2efe926f..f149cd7f 100644 --- a/libwvdrmengine/oemcrypto/mock/src/oemcrypto_mock.cpp +++ b/libwvdrmengine/oemcrypto/mock/src/oemcrypto_mock.cpp @@ -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++) { diff --git a/libwvdrmengine/oemcrypto/test/oemcrypto_test.cpp b/libwvdrmengine/oemcrypto/test/oemcrypto_test.cpp index 0c436808..f3c144b5 100644 --- a/libwvdrmengine/oemcrypto/test/oemcrypto_test.cpp +++ b/libwvdrmengine/oemcrypto/test/oemcrypto_test.cpp @@ -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,9 +5197,8 @@ TEST_P(DISABLED_UsageTableTest, BadReloadOfflineLicense) { Session s2; s2.open(); s2.GenerateDerivedKeys(); - s2.FillSimpleMessage( - 0, wvoec_mock::kControlNonceEnabled | wvoec_mock::kControlNonceOrEntry, - s2.get_nonce(), pst); + s2.FillSimpleMessage(0, wvoec_mock::kControlNonceOrEntry, + s2.get_nonce(), pst); s2.EncryptAndSign(); uint8_t* pst_ptr = s2.encrypted_license().pst; ASSERT_NE(OEMCrypto_SUCCESS, @@ -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,9 +5310,8 @@ TEST_P(DISABLED_UsageTableTest, BadRange) { Session s; 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(); uint8_t* pst_ptr = s.license().pst; // Bad: not in encrypted_license. ASSERT_NE(