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.
|
||||
|
||||
#include <errno.h>
|
||||
#include <time.h>
|
||||
|
||||
#include <chrono>
|
||||
#include <sstream>
|
||||
#include <thread>
|
||||
|
||||
#include <android-base/properties.h>
|
||||
#include <gmock/gmock.h>
|
||||
@@ -4873,7 +4877,7 @@ TEST_F(WvCdmRequestLicenseTest, SessionKeyChangeNotificationTest) {
|
||||
session_id_,
|
||||
AllOf(Each(Pair(_, kKeyStatusUsable)), Not(IsEmpty())), true))
|
||||
.WillOnce(Invoke(&decrypt_callback, &DecryptCallbackTester::Decrypt));
|
||||
;
|
||||
|
||||
EXPECT_CALL(listener, OnExpirationUpdate(session_id_, _));
|
||||
|
||||
const std::string kCpKeyId = a2bs_hex(
|
||||
@@ -5363,4 +5367,273 @@ TEST_F(WvCdmRequestLicenseTest, DISABLED_DecryptPathTest) {
|
||||
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
|
||||
|
||||
Reference in New Issue
Block a user