Merge "Test to ensure rollback doesn't affect key duration"
This commit is contained in:
committed by
Android (Google) Code Review
commit
007153889e
@@ -3,7 +3,11 @@
|
|||||||
// License Agreement.
|
// License Agreement.
|
||||||
|
|
||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
|
#include <time.h>
|
||||||
|
|
||||||
|
#include <chrono>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
|
#include <thread>
|
||||||
|
|
||||||
#include <android-base/properties.h>
|
#include <android-base/properties.h>
|
||||||
#include <gmock/gmock.h>
|
#include <gmock/gmock.h>
|
||||||
@@ -4873,7 +4877,7 @@ TEST_F(WvCdmRequestLicenseTest, SessionKeyChangeNotificationTest) {
|
|||||||
session_id_,
|
session_id_,
|
||||||
AllOf(Each(Pair(_, kKeyStatusUsable)), Not(IsEmpty())), true))
|
AllOf(Each(Pair(_, kKeyStatusUsable)), Not(IsEmpty())), true))
|
||||||
.WillOnce(Invoke(&decrypt_callback, &DecryptCallbackTester::Decrypt));
|
.WillOnce(Invoke(&decrypt_callback, &DecryptCallbackTester::Decrypt));
|
||||||
;
|
|
||||||
EXPECT_CALL(listener, OnExpirationUpdate(session_id_, _));
|
EXPECT_CALL(listener, OnExpirationUpdate(session_id_, _));
|
||||||
|
|
||||||
const std::string kCpKeyId = a2bs_hex(
|
const std::string kCpKeyId = a2bs_hex(
|
||||||
@@ -5363,4 +5367,273 @@ TEST_F(WvCdmRequestLicenseTest, DISABLED_DecryptPathTest) {
|
|||||||
decryptor_.CloseSession(session_id_);
|
decryptor_.CloseSession(session_id_);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class WvCdmRequestLicenseRollbackTest
|
||||||
|
: public WvCdmRequestLicenseTest,
|
||||||
|
public ::testing::WithParamInterface<SubSampleInfo*> {
|
||||||
|
public:
|
||||||
|
WvCdmRequestLicenseRollbackTest() {
|
||||||
|
SubSampleInfo* data = &single_encrypted_sub_sample_short_expiry;
|
||||||
|
decrypt_buffer_.resize(data->encrypt_data.size());
|
||||||
|
decryption_parameters_ = CdmDecryptionParameters(
|
||||||
|
&data->key_id, &data->encrypt_data.front(), data->encrypt_data.size(),
|
||||||
|
&data->iv, data->block_offset, &decrypt_buffer_[0]);
|
||||||
|
decryption_parameters_.is_encrypted = data->is_encrypted;
|
||||||
|
decryption_parameters_.is_secure = data->is_secure;
|
||||||
|
decryption_parameters_.subsample_flags = data->subsample_flags;
|
||||||
|
validate_key_id_ = data->validate_key_id;
|
||||||
|
}
|
||||||
|
~WvCdmRequestLicenseRollbackTest() {}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void RollbackSystemTime(time_t rollback_time_ms) {
|
||||||
|
if (!in_rollback_state_) {
|
||||||
|
LOGW("Rolling back system time %d ms.", rollback_time_ms);
|
||||||
|
wall_time_before_rollback_ = std::chrono::system_clock::now();
|
||||||
|
monotonic_time_before_rollback_ = std::chrono::steady_clock::now();
|
||||||
|
auto modified_wall_time = wall_time_before_rollback_ -
|
||||||
|
std::chrono::milliseconds(rollback_time_ms);
|
||||||
|
timespec modified_wall_time_spec;
|
||||||
|
modified_wall_time_spec.tv_sec =
|
||||||
|
std::chrono::duration_cast<std::chrono::seconds>(
|
||||||
|
modified_wall_time.time_since_epoch())
|
||||||
|
.count();
|
||||||
|
modified_wall_time_spec.tv_nsec =
|
||||||
|
std::chrono::duration_cast<std::chrono::nanoseconds>(
|
||||||
|
modified_wall_time.time_since_epoch())
|
||||||
|
.count() %
|
||||||
|
(1000 * 1000 * 1000);
|
||||||
|
ASSERT_EQ(0, clock_settime(CLOCK_REALTIME, &modified_wall_time_spec));
|
||||||
|
in_rollback_state_ = true;
|
||||||
|
} else {
|
||||||
|
LOGE("Can't rollback system time more than once without restoring.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void RestoreSystemTime() {
|
||||||
|
if (in_rollback_state_) {
|
||||||
|
LOGW("Restoring the system time.");
|
||||||
|
auto monotonic_time_after_rollback = std::chrono::steady_clock::now();
|
||||||
|
auto monotonic_time_diff =
|
||||||
|
monotonic_time_after_rollback - monotonic_time_before_rollback_;
|
||||||
|
auto real_time = wall_time_before_rollback_ + monotonic_time_diff;
|
||||||
|
timespec real_time_spec;
|
||||||
|
real_time_spec.tv_sec = std::chrono::duration_cast<std::chrono::seconds>(
|
||||||
|
real_time.time_since_epoch())
|
||||||
|
.count();
|
||||||
|
real_time_spec.tv_nsec =
|
||||||
|
std::chrono::duration_cast<std::chrono::nanoseconds>(
|
||||||
|
real_time.time_since_epoch())
|
||||||
|
.count() %
|
||||||
|
(1000 * 1000 * 1000);
|
||||||
|
ASSERT_EQ(0, clock_settime(CLOCK_REALTIME, &real_time_spec));
|
||||||
|
in_rollback_state_ = false;
|
||||||
|
} else {
|
||||||
|
LOGW("System time has already been restored.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CdmResponseType Decrypt(CdmSessionId session_id) {
|
||||||
|
std::fill(decrypt_buffer_.begin(), decrypt_buffer_.end(), 0);
|
||||||
|
return decryptor_.Decrypt(session_id, validate_key_id_,
|
||||||
|
decryption_parameters_);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::chrono::time_point<std::chrono::steady_clock>
|
||||||
|
monotonic_time_before_rollback_;
|
||||||
|
std::chrono::time_point<std::chrono::system_clock> wall_time_before_rollback_;
|
||||||
|
bool in_rollback_state_ = false;
|
||||||
|
const std::string init_data_with_expiry_ = a2bs_hex(
|
||||||
|
"000000347073736800000000" // blob size and pssh
|
||||||
|
"EDEF8BA979D64ACEA3C827DCD51D21ED00000014" // Widevine system id
|
||||||
|
"0801121030313233343536373839616263646566"); // pssh data
|
||||||
|
// Expiration of the key corresponding to init_data_with_expiry_ with a window
|
||||||
|
// to ensure expiration.
|
||||||
|
const time_t kExpirationTimeMs_ =
|
||||||
|
kSingleEncryptedSubSampleIcpLicenseDurationExpiration * 1000;
|
||||||
|
const time_t kExpirationWindowMs_ =
|
||||||
|
kSingleEncryptedSubSampleIcpLicenseExpirationWindow * 1000;
|
||||||
|
const time_t kExpirationWithWindowMs_ =
|
||||||
|
(kSingleEncryptedSubSampleIcpLicenseDurationExpiration +
|
||||||
|
kSingleEncryptedSubSampleIcpLicenseExpirationWindow) *
|
||||||
|
1000;
|
||||||
|
CdmDecryptionParameters decryption_parameters_;
|
||||||
|
std::vector<uint8_t> decrypt_buffer_;
|
||||||
|
bool validate_key_id_;
|
||||||
|
};
|
||||||
|
|
||||||
|
TEST_F(WvCdmRequestLicenseRollbackTest, Streaming_ExpireAfterRollback) {
|
||||||
|
Unprovision();
|
||||||
|
Provision(kLevelDefault);
|
||||||
|
|
||||||
|
ASSERT_EQ(NO_ERROR, decryptor_.OpenSession(config_.key_system(), nullptr,
|
||||||
|
kDefaultCdmIdentifier, nullptr,
|
||||||
|
&session_id_));
|
||||||
|
GenerateKeyRequest(init_data_with_expiry_, kLicenseTypeStreaming);
|
||||||
|
VerifyKeyRequestResponse(config_.license_server(), config_.client_auth());
|
||||||
|
|
||||||
|
// Verify that we can decrypt a subsample to begin with.
|
||||||
|
EXPECT_EQ(NO_ERROR, Decrypt(session_id_));
|
||||||
|
|
||||||
|
RollbackSystemTime(kExpirationWithWindowMs_);
|
||||||
|
|
||||||
|
// Elapse time so that the key should now be considered expired.
|
||||||
|
std::this_thread::sleep_for(
|
||||||
|
std::chrono::milliseconds(kExpirationWithWindowMs_));
|
||||||
|
|
||||||
|
// Verify that we can no longer decrypt a subsample due to key expiration.
|
||||||
|
EXPECT_EQ(NEED_KEY, Decrypt(session_id_));
|
||||||
|
|
||||||
|
RestoreSystemTime();
|
||||||
|
|
||||||
|
ASSERT_EQ(NO_ERROR, decryptor_.CloseSession(session_id_));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(WvCdmRequestLicenseRollbackTest, Streaming_ExpireBeforeRollback) {
|
||||||
|
Unprovision();
|
||||||
|
Provision(kLevelDefault);
|
||||||
|
|
||||||
|
ASSERT_EQ(NO_ERROR, decryptor_.OpenSession(config_.key_system(), nullptr,
|
||||||
|
kDefaultCdmIdentifier, nullptr,
|
||||||
|
&session_id_));
|
||||||
|
GenerateKeyRequest(init_data_with_expiry_, kLicenseTypeStreaming);
|
||||||
|
VerifyKeyRequestResponse(config_.license_server(), config_.client_auth());
|
||||||
|
|
||||||
|
// Elapse time so that the key should now be considered expired.
|
||||||
|
std::this_thread::sleep_for(
|
||||||
|
std::chrono::milliseconds(kExpirationWithWindowMs_));
|
||||||
|
|
||||||
|
EXPECT_EQ(NEED_KEY, Decrypt(session_id_));
|
||||||
|
|
||||||
|
RollbackSystemTime(kExpirationWithWindowMs_);
|
||||||
|
|
||||||
|
// Verify that we still can't decrypt even if we rollbacked the clock.
|
||||||
|
EXPECT_EQ(NEED_KEY, Decrypt(session_id_));
|
||||||
|
|
||||||
|
RestoreSystemTime();
|
||||||
|
|
||||||
|
ASSERT_EQ(NO_ERROR, decryptor_.CloseSession(session_id_));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(WvCdmRequestLicenseRollbackTest, Offline_RollbackBeforeRestoreKey) {
|
||||||
|
Unprovision();
|
||||||
|
Provision(kLevelDefault);
|
||||||
|
|
||||||
|
std::string unused_key_id;
|
||||||
|
std::string client_auth;
|
||||||
|
GetOfflineConfiguration(&unused_key_id, &client_auth);
|
||||||
|
|
||||||
|
ASSERT_EQ(NO_ERROR, decryptor_.OpenSession(config_.key_system(), nullptr,
|
||||||
|
kDefaultCdmIdentifier, nullptr,
|
||||||
|
&session_id_));
|
||||||
|
GenerateKeyRequest(init_data_with_expiry_, kLicenseTypeOffline);
|
||||||
|
VerifyKeyRequestResponse(config_.license_server(), client_auth);
|
||||||
|
|
||||||
|
// Verify that we can decrypt a subsample to begin with.
|
||||||
|
EXPECT_EQ(NO_ERROR, Decrypt(session_id_));
|
||||||
|
|
||||||
|
CdmKeySetId key_set_id = key_set_id_;
|
||||||
|
EXPECT_FALSE(key_set_id_.empty());
|
||||||
|
decryptor_.CloseSession(session_id_);
|
||||||
|
|
||||||
|
// This number must be > the time between GenerateKeyRequest and this call.
|
||||||
|
RollbackSystemTime(10 * 1000);
|
||||||
|
|
||||||
|
session_id_.clear();
|
||||||
|
decryptor_.OpenSession(config_.key_system(), nullptr, kDefaultCdmIdentifier,
|
||||||
|
nullptr, &session_id_);
|
||||||
|
|
||||||
|
decryptor_.RestoreKey(session_id_, key_set_id);
|
||||||
|
|
||||||
|
// Verify we can't decrypt.
|
||||||
|
EXPECT_EQ(DECRYPT_NOT_READY, Decrypt(session_id_));
|
||||||
|
|
||||||
|
RestoreSystemTime();
|
||||||
|
|
||||||
|
// Sleep for a little bit to account for the execution time of OpenSession and
|
||||||
|
// RestoreKey.
|
||||||
|
std::this_thread::sleep_for(
|
||||||
|
std::chrono::milliseconds(kExpirationTimeMs_ / 2));
|
||||||
|
|
||||||
|
// Verify we can decrypt.
|
||||||
|
EXPECT_EQ(NO_ERROR, Decrypt(session_id_));
|
||||||
|
|
||||||
|
ASSERT_EQ(NO_ERROR, decryptor_.CloseSession(session_id_));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(WvCdmRequestLicenseRollbackTest,
|
||||||
|
Offline_RollbackAndExpireAfterRestoreKey) {
|
||||||
|
Unprovision();
|
||||||
|
Provision(kLevelDefault);
|
||||||
|
|
||||||
|
std::string unused_key_id;
|
||||||
|
std::string client_auth;
|
||||||
|
GetOfflineConfiguration(&unused_key_id, &client_auth);
|
||||||
|
|
||||||
|
ASSERT_EQ(NO_ERROR, decryptor_.OpenSession(config_.key_system(), nullptr,
|
||||||
|
kDefaultCdmIdentifier, nullptr,
|
||||||
|
&session_id_));
|
||||||
|
GenerateKeyRequest(init_data_with_expiry_, kLicenseTypeOffline);
|
||||||
|
VerifyKeyRequestResponse(config_.license_server(), client_auth);
|
||||||
|
|
||||||
|
CdmKeySetId key_set_id = key_set_id_;
|
||||||
|
EXPECT_FALSE(key_set_id_.empty());
|
||||||
|
decryptor_.CloseSession(session_id_);
|
||||||
|
|
||||||
|
session_id_.clear();
|
||||||
|
decryptor_.OpenSession(config_.key_system(), nullptr, kDefaultCdmIdentifier,
|
||||||
|
nullptr, &session_id_);
|
||||||
|
EXPECT_EQ(wvcdm::KEY_ADDED, decryptor_.RestoreKey(session_id_, key_set_id));
|
||||||
|
|
||||||
|
RollbackSystemTime(kExpirationWithWindowMs_);
|
||||||
|
|
||||||
|
// Elapse time so that the key should now be considered expired.
|
||||||
|
std::this_thread::sleep_for(
|
||||||
|
std::chrono::milliseconds(kExpirationWithWindowMs_));
|
||||||
|
|
||||||
|
// Verify that we can no longer decrypt a subsample due to key expiration.
|
||||||
|
EXPECT_EQ(NEED_KEY, Decrypt(session_id_));
|
||||||
|
|
||||||
|
RestoreSystemTime();
|
||||||
|
|
||||||
|
ASSERT_EQ(NO_ERROR, decryptor_.CloseSession(session_id_));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(WvCdmRequestLicenseRollbackTest,
|
||||||
|
Offline_ExpireAndRollbackAfterRestoreKey) {
|
||||||
|
Unprovision();
|
||||||
|
Provision(kLevelDefault);
|
||||||
|
|
||||||
|
std::string unused_key_id;
|
||||||
|
std::string client_auth;
|
||||||
|
GetOfflineConfiguration(&unused_key_id, &client_auth);
|
||||||
|
|
||||||
|
ASSERT_EQ(NO_ERROR, decryptor_.OpenSession(config_.key_system(), nullptr,
|
||||||
|
kDefaultCdmIdentifier, nullptr,
|
||||||
|
&session_id_));
|
||||||
|
GenerateKeyRequest(init_data_with_expiry_, kLicenseTypeOffline);
|
||||||
|
VerifyKeyRequestResponse(config_.license_server(), client_auth);
|
||||||
|
|
||||||
|
CdmKeySetId key_set_id = key_set_id_;
|
||||||
|
EXPECT_FALSE(key_set_id_.empty());
|
||||||
|
decryptor_.CloseSession(session_id_);
|
||||||
|
|
||||||
|
session_id_.clear();
|
||||||
|
decryptor_.OpenSession(config_.key_system(), nullptr, kDefaultCdmIdentifier,
|
||||||
|
nullptr, &session_id_);
|
||||||
|
EXPECT_EQ(wvcdm::KEY_ADDED, decryptor_.RestoreKey(session_id_, key_set_id));
|
||||||
|
|
||||||
|
// Elapse time so that the key should now be considered expired.
|
||||||
|
std::this_thread::sleep_for(
|
||||||
|
std::chrono::milliseconds(kExpirationWithWindowMs_));
|
||||||
|
|
||||||
|
RollbackSystemTime(kExpirationWithWindowMs_);
|
||||||
|
|
||||||
|
// Verify that we can no longer decrypt a subsample due to key expiration.
|
||||||
|
EXPECT_EQ(NEED_KEY, Decrypt(session_id_));
|
||||||
|
|
||||||
|
RestoreSystemTime();
|
||||||
|
|
||||||
|
ASSERT_EQ(NO_ERROR, decryptor_.CloseSession(session_id_));
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace wvcdm
|
} // namespace wvcdm
|
||||||
|
|||||||
Reference in New Issue
Block a user