Treat the (0,0) Pattern as 'cbcs'

(This is a merge of http://go/wvgerrit/94928.)

In OEMCrypto v16, we dropped support for 'cens' and 'cbc1'. However, we
did not redefine the pattern (0,0) to be a valid pattern for 'cbcs', even
though it was no longer being used to signal 'cbc1'. Instead, we made
the CDM reject CTR with a pattern ('cens') and CBC with a (0,0) pattern
('cbc1') to mirror the behavior of OEMCrypto v16.

However, some apps have been using 'cbc1' mode to decrypt audio in
'cbcs' content. This is normally not possible but is possible for a
subset of content. Furthermore, it is easy to do by accident because of
the way most packagers package 'cbcs' audio and the special significance
Widevine has historically given the (0,0) pattern.

This patch updates the CDM to not reject CBC with a (0,0) pattern but
instead treat it as 'cbcs' content. To decrypt it correctly, the pattern
is treated specially inside the CDM core and converted to the
recommended equivalent pattern — (10,0) — before passing the content to
OEMCrypto.

For more specifics, please see the design doc: http://go/vclfg

Bug: 150219982
Test: ExoPlayer Demo App 'cbcs' Content
Test: GTS 'cbcs' Content
Change-Id: I334ff15db5f7b7d62040a036ba6d17515c3caee4
This commit is contained in:
John W. Bruce
2020-03-04 12:11:29 -08:00
parent d3f10ece7c
commit 82951b01ef
5 changed files with 9 additions and 32 deletions

View File

@@ -1468,6 +1468,13 @@ CdmResponseType CryptoSession::Decrypt(
// Convert the pattern descriptor // Convert the pattern descriptor
OEMCrypto_CENCEncryptPatternDesc oec_pattern{params.pattern.encrypt_blocks, OEMCrypto_CENCEncryptPatternDesc oec_pattern{params.pattern.encrypt_blocks,
params.pattern.skip_blocks}; params.pattern.skip_blocks};
// TODO(b/146581957): Remove this workaround once OEMCrypto treats (0,0) as
// 'cbcs' instead of 'cbc1'.
if (params.cipher_mode == kCipherModeCbc && oec_pattern.encrypt == 0 &&
oec_pattern.skip == 0) {
// (10, 0) is the preferred pattern for decrypting every block in 'cbcs'
oec_pattern.encrypt = 10;
}
// Check if a key needs to be selected // Check if a key needs to be selected
if (is_any_sample_protected) { if (is_any_sample_protected) {

View File

@@ -111,11 +111,6 @@ ssize_t WVCryptoPlugin::decrypt(bool secure, const uint8_t key[KEY_ID_SIZE],
errorDetailMsg->setTo( errorDetailMsg->setTo(
"The 'cens' schema is not supported by Widevine CDM."); "The 'cens' schema is not supported by Widevine CDM.");
return kErrorUnsupportedCrypto; return kErrorUnsupportedCrypto;
} else if (mode == kMode_AES_CBC &&
(pattern.mEncryptBlocks == 0 && pattern.mSkipBlocks == 0)) {
errorDetailMsg->setTo(
"The 'cbc1' schema is not supported by Widevine CDM.");
return kErrorUnsupportedCrypto;
} }
// Convert parameters to the form the CDM wishes to consume them in. // Convert parameters to the form the CDM wishes to consume them in.

View File

@@ -185,11 +185,6 @@ Return<void> WVCryptoPlugin::decrypt_1_2(
_hidl_cb(Status_V1_2::BAD_VALUE, 0, _hidl_cb(Status_V1_2::BAD_VALUE, 0,
"The 'cens' schema is not supported by Widevine CDM."); "The 'cens' schema is not supported by Widevine CDM.");
return Void(); return Void();
} else if (mode == Mode::AES_CBC &&
(pattern.encryptBlocks == 0 && pattern.skipBlocks == 0)) {
_hidl_cb(Status_V1_2::BAD_VALUE, 0,
"The 'cbc1' schema is not supported by Widevine CDM.");
return Void();
} }
// Convert parameters to the form the CDM wishes to consume them in. // Convert parameters to the form the CDM wishes to consume them in.

View File

@@ -339,7 +339,7 @@ TEST_F(WVCryptoPluginTest, AttemptsToDecrypt) {
"WVCryptoPlugin reported a detailed error message."; "WVCryptoPlugin reported a detailed error message.";
} }
TEST_F(WVCryptoPluginTest, RejectsCensAndCbc1) { TEST_F(WVCryptoPluginTest, RejectsCens) {
android::sp<StrictMock<MockCDM>> cdm = new StrictMock<MockCDM>(); android::sp<StrictMock<MockCDM>> cdm = new StrictMock<MockCDM>();
constexpr size_t kSubSampleCount = 2; constexpr size_t kSubSampleCount = 2;
@@ -380,7 +380,6 @@ TEST_F(WVCryptoPluginTest, RejectsCensAndCbc1) {
static_cast<void*>(destination->unsecurePointer())); static_cast<void*>(destination->unsecurePointer()));
ASSERT_NE(pDest, nullptr); ASSERT_NE(pDest, nullptr);
Pattern noPattern = { 0, 0 };
Pattern recommendedPattern = { 1, 9 }; Pattern recommendedPattern = { 1, 9 };
// Provide the expected behavior for IsOpenSession // Provide the expected behavior for IsOpenSession
@@ -409,15 +408,6 @@ TEST_F(WVCryptoPluginTest, RejectsCensAndCbc1) {
EXPECT_EQ(status, Status::BAD_VALUE); EXPECT_EQ(status, Status::BAD_VALUE);
EXPECT_EQ(bytesWritten, 0); EXPECT_EQ(bytesWritten, 0);
}); });
plugin.decrypt(
false, hidl_array<uint8_t, 16>(keyId), hidl_array<uint8_t, 16>(iv),
Mode::AES_CBC, noPattern, hSubSamples, sourceBuffer, 0, hDestination,
[&](Status status, uint32_t bytesWritten,
hidl_string /* errorDetailMessage */) {
EXPECT_EQ(status, Status::BAD_VALUE);
EXPECT_EQ(bytesWritten, 0);
});
} }
TEST_F(WVCryptoPluginTest, CommunicatesSecureBufferRequest) { TEST_F(WVCryptoPluginTest, CommunicatesSecureBufferRequest) {

View File

@@ -221,7 +221,7 @@ TEST_F(WVCryptoPluginTest, AttemptsToDecrypt) {
"WVCryptoPlugin reported a detailed error message."; "WVCryptoPlugin reported a detailed error message.";
} }
TEST_F(WVCryptoPluginTest, RejectsCensAndCbc1) { TEST_F(WVCryptoPluginTest, RejectsCens) {
android::sp<StrictMock<MockCDM>> cdm = new StrictMock<MockCDM>(); android::sp<StrictMock<MockCDM>> cdm = new StrictMock<MockCDM>();
constexpr size_t kSubSampleCount = 2; constexpr size_t kSubSampleCount = 2;
@@ -244,7 +244,6 @@ TEST_F(WVCryptoPluginTest, RejectsCensAndCbc1) {
fread(inputData, sizeof(uint8_t), kDataSize, fp); fread(inputData, sizeof(uint8_t), kDataSize, fp);
fclose(fp); fclose(fp);
android::CryptoPlugin::Pattern noPattern = { 0, 0 };
android::CryptoPlugin::Pattern recommendedPattern = { 1, 9 }; android::CryptoPlugin::Pattern recommendedPattern = { 1, 9 };
// Provide the expected behavior for IsOpenSession // Provide the expected behavior for IsOpenSession
@@ -267,15 +266,6 @@ TEST_F(WVCryptoPluginTest, RejectsCensAndCbc1) {
"WVCryptoPlugin did not return an error for 'cens'."; "WVCryptoPlugin did not return an error for 'cens'.";
EXPECT_NE(errorDetailMessage.size(), 0u) << EXPECT_NE(errorDetailMessage.size(), 0u) <<
"WVCryptoPlugin did not report a detailed error message for 'cens'."; "WVCryptoPlugin did not report a detailed error message for 'cens'.";
res = plugin.decrypt(false, keyId, iv, CryptoPlugin::kMode_AES_CBC,
noPattern, inputData, subSamples, kSubSampleCount,
outputData, &errorDetailMessage);
EXPECT_EQ(res, kErrorUnsupportedCrypto) <<
"WVCryptoPlugin did not return an error for 'cbc1'.";
EXPECT_NE(errorDetailMessage.size(), 0u) <<
"WVCryptoPlugin did not report a detailed error message for 'cbc1'.";
} }
TEST_F(WVCryptoPluginTest, CommunicatesSecureBufferRequest) { TEST_F(WVCryptoPluginTest, CommunicatesSecureBufferRequest) {