Removing Non-HIDL Widevine DRM Plugin for Android
[ Merge of http://go/wvgerrit/138089 ] Non-hidl code is no longer needed in the widevine android plugin. By doing work this cleanup will save the team from having to keep maintaining code that isn't needed anymore. Clean up steps taken as follows: 1. Remove non-hidl code but keep commonly used code 2. Remove legacy test code 3. Remove legacy test build and test entry Bug: 69573078 Bug: 172285246 Test: unit tests, GtsMediaTestCases, CtsMediaDrmTestCases Change-Id: I75938d69413c631ef6be82da3d885ba173efc581
This commit is contained in:
committed by
Kyle Zhang
parent
2046fc05fa
commit
0b02296014
@@ -11,56 +11,6 @@
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Builds libwvdrmcryptoplugin
|
||||
//
|
||||
// *** THIS PACKAGE HAS SPECIAL LICENSING CONDITIONS. PLEASE
|
||||
// CONSULT THE OWNERS AND opensource-licensing@google.com BEFORE
|
||||
// DEPENDING ON IT IN YOUR PROJECT. ***
|
||||
package {
|
||||
// See: http://go/android-license-faq
|
||||
// A large-scale-change added 'default_applicable_licenses' to import
|
||||
// all of the 'license_kinds' from "vendor_widevine_license"
|
||||
// to get the below license kinds:
|
||||
// legacy_by_exception_only (by exception only)
|
||||
default_applicable_licenses: ["vendor_widevine_license"],
|
||||
}
|
||||
|
||||
cc_library_static {
|
||||
name: "libwvdrmcryptoplugin",
|
||||
|
||||
srcs: ["src/WVCryptoPlugin.cpp"],
|
||||
|
||||
include_dirs: [
|
||||
"frameworks/av/include",
|
||||
"frameworks/native/include",
|
||||
"vendor/widevine/libwvdrmengine/cdm/core/include",
|
||||
"vendor/widevine/libwvdrmengine/cdm/include",
|
||||
"vendor/widevine/libwvdrmengine/cdm/metrics/include",
|
||||
"vendor/widevine/libwvdrmengine/cdm/util/include",
|
||||
"vendor/widevine/libwvdrmengine/include",
|
||||
"vendor/widevine/libwvdrmengine/mediacrypto/include",
|
||||
"vendor/widevine/libwvdrmengine/oemcrypto/include",
|
||||
],
|
||||
|
||||
header_libs: [
|
||||
"libstagefright_headers",
|
||||
"libstagefright_foundation_headers",
|
||||
"libutils_headers",
|
||||
],
|
||||
|
||||
static_libs: ["libcdm_protos"],
|
||||
|
||||
shared_libs: [
|
||||
"libcrypto",
|
||||
"liblog",
|
||||
],
|
||||
|
||||
proprietary: true,
|
||||
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Builds libwvdrmcryptoplugin_hidl
|
||||
//
|
||||
@@ -76,10 +26,9 @@ cc_library_static {
|
||||
"vendor/widevine/libwvdrmengine/cdm/include",
|
||||
"vendor/widevine/libwvdrmengine/cdm/metrics/include",
|
||||
"vendor/widevine/libwvdrmengine/cdm/util/include",
|
||||
"vendor/widevine/libwvdrmengine/include_hidl",
|
||||
"vendor/widevine/libwvdrmengine/include_hidl",
|
||||
"vendor/widevine/libwvdrmengine/include",
|
||||
"vendor/widevine/libwvdrmengine/mediacrypto/include_hidl",
|
||||
"vendor/widevine/libwvdrmengine/mediacrypto/include",
|
||||
"vendor/widevine/libwvdrmengine/oemcrypto/include",
|
||||
],
|
||||
|
||||
|
||||
@@ -1,57 +0,0 @@
|
||||
//
|
||||
// Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary
|
||||
// source code may only be used and distributed under the Widevine License
|
||||
// Agreement.
|
||||
//
|
||||
|
||||
#ifndef WV_CRYPTO_PLUGIN_H_
|
||||
#define WV_CRYPTO_PLUGIN_H_
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include "utils/StrongPointer.h"
|
||||
#include "utils/Vector.h"
|
||||
|
||||
#include "media/hardware/CryptoAPI.h"
|
||||
#include "media/stagefright/foundation/ABase.h"
|
||||
#include "media/stagefright/foundation/AString.h"
|
||||
#include "wv_content_decryption_module.h"
|
||||
|
||||
namespace wvdrm {
|
||||
|
||||
class WVCryptoPlugin : public android::CryptoPlugin {
|
||||
public:
|
||||
WVCryptoPlugin(const void* data, size_t size,
|
||||
const android::sp<wvcdm::WvContentDecryptionModule>& cdm);
|
||||
virtual ~WVCryptoPlugin() {}
|
||||
|
||||
virtual bool requiresSecureDecoderComponent(const char* mime) const;
|
||||
|
||||
virtual void notifyResolution(uint32_t width, uint32_t height);
|
||||
|
||||
virtual android::status_t setMediaDrmSession(
|
||||
const android::Vector<uint8_t>& sessionId);
|
||||
|
||||
virtual ssize_t decrypt(bool secure, const uint8_t key[16],
|
||||
const uint8_t iv[16], Mode mode, const Pattern &pattern,
|
||||
const void* srcPtr,
|
||||
const SubSample* subSamples, size_t numSubSamples,
|
||||
void* dstPtr, android::AString* errorDetailMsg);
|
||||
|
||||
private:
|
||||
DISALLOW_EVIL_CONSTRUCTORS(WVCryptoPlugin);
|
||||
|
||||
android::sp<wvcdm::WvContentDecryptionModule> const mCDM;
|
||||
|
||||
bool mTestMode;
|
||||
wvcdm::CdmSessionId mSessionId;
|
||||
|
||||
wvcdm::CdmSessionId configureTestMode(const void* data, size_t size);
|
||||
android::status_t attemptDecrypt(
|
||||
const wvcdm::CdmDecryptionParametersV16& params,
|
||||
bool haveEncryptedSubsamples, android::AString* errorDetailMsg);
|
||||
};
|
||||
|
||||
} // namespace wvdrm
|
||||
|
||||
#endif // WV_CRYPTO_PLUGIN_H_
|
||||
@@ -1,261 +0,0 @@
|
||||
//
|
||||
// Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary
|
||||
// source code may only be used and distributed under the Widevine License
|
||||
// Agreement.
|
||||
//
|
||||
|
||||
//#define LOG_NDEBUG 0
|
||||
#define LOG_TAG "WVCdm"
|
||||
#include <utils/Log.h>
|
||||
|
||||
#include "WVCryptoPlugin.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <endian.h>
|
||||
#include <iterator>
|
||||
#include <string.h>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "mapErrors-inl.h"
|
||||
#include "media/stagefright/MediaErrors.h"
|
||||
#include "OEMCryptoCENC.h"
|
||||
#include "openssl/sha.h"
|
||||
#include "utils/Errors.h"
|
||||
#include "utils/String8.h"
|
||||
#include "wv_cdm_constants.h"
|
||||
#include "WVErrors.h"
|
||||
|
||||
namespace wvdrm {
|
||||
|
||||
using namespace android;
|
||||
using namespace std;
|
||||
using namespace wvcdm;
|
||||
|
||||
WVCryptoPlugin::WVCryptoPlugin(const void* data, size_t size,
|
||||
const sp<WvContentDecryptionModule>& cdm)
|
||||
: mCDM(cdm),
|
||||
mTestMode(false),
|
||||
mSessionId(configureTestMode(data, size)) {}
|
||||
|
||||
CdmSessionId WVCryptoPlugin::configureTestMode(const void* data, size_t size) {
|
||||
CdmSessionId sessionId;
|
||||
if (data != NULL) {
|
||||
sessionId.assign(static_cast<const char *>(data), size);
|
||||
size_t index = sessionId.find("test_mode");
|
||||
if (index != string::npos) {
|
||||
sessionId = sessionId.substr(0, index);
|
||||
mTestMode = true;
|
||||
}
|
||||
}
|
||||
if (!mCDM->IsOpenSession(sessionId)) {
|
||||
sessionId.clear();
|
||||
}
|
||||
return sessionId;
|
||||
}
|
||||
|
||||
bool WVCryptoPlugin::requiresSecureDecoderComponent(const char* mime) const {
|
||||
if (!strncasecmp(mime, "video/", 6)) {
|
||||
// Type is video, so query CDM to see if we require a secure decoder.
|
||||
CdmQueryMap status;
|
||||
|
||||
CdmResponseType res = mCDM->QuerySessionStatus(mSessionId, &status);
|
||||
|
||||
if (!isCdmResponseTypeSuccess(res)) {
|
||||
ALOGE("Error querying CDM status: %u", res);
|
||||
return false;
|
||||
}
|
||||
|
||||
return status[QUERY_KEY_SECURITY_LEVEL] == QUERY_VALUE_SECURITY_LEVEL_L1;
|
||||
} else {
|
||||
// Type is not video, so never require a secure decoder.
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void WVCryptoPlugin::notifyResolution(uint32_t width, uint32_t height) {
|
||||
mCDM->NotifyResolution(mSessionId, width, height);
|
||||
}
|
||||
|
||||
status_t WVCryptoPlugin::setMediaDrmSession(const Vector<uint8_t>& sessionId) {
|
||||
CdmSessionId cdmSessionId(reinterpret_cast<const char *>(sessionId.array()),
|
||||
sessionId.size());
|
||||
if (sessionId.size() == 0) {
|
||||
return android::BAD_VALUE;
|
||||
}
|
||||
if (!mCDM->IsOpenSession(cdmSessionId)) {
|
||||
return android::ERROR_DRM_SESSION_NOT_OPENED;
|
||||
} else {
|
||||
mSessionId = cdmSessionId;
|
||||
return android::NO_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
// Returns negative values for error code and positive values for the size of
|
||||
// decrypted data. In theory, the output size can be larger than the input
|
||||
// size, but in practice this should never happen for AES-CTR.
|
||||
ssize_t WVCryptoPlugin::decrypt(bool secure, const uint8_t key[KEY_ID_SIZE],
|
||||
const uint8_t iv[KEY_IV_SIZE], Mode mode,
|
||||
const Pattern& pattern, const void* srcPtr,
|
||||
const SubSample* subSamples,
|
||||
size_t numSubSamples, void* dstPtr,
|
||||
AString* errorDetailMsg) {
|
||||
if (mode != kMode_Unencrypted &&
|
||||
mode != kMode_AES_CTR &&
|
||||
mode != kMode_AES_CBC) {
|
||||
errorDetailMsg->setTo(
|
||||
"The requested encryption mode is not supported by Widevine CDM.");
|
||||
return kErrorUnsupportedCrypto;
|
||||
} else if (mode == kMode_AES_CTR &&
|
||||
(pattern.mEncryptBlocks != 0 || pattern.mSkipBlocks != 0)) {
|
||||
errorDetailMsg->setTo(
|
||||
"The 'cens' schema is not supported by Widevine CDM.");
|
||||
return kErrorUnsupportedCrypto;
|
||||
}
|
||||
|
||||
// Convert parameters to the form the CDM wishes to consume them in.
|
||||
const KeyId keyId(reinterpret_cast<const char*>(key), KEY_ID_SIZE);
|
||||
vector<uint8_t> ivVector(iv, iv + KEY_IV_SIZE);
|
||||
const uint8_t* const source = static_cast<const uint8_t*>(srcPtr);
|
||||
uint8_t* const dest = static_cast<uint8_t*>(dstPtr);
|
||||
|
||||
// Set up the decrypt params
|
||||
CdmDecryptionParametersV16 params;
|
||||
params.key_id = keyId;
|
||||
params.is_secure = secure;
|
||||
if (mode == kMode_AES_CTR) {
|
||||
params.cipher_mode = kCipherModeCtr;
|
||||
} else if (mode == kMode_AES_CBC) {
|
||||
params.cipher_mode = kCipherModeCbc;
|
||||
}
|
||||
params.pattern.encrypt_blocks = pattern.mEncryptBlocks;
|
||||
params.pattern.skip_blocks = pattern.mSkipBlocks;
|
||||
|
||||
// Set up the sample
|
||||
// Android's API only supports one at a time
|
||||
params.samples.emplace_back();
|
||||
CdmDecryptionSample& sample = params.samples.back();
|
||||
sample.encrypt_buffer = source;
|
||||
sample.decrypt_buffer = dest;
|
||||
sample.decrypt_buffer_offset = 0;
|
||||
sample.iv = ivVector;
|
||||
|
||||
// Set up the subsamples
|
||||
// We abuse std::transform() here to also do some side-effects: Tallying the
|
||||
// total size of the sample and checking if any of the data is protected.
|
||||
size_t totalSize = 0;
|
||||
bool hasProtectedData = false;
|
||||
sample.subsamples.reserve(numSubSamples);
|
||||
std::transform(subSamples, subSamples + numSubSamples,
|
||||
std::back_inserter(sample.subsamples),
|
||||
[&](const SubSample& subSample) -> CdmDecryptionSubsample {
|
||||
totalSize +=
|
||||
subSample.mNumBytesOfClearData + subSample.mNumBytesOfEncryptedData;
|
||||
hasProtectedData |= subSample.mNumBytesOfEncryptedData > 0;
|
||||
return CdmDecryptionSubsample(subSample.mNumBytesOfClearData,
|
||||
subSample.mNumBytesOfEncryptedData);
|
||||
});
|
||||
|
||||
sample.encrypt_buffer_length = totalSize;
|
||||
sample.decrypt_buffer_size = totalSize;
|
||||
|
||||
if (mode == kMode_Unencrypted && hasProtectedData) {
|
||||
errorDetailMsg->setTo("Protected ranges found in allegedly clear data.");
|
||||
return kErrorExpectedUnencrypted;
|
||||
}
|
||||
|
||||
// Decrypt
|
||||
status_t res = attemptDecrypt(params, hasProtectedData, errorDetailMsg);
|
||||
if (res != android::OK) {
|
||||
return res;
|
||||
}
|
||||
|
||||
// In test mode, we return an error that causes a detailed error
|
||||
// message string containing a SHA256 hash of the decrypted data
|
||||
// to get passed to the java app via CryptoException. The test app
|
||||
// can then use the hash to verify that decryption was successful.
|
||||
|
||||
if (mTestMode) {
|
||||
if (secure) {
|
||||
// can't access data in secure mode
|
||||
errorDetailMsg->setTo("secure");
|
||||
} else {
|
||||
SHA256_CTX ctx;
|
||||
uint8_t digest[SHA256_DIGEST_LENGTH];
|
||||
SHA256_Init(&ctx);
|
||||
SHA256_Update(&ctx, dstPtr, totalSize);
|
||||
SHA256_Final(digest, &ctx);
|
||||
String8 buf;
|
||||
for (size_t i = 0; i < sizeof(digest); i++) {
|
||||
buf.appendFormat("%02x", digest[i]);
|
||||
}
|
||||
errorDetailMsg->setTo(buf.string());
|
||||
}
|
||||
|
||||
return kErrorTestMode;
|
||||
}
|
||||
|
||||
return static_cast<ssize_t>(totalSize);
|
||||
}
|
||||
|
||||
status_t WVCryptoPlugin::attemptDecrypt(
|
||||
const CdmDecryptionParametersV16& params, bool hasProtectedData,
|
||||
AString* errorDetailMsg) {
|
||||
CdmResponseType res = mCDM->DecryptV16(mSessionId, hasProtectedData,
|
||||
params);
|
||||
|
||||
if (isCdmResponseTypeSuccess(res)) {
|
||||
return android::OK;
|
||||
} else {
|
||||
ALOGE("Decrypt error in session %s during a sample %s protected data: %d",
|
||||
mSessionId.c_str(),
|
||||
hasProtectedData ? "with" : "without",
|
||||
res);
|
||||
bool actionableError = true;
|
||||
switch (res) {
|
||||
case wvcdm::INSUFFICIENT_CRYPTO_RESOURCES:
|
||||
errorDetailMsg->setTo(
|
||||
"Error decrypting data: insufficient crypto resources");
|
||||
break;
|
||||
case wvcdm::NEED_KEY:
|
||||
case wvcdm::KEY_NOT_FOUND_IN_SESSION:
|
||||
errorDetailMsg->setTo(
|
||||
"Error decrypting data: requested key has not been loaded");
|
||||
break;
|
||||
case wvcdm::DECRYPT_NOT_READY:
|
||||
errorDetailMsg->setTo(
|
||||
"Error decrypting data: license validity period is in the future");
|
||||
break;
|
||||
case wvcdm::SESSION_NOT_FOUND_FOR_DECRYPT:
|
||||
errorDetailMsg->setTo(
|
||||
"Error decrypting data: session not found, possibly reclaimed");
|
||||
break;
|
||||
case wvcdm::DECRYPT_ERROR:
|
||||
errorDetailMsg->setTo(
|
||||
"Error decrypting data: unspecified error");
|
||||
break;
|
||||
case wvcdm::INSUFFICIENT_OUTPUT_PROTECTION:
|
||||
case wvcdm::ANALOG_OUTPUT_ERROR:
|
||||
errorDetailMsg->setTo(
|
||||
"Error decrypting data: insufficient output protection");
|
||||
break;
|
||||
case wvcdm::KEY_PROHIBITED_FOR_SECURITY_LEVEL:
|
||||
errorDetailMsg->setTo(
|
||||
"Error decrypting data: key prohibited for security level");
|
||||
break;
|
||||
default:
|
||||
actionableError = false;
|
||||
break;
|
||||
}
|
||||
|
||||
if (actionableError) {
|
||||
// This error is actionable by the app and should be passed up.
|
||||
return mapCdmResponseType(res);
|
||||
} else {
|
||||
// Swallow the specifics of other errors to obscure decrypt internals.
|
||||
return kErrorCDMGeneric;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace wvdrm
|
||||
@@ -1,67 +1,4 @@
|
||||
LOCAL_PATH := $(call my-dir)
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Builds libwvdrmmediacrypto_test
|
||||
#
|
||||
include $(CLEAR_VARS)
|
||||
|
||||
LOCAL_SRC_FILES := \
|
||||
legacy_src/WVCryptoPlugin_test.cpp \
|
||||
|
||||
LOCAL_C_INCLUDES := \
|
||||
frameworks/av/include \
|
||||
frameworks/native/include \
|
||||
vendor/widevine/libwvdrmengine/cdm/core/include \
|
||||
vendor/widevine/libwvdrmengine/cdm/include \
|
||||
vendor/widevine/libwvdrmengine/cdm/metrics/include \
|
||||
vendor/widevine/libwvdrmengine/cdm/util/include \
|
||||
vendor/widevine/libwvdrmengine/mediacrypto/include \
|
||||
vendor/widevine/libwvdrmengine/oemcrypto/include \
|
||||
|
||||
LOCAL_STATIC_LIBRARIES := \
|
||||
libcdm \
|
||||
libcdm_protos \
|
||||
libcdm_utils \
|
||||
libjsmn \
|
||||
libgmock \
|
||||
libgmock_main \
|
||||
libgtest \
|
||||
libwvlevel3 \
|
||||
libwvdrmcryptoplugin \
|
||||
libwv_odk \
|
||||
|
||||
LOCAL_SHARED_LIBRARIES := \
|
||||
libbase \
|
||||
libcrypto \
|
||||
libcutils \
|
||||
libdl \
|
||||
libhidlbase \
|
||||
liblog \
|
||||
libprotobuf-cpp-lite \
|
||||
libstagefright_foundation \
|
||||
libutils \
|
||||
|
||||
LOCAL_C_INCLUDES += \
|
||||
external/protobuf/src \
|
||||
|
||||
LOCAL_MODULE := libwvdrmmediacrypto_test
|
||||
LOCAL_LICENSE_KINDS := legacy_by_exception_only
|
||||
LOCAL_LICENSE_CONDITIONS := by_exception_only
|
||||
|
||||
LOCAL_MODULE_TAGS := tests
|
||||
|
||||
LOCAL_MODULE_OWNER := widevine
|
||||
LOCAL_PROPRIETARY_MODULE := true
|
||||
|
||||
# When built, explicitly put it in the DATA/nativetest directory.
|
||||
LOCAL_MODULE_PATH := $(TARGET_OUT_DATA)/nativetest
|
||||
|
||||
ifneq ($(TARGET_ENABLE_MEDIADRM_64), true)
|
||||
LOCAL_MODULE_TARGET_ARCH := arm x86 mips
|
||||
endif
|
||||
|
||||
include $(BUILD_EXECUTABLE)
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Builds libwvdrmmediacrypto_hidl_test
|
||||
#
|
||||
@@ -79,7 +16,6 @@ LOCAL_C_INCLUDES := \
|
||||
vendor/widevine/libwvdrmengine/cdm/util/include \
|
||||
vendor/widevine/libwvdrmengine/include_hidl \
|
||||
vendor/widevine/libwvdrmengine/mediacrypto/include_hidl \
|
||||
vendor/widevine/libwvdrmengine/mediacrypto/include \
|
||||
vendor/widevine/libwvdrmengine/oemcrypto/include \
|
||||
|
||||
LOCAL_STATIC_LIBRARIES := \
|
||||
|
||||
@@ -1,430 +0,0 @@
|
||||
//
|
||||
// Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary
|
||||
// source code may only be used and distributed under the Widevine License
|
||||
// Agreement.
|
||||
//
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "gmock/gmock.h"
|
||||
#include "gtest/gtest.h"
|
||||
#include "media/stagefright/foundation/ABase.h"
|
||||
#include "media/stagefright/foundation/AString.h"
|
||||
#include "media/stagefright/MediaErrors.h"
|
||||
|
||||
#include "OEMCryptoCENC.h"
|
||||
#include "wv_cdm_constants.h"
|
||||
#include "wv_cdm_types.h"
|
||||
#include "wv_content_decryption_module.h"
|
||||
#include "WVCryptoPlugin.h"
|
||||
|
||||
using namespace android;
|
||||
using namespace std;
|
||||
using namespace testing;
|
||||
using namespace wvcdm;
|
||||
using namespace wvdrm;
|
||||
|
||||
namespace {
|
||||
constexpr ssize_t kErrorUnsupportedCrypto = ERROR_DRM_VENDOR_MIN + 2;
|
||||
}
|
||||
|
||||
class MockCDM : public WvContentDecryptionModule {
|
||||
public:
|
||||
MOCK_METHOD1(IsOpenSession, bool(const CdmSessionId&));
|
||||
|
||||
MOCK_METHOD3(DecryptV16, CdmResponseType(const CdmSessionId&, bool,
|
||||
const CdmDecryptionParametersV16&));
|
||||
|
||||
MOCK_METHOD2(QuerySessionStatus, CdmResponseType(const CdmSessionId&,
|
||||
CdmQueryMap*));
|
||||
};
|
||||
|
||||
class WVCryptoPluginTest : public Test {
|
||||
protected:
|
||||
static const uint32_t kSessionIdSize = 16;
|
||||
uint8_t sessionId[kSessionIdSize];
|
||||
|
||||
virtual void SetUp() {
|
||||
FILE* fp = fopen("/dev/urandom", "r");
|
||||
fread(sessionId, sizeof(uint8_t), kSessionIdSize, fp);
|
||||
fclose(fp);
|
||||
|
||||
// Set default CdmResponseType value for gMock
|
||||
DefaultValue<CdmResponseType>::Set(wvcdm::NO_ERROR);
|
||||
}
|
||||
};
|
||||
|
||||
TEST_F(WVCryptoPluginTest, CorrectlyReportsSecureBuffers) {
|
||||
android::sp<StrictMock<MockCDM>> cdm = new StrictMock<MockCDM>();
|
||||
|
||||
CdmQueryMap l1Map;
|
||||
l1Map[QUERY_KEY_SECURITY_LEVEL] = QUERY_VALUE_SECURITY_LEVEL_L1;
|
||||
CdmQueryMap l3Map;
|
||||
l3Map[QUERY_KEY_SECURITY_LEVEL] = QUERY_VALUE_SECURITY_LEVEL_L3;
|
||||
|
||||
// Provide the expected behavior for IsOpenSession
|
||||
EXPECT_CALL(*cdm, IsOpenSession(_))
|
||||
.WillRepeatedly(Return(true));
|
||||
|
||||
// Specify the expected calls to QuerySessionStatus
|
||||
EXPECT_CALL(*cdm, QuerySessionStatus(_, _))
|
||||
.WillOnce(DoAll(SetArgPointee<1>(l1Map),
|
||||
Return(wvcdm::NO_ERROR)))
|
||||
.WillOnce(DoAll(SetArgPointee<1>(l3Map),
|
||||
Return(wvcdm::NO_ERROR)));
|
||||
|
||||
WVCryptoPlugin plugin(sessionId, kSessionIdSize, cdm.get());
|
||||
|
||||
EXPECT_TRUE(plugin.requiresSecureDecoderComponent("video/mp4")) <<
|
||||
"WVCryptoPlugin incorrectly allows an insecure video decoder on L1";
|
||||
EXPECT_FALSE(plugin.requiresSecureDecoderComponent("video/mp4")) <<
|
||||
"WVCryptoPlugin incorrectly expects a secure video decoder on L3";
|
||||
EXPECT_FALSE(plugin.requiresSecureDecoderComponent("audio/aac")) <<
|
||||
"WVCryptoPlugin incorrectly expects a secure audio decoder";
|
||||
}
|
||||
|
||||
// TODO(b/28295739):
|
||||
// Add New MediaCrypto Unit Tests for CBC & Pattern Mode in cdmPatternDesc.
|
||||
|
||||
// Predicate that validates that the fields of a passed-in
|
||||
// CdmDecryptionParametersV16 match the values it was given at construction
|
||||
// time.
|
||||
//
|
||||
// This could be done with a huge pile of gMock matchers, but it is ugly and
|
||||
// unmaintainable, particularly once you get into validating the subsamples. The
|
||||
// logic here is complex enough to warrant a custom matcher for this one test.
|
||||
class CDPMatcher {
|
||||
public:
|
||||
CDPMatcher(const uint8_t* keyId, bool isSecure, CryptoPlugin::Mode cipherMode,
|
||||
const CryptoPlugin::Pattern& pattern,
|
||||
const uint8_t* input, size_t inputLength,
|
||||
const uint8_t* output, size_t outputLength, const uint8_t* iv,
|
||||
const CryptoPlugin::SubSample* subsamples,
|
||||
size_t subsamplesLength)
|
||||
: mKeyId(keyId, keyId + KEY_ID_SIZE), mIsSecure(isSecure),
|
||||
mCipherMode(cipherMode), mPattern(pattern), mInput(input),
|
||||
mInputLength(inputLength), mOutput(output), mOutputLength(outputLength),
|
||||
mIv(iv, iv + KEY_IV_SIZE),
|
||||
mSubsamples(subsamples, subsamples + subsamplesLength) {}
|
||||
|
||||
bool operator()(const CdmDecryptionParametersV16& params) const {
|
||||
if (mCipherMode == CryptoPlugin::kMode_AES_CTR &&
|
||||
params.cipher_mode != kCipherModeCtr) {
|
||||
return false;
|
||||
} else if (mCipherMode == CryptoPlugin::kMode_AES_CBC &&
|
||||
params.cipher_mode != kCipherModeCbc) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (params.key_id != mKeyId ||
|
||||
params.is_secure != mIsSecure ||
|
||||
params.pattern.encrypt_blocks != mPattern.mEncryptBlocks ||
|
||||
params.pattern.skip_blocks != mPattern.mSkipBlocks ||
|
||||
params.samples.size() != 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const CdmDecryptionSample& sample = params.samples[0];
|
||||
if (sample.encrypt_buffer != mInput ||
|
||||
sample.encrypt_buffer_length != mInputLength ||
|
||||
sample.decrypt_buffer != mOutput ||
|
||||
sample.decrypt_buffer_size != mOutputLength ||
|
||||
sample.decrypt_buffer_offset != 0 ||
|
||||
sample.iv != mIv ||
|
||||
sample.subsamples.size() != mSubsamples.size()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < mSubsamples.size(); ++i) {
|
||||
const CryptoPlugin::SubSample& androidSubsample = mSubsamples[i];
|
||||
const CdmDecryptionSubsample& cdmSubsample = sample.subsamples[i];
|
||||
|
||||
if (cdmSubsample.clear_bytes != androidSubsample.mNumBytesOfClearData||
|
||||
cdmSubsample.protected_bytes != androidSubsample.mNumBytesOfEncryptedData) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private:
|
||||
const KeyId mKeyId;
|
||||
const bool mIsSecure;
|
||||
const CryptoPlugin::Mode mCipherMode;
|
||||
const CryptoPlugin::Pattern mPattern;
|
||||
const uint8_t* const mInput;
|
||||
const size_t mInputLength;
|
||||
const uint8_t* const mOutput;
|
||||
const size_t mOutputLength;
|
||||
const std::vector<uint8_t> mIv;
|
||||
const std::vector<CryptoPlugin::SubSample> mSubsamples;
|
||||
};
|
||||
|
||||
TEST_F(WVCryptoPluginTest, AttemptsToDecrypt) {
|
||||
android::sp<StrictMock<MockCDM>> cdm = new StrictMock<MockCDM>();
|
||||
|
||||
constexpr size_t kSubSampleCount = 6;
|
||||
CryptoPlugin::SubSample subSamples[kSubSampleCount];
|
||||
memset(subSamples, 0, sizeof(subSamples));
|
||||
subSamples[0].mNumBytesOfEncryptedData = 16;
|
||||
subSamples[1].mNumBytesOfClearData = 16;
|
||||
subSamples[1].mNumBytesOfEncryptedData = 16;
|
||||
subSamples[2].mNumBytesOfEncryptedData = 8;
|
||||
subSamples[3].mNumBytesOfClearData = 29;
|
||||
subSamples[3].mNumBytesOfEncryptedData = 24;
|
||||
subSamples[4].mNumBytesOfEncryptedData = 60;
|
||||
subSamples[5].mNumBytesOfEncryptedData = 16;
|
||||
|
||||
uint8_t keyId[KEY_ID_SIZE];
|
||||
uint8_t iv[KEY_IV_SIZE];
|
||||
|
||||
constexpr size_t kDataSize = 185;
|
||||
uint8_t inputData[kDataSize];
|
||||
uint8_t outputData[kDataSize];
|
||||
|
||||
FILE* fp = fopen("/dev/urandom", "r");
|
||||
fread(keyId, sizeof(uint8_t), KEY_ID_SIZE, fp);
|
||||
fread(iv, sizeof(uint8_t), KEY_IV_SIZE, fp);
|
||||
fread(inputData, sizeof(uint8_t), kDataSize, fp);
|
||||
fclose(fp);
|
||||
|
||||
android::CryptoPlugin::Pattern noPattern = { 0, 0 };
|
||||
|
||||
// Provide the expected behavior for IsOpenSession
|
||||
EXPECT_CALL(*cdm, IsOpenSession(_))
|
||||
.WillRepeatedly(Return(true));
|
||||
|
||||
// Specify the expected calls to Decrypt
|
||||
CDPMatcher paramsMatcher(keyId, false, CryptoPlugin::kMode_AES_CTR, noPattern,
|
||||
inputData, kDataSize, outputData, kDataSize, iv,
|
||||
subSamples, kSubSampleCount);
|
||||
|
||||
EXPECT_CALL(*cdm, DecryptV16(ElementsAreArray(sessionId, kSessionIdSize),
|
||||
true,
|
||||
Truly(paramsMatcher)))
|
||||
.Times(1);
|
||||
|
||||
WVCryptoPlugin plugin(sessionId, kSessionIdSize, cdm.get());
|
||||
AString errorDetailMessage;
|
||||
|
||||
ssize_t res = plugin.decrypt(false, keyId, iv, CryptoPlugin::kMode_AES_CTR,
|
||||
noPattern, inputData, subSamples,
|
||||
kSubSampleCount, outputData,
|
||||
&errorDetailMessage);
|
||||
|
||||
EXPECT_EQ(static_cast<ssize_t>(kDataSize), res) <<
|
||||
"WVCryptoPlugin decrypted the wrong number of bytes";
|
||||
EXPECT_EQ(0u, errorDetailMessage.size()) <<
|
||||
"WVCryptoPlugin reported a detailed error message.";
|
||||
}
|
||||
|
||||
TEST_F(WVCryptoPluginTest, RejectsCens) {
|
||||
android::sp<StrictMock<MockCDM>> cdm = new StrictMock<MockCDM>();
|
||||
|
||||
constexpr size_t kSubSampleCount = 2;
|
||||
CryptoPlugin::SubSample subSamples[kSubSampleCount];
|
||||
memset(subSamples, 0, sizeof(subSamples));
|
||||
subSamples[0].mNumBytesOfEncryptedData = 16;
|
||||
subSamples[1].mNumBytesOfClearData = 16;
|
||||
subSamples[1].mNumBytesOfEncryptedData = 16;
|
||||
|
||||
uint8_t keyId[KEY_ID_SIZE];
|
||||
uint8_t iv[KEY_IV_SIZE];
|
||||
|
||||
constexpr size_t kDataSize = 48;
|
||||
uint8_t inputData[kDataSize];
|
||||
uint8_t outputData[kDataSize];
|
||||
|
||||
FILE* fp = fopen("/dev/urandom", "r");
|
||||
fread(keyId, sizeof(uint8_t), KEY_ID_SIZE, fp);
|
||||
fread(iv, sizeof(uint8_t), KEY_IV_SIZE, fp);
|
||||
fread(inputData, sizeof(uint8_t), kDataSize, fp);
|
||||
fclose(fp);
|
||||
|
||||
android::CryptoPlugin::Pattern recommendedPattern = { 1, 9 };
|
||||
|
||||
// Provide the expected behavior for IsOpenSession
|
||||
EXPECT_CALL(*cdm, IsOpenSession(_))
|
||||
.WillRepeatedly(Return(true));
|
||||
|
||||
// Refuse calls to Decrypt
|
||||
EXPECT_CALL(*cdm, DecryptV16(_, _, _))
|
||||
.Times(0);
|
||||
|
||||
WVCryptoPlugin plugin(sessionId, kSessionIdSize, cdm.get());
|
||||
AString errorDetailMessage;
|
||||
|
||||
ssize_t res = plugin.decrypt(false, keyId, iv, CryptoPlugin::kMode_AES_CTR,
|
||||
recommendedPattern, inputData, subSamples,
|
||||
kSubSampleCount, outputData,
|
||||
&errorDetailMessage);
|
||||
|
||||
EXPECT_EQ(res, kErrorUnsupportedCrypto) <<
|
||||
"WVCryptoPlugin did not return an error for 'cens'.";
|
||||
EXPECT_NE(errorDetailMessage.size(), 0u) <<
|
||||
"WVCryptoPlugin did not report a detailed error message for 'cens'.";
|
||||
}
|
||||
|
||||
TEST_F(WVCryptoPluginTest, CommunicatesSecureBufferRequest) {
|
||||
android::sp<StrictMock<MockCDM>> cdm = new StrictMock<MockCDM>();
|
||||
|
||||
uint8_t keyId[KEY_ID_SIZE];
|
||||
uint8_t iv[KEY_IV_SIZE];
|
||||
|
||||
static const size_t kDataSize = 32;
|
||||
uint8_t in[kDataSize];
|
||||
uint8_t out[kDataSize];
|
||||
|
||||
FILE* fp = fopen("/dev/urandom", "r");
|
||||
fread(keyId, sizeof(uint8_t), KEY_ID_SIZE, fp);
|
||||
fread(iv, sizeof(uint8_t), KEY_IV_SIZE, fp);
|
||||
fread(in, sizeof(uint8_t), kDataSize, fp);
|
||||
fclose(fp);
|
||||
|
||||
static const uint32_t kSubSampleCount = 1;
|
||||
CryptoPlugin::SubSample subSamples[kSubSampleCount];
|
||||
memset(subSamples, 0, sizeof(subSamples));
|
||||
subSamples[0].mNumBytesOfClearData = 16;
|
||||
subSamples[0].mNumBytesOfEncryptedData = 16;
|
||||
|
||||
// Provide the expected behavior for IsOpenSession
|
||||
EXPECT_CALL(*cdm, IsOpenSession(_))
|
||||
.WillRepeatedly(Return(true));
|
||||
|
||||
// Specify the expected calls to Decrypt
|
||||
{
|
||||
InSequence calls;
|
||||
|
||||
typedef CdmDecryptionParametersV16 CDP;
|
||||
|
||||
EXPECT_CALL(*cdm, DecryptV16(_, _, Field(&CDP::is_secure, false)))
|
||||
.Times(1);
|
||||
|
||||
EXPECT_CALL(*cdm, DecryptV16(_, _, Field(&CDP::is_secure, true)))
|
||||
.Times(1);
|
||||
}
|
||||
|
||||
WVCryptoPlugin plugin(sessionId, kSessionIdSize, cdm.get());
|
||||
android::CryptoPlugin::Pattern noPattern = { 0, 0 };
|
||||
AString errorDetailMessage;
|
||||
|
||||
ssize_t res = plugin.decrypt(false, keyId, iv, CryptoPlugin::kMode_AES_CTR,
|
||||
noPattern, in, subSamples, kSubSampleCount,
|
||||
out, &errorDetailMessage);
|
||||
ASSERT_GE(res, 0) <<
|
||||
"WVCryptoPlugin returned an error";
|
||||
EXPECT_EQ(0u, errorDetailMessage.size()) <<
|
||||
"WVCryptoPlugin reported a detailed error message.";
|
||||
|
||||
res = plugin.decrypt(true, keyId, iv, CryptoPlugin::kMode_AES_CTR,
|
||||
noPattern, in, subSamples, kSubSampleCount, out,
|
||||
&errorDetailMessage);
|
||||
ASSERT_GE(res, 0) <<
|
||||
"WVCryptoPlugin returned an error";
|
||||
EXPECT_EQ(0u, errorDetailMessage.size()) <<
|
||||
"WVCryptoPlugin reported a detailed error message.";
|
||||
}
|
||||
|
||||
TEST_F(WVCryptoPluginTest, AllowsSessionIdChanges) {
|
||||
android::sp<StrictMock<MockCDM>> cdm = new StrictMock<MockCDM>();
|
||||
|
||||
uint8_t keyId[KEY_ID_SIZE];
|
||||
uint8_t iv[KEY_IV_SIZE];
|
||||
uint8_t sessionId2[kSessionIdSize];
|
||||
|
||||
static const size_t kDataSize = 32;
|
||||
uint8_t in[kDataSize];
|
||||
uint8_t out[kDataSize];
|
||||
|
||||
FILE* fp = fopen("/dev/urandom", "r");
|
||||
fread(keyId, sizeof(uint8_t), KEY_ID_SIZE, fp);
|
||||
fread(iv, sizeof(uint8_t), KEY_IV_SIZE, fp);
|
||||
fread(sessionId2, sizeof(uint8_t), kSessionIdSize, fp);
|
||||
fread(in, sizeof(uint8_t), kDataSize, fp);
|
||||
fclose(fp);
|
||||
|
||||
static const uint32_t kSubSampleCount = 1;
|
||||
CryptoPlugin::SubSample subSamples[kSubSampleCount];
|
||||
memset(subSamples, 0, sizeof(subSamples));
|
||||
subSamples[0].mNumBytesOfClearData = 16;
|
||||
subSamples[0].mNumBytesOfEncryptedData = 16;
|
||||
|
||||
Vector<uint8_t> sessionIdVector;
|
||||
sessionIdVector.appendArray(sessionId, kSessionIdSize);
|
||||
Vector<uint8_t> sessionId2Vector;
|
||||
sessionId2Vector.appendArray(sessionId2, kSessionIdSize);
|
||||
|
||||
// Provide the expected behavior for IsOpenSession
|
||||
EXPECT_CALL(*cdm, IsOpenSession(_))
|
||||
.WillRepeatedly(Return(true));
|
||||
|
||||
// Specify the expected calls to Decrypt
|
||||
{
|
||||
InSequence calls;
|
||||
|
||||
EXPECT_CALL(*cdm,
|
||||
DecryptV16(ElementsAreArray(sessionId, kSessionIdSize), _, _))
|
||||
.Times(1);
|
||||
|
||||
EXPECT_CALL(*cdm,
|
||||
DecryptV16(ElementsAreArray(sessionId2, kSessionIdSize), _, _))
|
||||
.Times(1);
|
||||
}
|
||||
|
||||
uint8_t blank[1]; // Some compilers will not accept 0.
|
||||
WVCryptoPlugin plugin(blank, 0, cdm.get());
|
||||
android::CryptoPlugin::Pattern noPattern = { 0, 0 };
|
||||
AString errorDetailMessage;
|
||||
ssize_t res;
|
||||
|
||||
res = plugin.setMediaDrmSession(sessionIdVector);
|
||||
EXPECT_EQ(android::NO_ERROR, res);
|
||||
res = plugin.decrypt(false, keyId, iv, CryptoPlugin::kMode_AES_CTR,
|
||||
noPattern, in, subSamples, kSubSampleCount, out,
|
||||
&errorDetailMessage);
|
||||
EXPECT_GE(res, 0) <<
|
||||
"WVCryptoPlugin returned an error";
|
||||
EXPECT_EQ(0u, errorDetailMessage.size()) <<
|
||||
"WVCryptoPlugin reported a detailed error message.";
|
||||
|
||||
res = plugin.setMediaDrmSession(sessionId2Vector);
|
||||
EXPECT_EQ(android::NO_ERROR, res);
|
||||
res = plugin.decrypt(false, keyId, iv, CryptoPlugin::kMode_AES_CTR,
|
||||
noPattern, in, subSamples, kSubSampleCount, out,
|
||||
&errorDetailMessage);
|
||||
EXPECT_GE(res, 0) <<
|
||||
"WVCryptoPlugin returned an error";
|
||||
EXPECT_EQ(0u, errorDetailMessage.size()) <<
|
||||
"WVCryptoPlugin reported a detailed error message.";
|
||||
}
|
||||
|
||||
TEST_F(WVCryptoPluginTest, DisallowsUnopenedSessionIdChanges) {
|
||||
android::sp<StrictMock<MockCDM>> cdm = new StrictMock<MockCDM>();
|
||||
|
||||
uint8_t blank[1]; // Some compilers will not accept 0.
|
||||
Vector<uint8_t> sessionIdVector;
|
||||
sessionIdVector.appendArray(sessionId, kSessionIdSize);
|
||||
|
||||
// Specify the expected calls to IsOpenSession
|
||||
{
|
||||
InSequence calls;
|
||||
|
||||
EXPECT_CALL(*cdm, IsOpenSession(ElementsAreArray(blank, 0)))
|
||||
.WillOnce(Return(false));
|
||||
|
||||
EXPECT_CALL(*cdm, IsOpenSession(ElementsAreArray(sessionId, kSessionIdSize)))
|
||||
.WillOnce(Return(false))
|
||||
.WillOnce(Return(true));
|
||||
}
|
||||
|
||||
WVCryptoPlugin plugin(blank, 0, cdm.get());
|
||||
|
||||
ssize_t res;
|
||||
res = plugin.setMediaDrmSession(sessionIdVector);
|
||||
EXPECT_EQ(android::ERROR_DRM_SESSION_NOT_OPENED, res);
|
||||
res = plugin.setMediaDrmSession(sessionIdVector);
|
||||
EXPECT_EQ(android::NO_ERROR, res);
|
||||
}
|
||||
Reference in New Issue
Block a user