diff --git a/libwvdrmengine/cdm/core/include/cdm_engine.h b/libwvdrmengine/cdm/core/include/cdm_engine.h index 7a57a431..a50ed281 100644 --- a/libwvdrmengine/cdm/core/include/cdm_engine.h +++ b/libwvdrmengine/cdm/core/include/cdm_engine.h @@ -36,6 +36,7 @@ class CdmEngine { WvCdmEventListener* event_listener, CdmSessionId* session_id); virtual CdmResponseType CloseSession(const CdmSessionId& session_id); + virtual bool IsOpenSession(const CdmSessionId& session_id); virtual CdmResponseType OpenKeySetSession( const CdmKeySetId& key_set_id, const CdmClientPropertySet* property_set, diff --git a/libwvdrmengine/cdm/core/src/cdm_engine.cpp b/libwvdrmengine/cdm/core/src/cdm_engine.cpp index 29a9419d..f3166d67 100644 --- a/libwvdrmengine/cdm/core/src/cdm_engine.cpp +++ b/libwvdrmengine/cdm/core/src/cdm_engine.cpp @@ -164,6 +164,11 @@ CdmResponseType CdmEngine::CloseKeySetSession(const CdmKeySetId& key_set_id) { return sts; } +bool CdmEngine::IsOpenSession(const CdmSessionId& session_id) { + CdmSessionMap::iterator iter = sessions_.find(session_id); + return iter != sessions_.end(); +} + CdmResponseType CdmEngine::GenerateKeyRequest( const CdmSessionId& session_id, const CdmKeySetId& key_set_id, const InitializationData& init_data, const CdmLicenseType license_type, diff --git a/libwvdrmengine/cdm/core/test/cdm_engine_test.cpp b/libwvdrmengine/cdm/core/test/cdm_engine_test.cpp index 23b8c280..f18ecd45 100644 --- a/libwvdrmengine/cdm/core/test/cdm_engine_test.cpp +++ b/libwvdrmengine/cdm/core/test/cdm_engine_test.cpp @@ -57,6 +57,7 @@ class WvCdmEngineTest : public testing::Test { } ASSERT_EQ(NO_ERROR, status); ASSERT_NE("", session_id_) << "Could not open CDM session."; + ASSERT_TRUE(cdm_engine_.IsOpenSession(session_id_)); } virtual void TearDown() { cdm_engine_.CloseSession(session_id_); } diff --git a/libwvdrmengine/cdm/include/wv_content_decryption_module.h b/libwvdrmengine/cdm/include/wv_content_decryption_module.h index 19a1a427..b52e190a 100644 --- a/libwvdrmengine/cdm/include/wv_content_decryption_module.h +++ b/libwvdrmengine/cdm/include/wv_content_decryption_module.h @@ -32,6 +32,7 @@ class WvContentDecryptionModule : public TimerHandler { WvCdmEventListener* event_listener, CdmSessionId* session_id); virtual CdmResponseType CloseSession(const CdmSessionId& session_id); + virtual bool IsOpenSession(const CdmSessionId& session_id); // Construct a valid license request. virtual CdmResponseType GenerateKeyRequest(const CdmSessionId& session_id, diff --git a/libwvdrmengine/cdm/src/wv_content_decryption_module.cpp b/libwvdrmengine/cdm/src/wv_content_decryption_module.cpp index 4774d78d..fd8c99fa 100644 --- a/libwvdrmengine/cdm/src/wv_content_decryption_module.cpp +++ b/libwvdrmengine/cdm/src/wv_content_decryption_module.cpp @@ -58,6 +58,10 @@ CdmResponseType WvContentDecryptionModule::CloseSession( return sts; } +bool WvContentDecryptionModule::IsOpenSession(const CdmSessionId& session_id) { + return cdm_engine_->IsOpenSession(session_id); +} + CdmResponseType WvContentDecryptionModule::GenerateKeyRequest( const CdmSessionId& session_id, const CdmKeySetId& key_set_id, const std::string& init_data_type, const CdmInitData& init_data, diff --git a/libwvdrmengine/mediacrypto/include/WVCryptoPlugin.h b/libwvdrmengine/mediacrypto/include/WVCryptoPlugin.h index 21f4299e..25beecba 100644 --- a/libwvdrmengine/mediacrypto/include/WVCryptoPlugin.h +++ b/libwvdrmengine/mediacrypto/include/WVCryptoPlugin.h @@ -7,6 +7,8 @@ #include +#include "utils/Vector.h" + #include "media/hardware/CryptoAPI.h" #include "media/stagefright/foundation/ABase.h" #include "media/stagefright/foundation/AString.h" @@ -24,6 +26,9 @@ class WVCryptoPlugin : public android::CryptoPlugin { virtual void notifyResolution(uint32_t width, uint32_t height); + virtual android::status_t setMediaDrmSession( + const android::Vector& sessionId); + 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, @@ -35,7 +40,7 @@ class WVCryptoPlugin : public android::CryptoPlugin { wvcdm::WvContentDecryptionModule* const mCDM; bool mTestMode; - const wvcdm::CdmSessionId mSessionId; + wvcdm::CdmSessionId mSessionId; wvcdm::CdmSessionId configureTestMode(const void* data, size_t size); static void incrementIV(uint64_t increaseBy, std::vector* ivPtr); diff --git a/libwvdrmengine/mediacrypto/src/WVCryptoPlugin.cpp b/libwvdrmengine/mediacrypto/src/WVCryptoPlugin.cpp index a60f6408..55e0dffc 100644 --- a/libwvdrmengine/mediacrypto/src/WVCryptoPlugin.cpp +++ b/libwvdrmengine/mediacrypto/src/WVCryptoPlugin.cpp @@ -34,13 +34,18 @@ WVCryptoPlugin::WVCryptoPlugin(const void* data, size_t size, mTestMode(false), mSessionId(configureTestMode(data, size)) {} -wvcdm::CdmSessionId WVCryptoPlugin::configureTestMode(const void* data, - size_t size) { - wvcdm::CdmSessionId sessionId(static_cast(data), size); - size_t index = sessionId.find("test_mode"); - if (index != string::npos) { +CdmSessionId WVCryptoPlugin::configureTestMode(const void* data, size_t size) { + CdmSessionId sessionId; + if (data != NULL) { + sessionId.assign(static_cast(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; } @@ -68,10 +73,20 @@ void WVCryptoPlugin::notifyResolution(uint32_t width, uint32_t height) { mCDM->NotifyResolution(mSessionId, width, height); } -// 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. +status_t WVCryptoPlugin::setMediaDrmSession(const Vector& sessionId) { + CdmSessionId cdmSessionId(reinterpret_cast(sessionId.array()), + sessionId.size()); + 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 void* srcPtr, const SubSample* subSamples, diff --git a/libwvdrmengine/mediacrypto/test/WVCryptoPlugin_test.cpp b/libwvdrmengine/mediacrypto/test/WVCryptoPlugin_test.cpp index c71b9e42..de1c2a41 100644 --- a/libwvdrmengine/mediacrypto/test/WVCryptoPlugin_test.cpp +++ b/libwvdrmengine/mediacrypto/test/WVCryptoPlugin_test.cpp @@ -10,6 +10,8 @@ #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" @@ -24,6 +26,8 @@ using namespace wvdrm; class MockCDM : public WvContentDecryptionModule { public: + MOCK_METHOD1(IsOpenSession, bool(const CdmSessionId&)); + MOCK_METHOD3(Decrypt, CdmResponseType(const CdmSessionId&, bool, const CdmDecryptionParameters&)); @@ -48,20 +52,25 @@ class WVCryptoPluginTest : public Test { TEST_F(WVCryptoPluginTest, CorrectlyReportsSecureBuffers) { StrictMock cdm; - WVCryptoPlugin plugin(sessionId, kSessionIdSize, &cdm); 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); + EXPECT_TRUE(plugin.requiresSecureDecoderComponent("video/mp4")) << "WVCryptoPlugin incorrectly allows an insecure video decoder on L1"; EXPECT_FALSE(plugin.requiresSecureDecoderComponent("video/mp4")) << @@ -140,7 +149,6 @@ class CDPMatcherFactory { TEST_F(WVCryptoPluginTest, AttemptsToDecrypt) { StrictMock cdm; - WVCryptoPlugin plugin(sessionId, kSessionIdSize, &cdm); uint8_t keyId[KEY_ID_SIZE]; uint8_t baseIv[KEY_IV_SIZE]; @@ -181,6 +189,11 @@ TEST_F(WVCryptoPluginTest, AttemptsToDecrypt) { CDPMatcherFactory ParamsAre = CDPMatcherFactory(false, keyId, out, kDataSize); + // Provide the expected behavior for IsOpenSession + EXPECT_CALL(cdm, IsOpenSession(_)) + .WillRepeatedly(Return(true)); + + // Specify the expected calls to Decrypt { InSequence calls; @@ -233,6 +246,7 @@ TEST_F(WVCryptoPluginTest, AttemptsToDecrypt) { .Times(1); } + WVCryptoPlugin plugin(sessionId, kSessionIdSize, &cdm); AString errorDetailMessage; ssize_t res = plugin.decrypt(false, keyId, iv[0], CryptoPlugin::kMode_AES_CTR, @@ -247,7 +261,6 @@ TEST_F(WVCryptoPluginTest, AttemptsToDecrypt) { TEST_F(WVCryptoPluginTest, CommunicatesSecureBufferRequest) { StrictMock cdm; - WVCryptoPlugin plugin(sessionId, kSessionIdSize, &cdm); uint8_t keyId[KEY_ID_SIZE]; uint8_t iv[KEY_IV_SIZE]; @@ -268,6 +281,10 @@ TEST_F(WVCryptoPluginTest, CommunicatesSecureBufferRequest) { 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; @@ -281,6 +298,7 @@ TEST_F(WVCryptoPluginTest, CommunicatesSecureBufferRequest) { .Times(2); } + WVCryptoPlugin plugin(sessionId, kSessionIdSize, &cdm); AString errorDetailMessage; ssize_t res = plugin.decrypt(false, keyId, iv, CryptoPlugin::kMode_AES_CTR, @@ -301,7 +319,6 @@ TEST_F(WVCryptoPluginTest, CommunicatesSecureBufferRequest) { TEST_F(WVCryptoPluginTest, SetsFlagsForMinimumSubsampleRuns) { MockCDM cdm; - WVCryptoPlugin plugin(sessionId, kSessionIdSize, &cdm); uint8_t keyId[KEY_ID_SIZE]; uint8_t iv[KEY_IV_SIZE]; @@ -330,6 +347,10 @@ TEST_F(WVCryptoPluginTest, SetsFlagsForMinimumSubsampleRuns) { mixedSubSamples[0].mNumBytesOfClearData = 8; mixedSubSamples[0].mNumBytesOfEncryptedData = 8; + // Provide the expected behavior for IsOpenSession + EXPECT_CALL(cdm, IsOpenSession(_)) + .WillRepeatedly(Return(true)); + // Specify the expected calls to Decrypt { InSequence calls; @@ -350,6 +371,7 @@ TEST_F(WVCryptoPluginTest, SetsFlagsForMinimumSubsampleRuns) { .Times(1); } + WVCryptoPlugin plugin(sessionId, kSessionIdSize, &cdm); AString errorDetailMessage; ssize_t res = plugin.decrypt(false, keyId, iv, CryptoPlugin::kMode_AES_CTR, @@ -376,3 +398,101 @@ TEST_F(WVCryptoPluginTest, SetsFlagsForMinimumSubsampleRuns) { EXPECT_EQ(0u, errorDetailMessage.size()) << "WVCryptoPlugin reported a detailed error message."; } + +TEST_F(WVCryptoPluginTest, AllowsSessionIdChanges) { + StrictMock cdm; + + 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 sessionIdVector; + sessionIdVector.appendArray(sessionId, kSessionIdSize); + Vector 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, + Decrypt(ElementsAreArray(sessionId, kSessionIdSize), _, _)) + .Times(2); + + EXPECT_CALL(cdm, + Decrypt(ElementsAreArray(sessionId2, kSessionIdSize), _, _)) + .Times(2); + } + + uint8_t blank[1]; // Some compilers will not accept 0. + WVCryptoPlugin plugin(blank, 0, &cdm); + 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, 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, 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) { + StrictMock cdm; + + uint8_t blank[1]; // Some compilers will not accept 0. + Vector 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); + + 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); +}