Initial import of Widevine Common Encryption DRM engine
Builds libwvmdrmengine.so, which is loaded by the new MediaDrm APIs to support playback of Widevine/CENC protected content. Change-Id: I6f57dd37083dfd96c402cb9dd137c7d74edc8f1c
This commit is contained in:
20
libwvdrmengine/mediacrypto/Android.mk
Normal file
20
libwvdrmengine/mediacrypto/Android.mk
Normal file
@@ -0,0 +1,20 @@
|
||||
LOCAL_PATH := $(call my-dir)
|
||||
include $(CLEAR_VARS)
|
||||
|
||||
LOCAL_SRC_FILES := \
|
||||
src/WVCryptoPlugin.cpp \
|
||||
|
||||
LOCAL_C_INCLUDES := \
|
||||
bionic \
|
||||
external/stlport/stlport \
|
||||
frameworks/av/include \
|
||||
frameworks/native/include \
|
||||
vendor/widevine/libwvdrmengine/cdm/core/include \
|
||||
vendor/widevine/libwvdrmengine/cdm/include \
|
||||
vendor/widevine/libwvdrmengine/mediacrypto/include \
|
||||
|
||||
LOCAL_MODULE := libwvdrmcryptoplugin
|
||||
|
||||
LOCAL_MODULE_TAGS := optional
|
||||
|
||||
include $(BUILD_STATIC_LIBRARY)
|
||||
37
libwvdrmengine/mediacrypto/include/WVCryptoPlugin.h
Normal file
37
libwvdrmengine/mediacrypto/include/WVCryptoPlugin.h
Normal file
@@ -0,0 +1,37 @@
|
||||
//
|
||||
// Copyright 2013 Google Inc. All Rights Reserved.
|
||||
//
|
||||
|
||||
#ifndef WV_CRYPTO_PLUGIN_H_
|
||||
#define WV_CRYPTO_PLUGIN_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,
|
||||
wvcdm::WvContentDecryptionModule* cdm);
|
||||
virtual ~WVCryptoPlugin() {}
|
||||
|
||||
virtual bool requiresSecureDecoderComponent(const char *mime) const;
|
||||
|
||||
virtual ssize_t decrypt(bool secure, const uint8_t key[16],
|
||||
const uint8_t iv[16], Mode mode, const void* srcPtr,
|
||||
const SubSample* subSamples, size_t numSubSamples,
|
||||
void* dstPtr, android::AString* errorDetailMsg);
|
||||
|
||||
private:
|
||||
DISALLOW_EVIL_CONSTRUCTORS(WVCryptoPlugin);
|
||||
|
||||
wvcdm::WvContentDecryptionModule* const mCDM;
|
||||
const wvcdm::CdmSessionId mSessionId;
|
||||
};
|
||||
|
||||
} // namespace wvdrm
|
||||
|
||||
#endif // WV_CRYPTO_PLUGIN_H_
|
||||
106
libwvdrmengine/mediacrypto/src/WVCryptoPlugin.cpp
Normal file
106
libwvdrmengine/mediacrypto/src/WVCryptoPlugin.cpp
Normal file
@@ -0,0 +1,106 @@
|
||||
//
|
||||
// Copyright 2013 Google Inc. All Rights Reserved.
|
||||
//
|
||||
|
||||
//#define LOG_NDEBUG 0
|
||||
#define LOG_TAG "WVCdm"
|
||||
#include <utils/Log.h>
|
||||
|
||||
#include "WVCryptoPlugin.h"
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "utils/Errors.h"
|
||||
#include "wv_cdm_constants.h"
|
||||
|
||||
namespace wvdrm {
|
||||
|
||||
using namespace android;
|
||||
using namespace std;
|
||||
using namespace wvcdm;
|
||||
|
||||
WVCryptoPlugin::WVCryptoPlugin(const void* data, size_t size,
|
||||
WvContentDecryptionModule* cdm)
|
||||
: mCDM(cdm),
|
||||
mSessionId(static_cast<const char*>(data), size) {}
|
||||
|
||||
bool WVCryptoPlugin::requiresSecureDecoderComponent(const char *mime) const {
|
||||
// TODO: Determine if we are using L1 or L3 and return an appropriate value.
|
||||
// For Demo 2, we are always L3.
|
||||
return false;
|
||||
}
|
||||
|
||||
// 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 void* srcPtr, const SubSample* subSamples,
|
||||
size_t numSubSamples, void* dstPtr,
|
||||
AString *errorDetailMsg) {
|
||||
if (mode != kMode_Unencrypted && mode != kMode_AES_CTR) {
|
||||
return BAD_TYPE;
|
||||
}
|
||||
|
||||
if (secure) {
|
||||
// TODO: Can't do secure in the Demo 2 milestone
|
||||
return -EPERM;
|
||||
}
|
||||
|
||||
// Convert parameters to the form the CDM wishes to consume them in.
|
||||
const KeyId keyId(reinterpret_cast<const char*>(key), KEY_ID_SIZE);
|
||||
const 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);
|
||||
|
||||
// Iterate through subsamples, sending them to the CDM serially.
|
||||
size_t offset = 0;
|
||||
|
||||
for (size_t i = 0; i < numSubSamples; ++i) {
|
||||
const SubSample &subSample = subSamples[i];
|
||||
|
||||
if (mode == kMode_Unencrypted && subSample.mNumBytesOfEncryptedData != 0) {
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
// "Decrypt" any unencrypted data. Per the ISO-CENC standard, clear data
|
||||
// comes before encrypted data.
|
||||
if (subSample.mNumBytesOfClearData != 0) {
|
||||
CdmResponseType res = mCDM->Decrypt(mSessionId, false, keyId,
|
||||
source + offset,
|
||||
subSample.mNumBytesOfClearData,
|
||||
ivVector, offset % 16, dest + offset);
|
||||
|
||||
if (res != wvcdm::NO_ERROR) {
|
||||
ALOGE("Decrypt error result in session %s during unencrypted block: %d",
|
||||
mSessionId.c_str(), res);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
offset += subSample.mNumBytesOfClearData;
|
||||
}
|
||||
|
||||
// Decrypt any encrypted data. Per the ISO-CENC standard, encrypted data
|
||||
// comes after clear data.
|
||||
if (subSample.mNumBytesOfEncryptedData != 0) {
|
||||
CdmResponseType res = mCDM->Decrypt(mSessionId, true, keyId,
|
||||
source + offset,
|
||||
subSample.mNumBytesOfEncryptedData,
|
||||
ivVector, offset % 16, dest + offset);
|
||||
|
||||
if (res != wvcdm::NO_ERROR) {
|
||||
ALOGE("Decrypt error result in session %s during encrypted block: %d",
|
||||
mSessionId.c_str(), res);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
offset += subSample.mNumBytesOfEncryptedData;
|
||||
}
|
||||
}
|
||||
|
||||
return static_cast<ssize_t>(offset);
|
||||
}
|
||||
|
||||
} // namespace wvdrm
|
||||
54
libwvdrmengine/mediacrypto/test/Android.mk
Normal file
54
libwvdrmengine/mediacrypto/test/Android.mk
Normal file
@@ -0,0 +1,54 @@
|
||||
LOCAL_PATH := $(call my-dir)
|
||||
include $(CLEAR_VARS)
|
||||
|
||||
LOCAL_SRC_FILES := \
|
||||
WVCryptoPlugin_test.cpp \
|
||||
|
||||
LOCAL_C_INCLUDES := \
|
||||
bionic \
|
||||
external/gtest/include \
|
||||
external/stlport/stlport \
|
||||
frameworks/av/include \
|
||||
frameworks/native/include \
|
||||
vendor/widevine/libwvdrmengine/cdm/core/include \
|
||||
vendor/widevine/libwvdrmengine/cdm/include \
|
||||
vendor/widevine/libwvdrmengine/mediacrypto/include \
|
||||
vendor/widevine/libwvdrmengine/mediacrypto/test \
|
||||
vendor/widevine/libwvdrmengine/test/gmock/include \
|
||||
|
||||
LOCAL_STATIC_LIBRARIES := \
|
||||
libcdm \
|
||||
libgmock \
|
||||
libgmock_main \
|
||||
libgtest \
|
||||
libprotobuf-cpp-2.3.0-lite \
|
||||
libwvdrmcryptoplugin \
|
||||
|
||||
LOCAL_SHARED_LIBRARIES := \
|
||||
liblog \
|
||||
liboemcrypto \
|
||||
libstlport \
|
||||
libutils \
|
||||
|
||||
# CDM's protobuffers are not part of the library
|
||||
PROTO_SRC_DIR := $(proto_generated_cc_sources_dir)/$(LOCAL_PATH)/core/src
|
||||
|
||||
LOCAL_SRC_FILES += \
|
||||
$(PROTO_SRC_DIR)/license_protocol.pb.cc \
|
||||
|
||||
LOCAL_C_INCLUDES += \
|
||||
$(proto_generated_cc_sources_dir)/$(LOCAL_PATH)/core/src \
|
||||
external/protobuf/src \
|
||||
|
||||
LOCAL_ADDITIONAL_DEPENDENCIES += $(proto_generated_headers)
|
||||
|
||||
LOCAL_WHOLE_STATIC_LIBRARIES := \
|
||||
license_protocol_protos \
|
||||
|
||||
# End protobuf section
|
||||
|
||||
LOCAL_MODULE := libwvdrmmediacrypto_test
|
||||
|
||||
LOCAL_MODULE_TAGS := tests
|
||||
|
||||
include $(BUILD_EXECUTABLE)
|
||||
28
libwvdrmengine/mediacrypto/test/MockCDM.h
Normal file
28
libwvdrmengine/mediacrypto/test/MockCDM.h
Normal file
@@ -0,0 +1,28 @@
|
||||
//
|
||||
// Copyright 2013 Google Inc. All Rights Reserved.
|
||||
//
|
||||
|
||||
#ifndef WV_CRYPTO_PLUGIN_MOCK_CDM_H_
|
||||
#define WV_CRYPTO_PLUGIN_MOCK_CDM_H_
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "gmock/gmock.h"
|
||||
#include "wv_cdm_types.h"
|
||||
#include "wv_content_decryption_module.h"
|
||||
|
||||
|
||||
namespace wvcdm {
|
||||
|
||||
class MockCDM : public WvContentDecryptionModule {
|
||||
public:
|
||||
MOCK_METHOD8(Decrypt, CdmResponseType(const CdmSessionId&, bool, const KeyId&,
|
||||
const uint8_t*, size_t,
|
||||
const std::vector<uint8_t>&, size_t,
|
||||
void*));
|
||||
};
|
||||
|
||||
} // namespace wvcdm
|
||||
|
||||
#endif // WV_CRYPTO_PLUGIN_MOCK_CDM_H_
|
||||
118
libwvdrmengine/mediacrypto/test/WVCryptoPlugin_test.cpp
Normal file
118
libwvdrmengine/mediacrypto/test/WVCryptoPlugin_test.cpp
Normal file
@@ -0,0 +1,118 @@
|
||||
//
|
||||
// Copyright 2013 Google Inc. All Rights Reserved.
|
||||
//
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string>
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
#include "media/stagefright/foundation/ABase.h"
|
||||
#include "media/stagefright/foundation/AString.h"
|
||||
#include "MockCDM.h"
|
||||
#include "wv_cdm_constants.h"
|
||||
#include "WVCryptoPlugin.h"
|
||||
|
||||
using namespace android;
|
||||
using namespace std;
|
||||
using namespace testing;
|
||||
using namespace wvcdm;
|
||||
using namespace wvdrm;
|
||||
|
||||
class WVCryptoPluginTest : public Test {
|
||||
protected:
|
||||
static const uint32_t kSessionIdSize = 16;
|
||||
uint8_t sessionId[kSessionIdSize];
|
||||
|
||||
uint8_t keyId[KEY_ID_SIZE];
|
||||
uint8_t iv[KEY_IV_SIZE];
|
||||
|
||||
static const uint32_t kDataSize = 64;
|
||||
uint8_t in[kDataSize];
|
||||
uint8_t out[kDataSize];
|
||||
|
||||
static const uint32_t kSubSampleCount = 3;
|
||||
CryptoPlugin::SubSample subSamples[kSubSampleCount];
|
||||
|
||||
virtual void SetUp() {
|
||||
FILE* fp = fopen("/dev/urandom", "r");
|
||||
fread(sessionId, sizeof(uint8_t), kSessionIdSize, fp);
|
||||
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);
|
||||
|
||||
memset(out, 0, sizeof(out));
|
||||
|
||||
memset(subSamples, 0, sizeof(subSamples));
|
||||
subSamples[0].mNumBytesOfEncryptedData = 16;
|
||||
subSamples[1].mNumBytesOfClearData = 16;
|
||||
subSamples[1].mNumBytesOfEncryptedData = 24;
|
||||
subSamples[2].mNumBytesOfEncryptedData = 8;
|
||||
|
||||
// Set default CdmResponseType value for gMock
|
||||
DefaultValue<CdmResponseType>::Set(wvcdm::NO_ERROR);
|
||||
}
|
||||
};
|
||||
|
||||
TEST_F(WVCryptoPluginTest, CorrectlyReportsSecureBuffers) {
|
||||
MockCDM cdm;
|
||||
WVCryptoPlugin plugin(sessionId, kSessionIdSize, &cdm);
|
||||
|
||||
EXPECT_FALSE(plugin.requiresSecureDecoderComponent("video/mp4")) <<
|
||||
"WVCryptoPlugin incorrectly expects a secure video decoder";
|
||||
EXPECT_FALSE(plugin.requiresSecureDecoderComponent("audio/aac")) <<
|
||||
"WVCryptoPlugin incorrectly expects a secure audio decoder";
|
||||
}
|
||||
|
||||
TEST_F(WVCryptoPluginTest, RejectsSecureDecode) {
|
||||
MockCDM cdm;
|
||||
WVCryptoPlugin plugin(sessionId, kSessionIdSize, &cdm);
|
||||
|
||||
// Decrypt should not be called because we specified an unsupported
|
||||
// security level
|
||||
EXPECT_CALL(cdm, Decrypt(_, _, _, _, _, _, _, _))
|
||||
.Times(0);
|
||||
|
||||
ssize_t res = plugin.decrypt(true, keyId, iv, CryptoPlugin::kMode_AES_CTR,
|
||||
in, subSamples, kSubSampleCount, out, NULL);
|
||||
|
||||
EXPECT_EQ(static_cast<ssize_t>(-EPERM), res) <<
|
||||
"WVCryptoPlugin allowed decryption to proceed despite being asked for an "
|
||||
"unsupported security level";
|
||||
}
|
||||
|
||||
TEST_F(WVCryptoPluginTest, AttemptsToDecrypt) {
|
||||
MockCDM cdm;
|
||||
WVCryptoPlugin plugin(sessionId, kSessionIdSize, &cdm);
|
||||
|
||||
// Specify the expected calls to Decrypt
|
||||
{
|
||||
InSequence calls;
|
||||
|
||||
EXPECT_CALL(cdm, Decrypt(ElementsAreArray(sessionId, kSessionIdSize), true,
|
||||
ElementsAreArray(keyId, KEY_ID_SIZE), in, 16,
|
||||
ElementsAreArray(iv, KEY_IV_SIZE), 0, out))
|
||||
.WillOnce(Return(wvcdm::NO_ERROR));
|
||||
|
||||
EXPECT_CALL(cdm, Decrypt(ElementsAreArray(sessionId, kSessionIdSize), false,
|
||||
ElementsAreArray(keyId, KEY_ID_SIZE), in + 16, 16,
|
||||
ElementsAreArray(iv, KEY_IV_SIZE), 0, out + 16))
|
||||
.WillOnce(Return(wvcdm::NO_ERROR));
|
||||
|
||||
EXPECT_CALL(cdm, Decrypt(ElementsAreArray(sessionId, kSessionIdSize), true,
|
||||
ElementsAreArray(keyId, KEY_ID_SIZE), in + 32, 24,
|
||||
ElementsAreArray(iv, KEY_IV_SIZE), 0, out + 32))
|
||||
.WillOnce(Return(wvcdm::NO_ERROR));
|
||||
|
||||
EXPECT_CALL(cdm, Decrypt(ElementsAreArray(sessionId, kSessionIdSize), true,
|
||||
ElementsAreArray(keyId, KEY_ID_SIZE), in + 56, 8,
|
||||
ElementsAreArray(iv, KEY_IV_SIZE), 8, out + 56))
|
||||
.WillOnce(Return(wvcdm::NO_ERROR));
|
||||
}
|
||||
|
||||
ssize_t res = plugin.decrypt(false, keyId, iv, CryptoPlugin::kMode_AES_CTR,
|
||||
in, subSamples, kSubSampleCount, out, NULL);
|
||||
|
||||
EXPECT_EQ(static_cast<ssize_t>(kDataSize), res) <<
|
||||
"WVCryptoPlugin decrypted the wrong number of bytes";
|
||||
}
|
||||
Reference in New Issue
Block a user