Backward compatibility for licenses and certificates

Certificates and offline licenses are stored in security level
specific directories in klp. When devices transition from jb-mr2,
their persistent information has to be ported to these directories.

bug:10366036

Merge of https://widevine-internal-review.googlesource.com/#/c/7310/
from the widevine CDM repo

Change-Id: I70b4a79dc5b69bda7fc3a4b92fdcde7ef8b41836
This commit is contained in:
Jeff Tinker
2013-08-22 09:37:18 -07:00
parent 2fa6b63292
commit db41502f86
10 changed files with 455 additions and 12 deletions

View File

@@ -59,6 +59,8 @@ class DeviceFiles {
bool RetrieveFile(const char* name, std::string* data);
private:
virtual void SecurityLevelPathBackwardCompatibility();
File* file_;
CdmSecurityLevel security_level_;
bool initialized_;

View File

@@ -31,6 +31,8 @@ class File {
virtual bool Exists(const std::string& file_path);
virtual bool Remove(const std::string& file_path);
virtual bool Copy(const std::string& old_path, const std::string& new_path);
virtual bool List(const std::string& path, std::vector<std::string>* files);
virtual bool CreateDirectory(const std::string dir_path);
virtual bool IsDirectory(const std::string& dir_path);
virtual bool IsRegularFile(const std::string& file_path);

View File

@@ -48,6 +48,9 @@ class Properties {
static inline bool decrypt_with_empty_session_support() {
return decrypt_with_empty_session_support_;
}
static inline bool security_level_path_backward_compatibility_support() {
return security_level_path_backward_compatibility_support_;
}
static bool GetCompanyName(std::string* company_name);
static bool GetModelName(std::string* model_name);
static bool GetArchitectureName(std::string* arch_name);
@@ -58,6 +61,7 @@ class Properties {
std::string* base_path);
static bool GetFactoryKeyboxPath(std::string* keybox);
static bool GetOEMCryptoPath(std::string* library_name);
static bool GetSecurityLevelDirectories(std::vector<std::string>* dirs);
static const std::string GetSecurityLevel(const CdmSessionId& session_id);
static const std::vector<uint8_t> GetServiceCertificate(
const CdmSessionId& session_id);
@@ -95,6 +99,9 @@ class Properties {
static void set_decrypt_with_empty_session_support(bool flag) {
decrypt_with_empty_session_support_ = flag;
}
static void set_security_level_path_backward_compatibility_support(bool flag) {
security_level_path_backward_compatibility_support_ = flag;
}
static bool begin_license_usage_when_received_;
static bool require_explicit_renew_request_;
@@ -104,6 +111,7 @@ class Properties {
static bool use_certificates_as_identification_;
static bool extract_pssh_data_;
static bool decrypt_with_empty_session_support_;
static bool security_level_path_backward_compatibility_support_;
static scoped_ptr<CdmClientPropertySetMap> session_property_set_;
CORE_DISALLOW_COPY_AND_ASSIGN(Properties);

View File

@@ -22,6 +22,8 @@ namespace {
const char kCertificateFileName[] = "cert.bin";
const char kLicenseFileNameExt[] = ".lic";
const char kWildcard[] = "*";
const char kPathDelimiter[] = "/";
const char *kSecurityLevelPathCompatibilityExclusionList[] = { "ay64.dat" };
} // namespace
namespace wvcdm {
@@ -90,6 +92,10 @@ bool DeviceFiles::RetrieveCertificate(std::string* certificate,
return false;
}
if (Properties::security_level_path_backward_compatibility_support()) {
SecurityLevelPathBackwardCompatibility();
}
std::string serialized_hashed_file;
if (!RetrieveFile(kCertificateFileName, &serialized_hashed_file))
return false;
@@ -448,6 +454,65 @@ bool DeviceFiles::RetrieveFile(const char* name, std::string* data) {
return true;
}
void DeviceFiles::SecurityLevelPathBackwardCompatibility() {
std::string path;
if (!Properties::GetDeviceFilesBasePath(security_level_, &path)) {
LOGW("DeviceFiles::SecurityLevelPathBackwardCompatibility: Unable to "
"get base path");
return;
}
std::vector<std::string> security_dirs;
if (!Properties::GetSecurityLevelDirectories(&security_dirs)) {
LOGW("DeviceFiles::SecurityLevelPathBackwardCompatibility: Unable to "
"get security directories");
return;
}
size_t pos = std::string::npos;
for (size_t i = 0; i < security_dirs.size(); ++i) {
pos = path.rfind(security_dirs[i]);
if (std::string::npos != pos)
break;
}
if (pos == std::string::npos) {
LOGV("DeviceFiles::SecurityLevelPathBackwardCompatibility: Security level "
"specific path not found. Check properties?");
return;
}
std::string from_dir(path, 0, pos);
std::vector<std::string> files;
file_->List(from_dir, &files);
for (size_t i = 0; i < files.size(); ++i) {
std::string from = from_dir + files[i];
bool exclude = false;
for (size_t j = 0;
j < sizeof(kSecurityLevelPathCompatibilityExclusionList) /
sizeof(const char*);
j++) {
if (files[i].compare(kSecurityLevelPathCompatibilityExclusionList[j]) == 0) {
exclude = true;
break;
}
}
if (exclude) continue;
if (!file_->IsRegularFile(from)) continue;
for (size_t j = 0; j < security_dirs.size(); ++j) {
std::string to_dir = from_dir + security_dirs[j];
if (!file_->Exists(to_dir))
file_->CreateDirectory(to_dir);
std::string to = to_dir + files[i];
file_->Copy(from, to);
}
file_->Remove(from);
}
}
std::string DeviceFiles::GetCertificateFileName() {
return kCertificateFileName;
}

View File

@@ -4,6 +4,10 @@
#include "properties_configuration.h"
#include "wv_cdm_constants.h"
namespace {
const char *kSecurityLevelDirs[] = { "L1/", "L3/" };
} // namespace
namespace wvcdm {
bool Properties::begin_license_usage_when_received_;
bool Properties::require_explicit_renew_request_;
@@ -13,6 +17,7 @@ bool Properties::oem_crypto_use_userspace_buffers_;
bool Properties::use_certificates_as_identification_;
bool Properties::extract_pssh_data_;
bool Properties::decrypt_with_empty_session_support_;
bool Properties::security_level_path_backward_compatibility_support_;
scoped_ptr<CdmClientPropertySetMap> Properties::session_property_set_;
void Properties::Init() {
@@ -25,6 +30,7 @@ void Properties::Init() {
kPropertyUseCertificatesAsIdentification;
extract_pssh_data_ = kExtractPsshData;
decrypt_with_empty_session_support_ = kDecryptWithEmptySessionSupport;
security_level_path_backward_compatibility_support_ = kSecurityLevelPathBackwardCompatibilitySupport;
session_property_set_.reset(new CdmClientPropertySetMap());
}
@@ -93,4 +99,13 @@ bool Properties::UsePrivacyMode(const CdmSessionId& session_id) {
}
return property_set->use_privacy_mode();
}
bool Properties::GetSecurityLevelDirectories(std::vector<std::string>* dirs) {
dirs->resize(sizeof(kSecurityLevelDirs)/sizeof(const char*));
for (size_t i = 0; i < dirs->size(); ++i) {
(*dirs)[i] = kSecurityLevelDirs[i];
}
return true;
}
} // namespace wvcdm

View File

@@ -19,6 +19,7 @@ using ::testing::HasSubstr;
using ::testing::NotNull;
using ::testing::Return;
using ::testing::ReturnArg;
using ::testing::SetArgPointee;
using ::testing::SetArrayArgument;
using ::testing::StrEq;
@@ -987,6 +988,8 @@ class MockFile : public File {
MOCK_METHOD1(Exists, bool(const std::string&));
MOCK_METHOD1(Remove, bool(const std::string&));
MOCK_METHOD2(Copy, bool(const std::string&, const std::string&));
MOCK_METHOD2(List, bool(const std::string&, std::vector<std::string>*));
MOCK_METHOD1(CreateDirectory, bool(const std::string));
MOCK_METHOD1(IsDirectory, bool(const std::string&));
MOCK_METHOD1(IsRegularFile, bool(const std::string&));
@@ -1021,6 +1024,10 @@ class DeviceFilesTest : public ::testing::Test {
class DeviceFilesStoreTest : public DeviceFilesTest,
public ::testing::WithParamInterface<bool> {};
class DeviceFilesSecurityLevelTest
: public DeviceFilesTest,
public ::testing::WithParamInterface<CdmSecurityLevel> {};
MATCHER(IsCreateFileFlagSet, "") { return File::kCreate & arg; }
MATCHER(IsBinaryFileFlagSet, "") { return File::kBinary & arg; }
MATCHER_P(IsStrEq, str, "") {
@@ -1031,7 +1038,7 @@ MATCHER_P(IsStrEq, str, "") {
MATCHER_P2(Contains, str1, str2, "") {
// Estimating the length of data. We can have gmock provide length
// as well as pointer to data but that will introduce a dependency on tr1
std::string data(arg, str1.size() + str2.size() + 500);
std::string data(arg, str1.size() + str2.size() + 75);
return (data.find(str1) != std::string::npos &&
data.find(str2) != std::string::npos);
}
@@ -1039,12 +1046,12 @@ MATCHER_P6(Contains, str1, str2, str3, str4, str5, str6, "") {
// Estimating the length of data. We can have gmock provide length
// as well as pointer to data but that will introduce a dependency on tr1
std::string data(arg, str1.size() + str2.size() + str3.size() + str4.size() +
str5.size() + str6.size() + 500);
str5.size() + str6.size() + 75);
return (data.find(str1) != std::string::npos &&
data.find(str2) != std::string::npos,
data.find(str3) != std::string::npos,
data.find(str4) != std::string::npos,
data.find(str5) != std::string::npos,
data.find(str2) != std::string::npos &&
data.find(str3) != std::string::npos &&
data.find(str4) != std::string::npos &&
data.find(str5) != std::string::npos &&
data.find(str6) != std::string::npos);
}
@@ -1109,6 +1116,40 @@ TEST_F(DeviceFilesTest, ReadCertificate) {
EXPECT_EQ(kTestWrappedPrivateKey, b2a_hex(wrapped_private_key));
}
TEST_P(DeviceFilesSecurityLevelTest, SecurityLevel) {
MockFile file;
std::string certificate(GenerateRandomData(kCertificateLen));
std::string wrapped_private_key(GenerateRandomData(kWrappedKeyLen));
CdmSecurityLevel security_level = GetParam();
std::string device_base_path;
ASSERT_TRUE(
Properties::GetDeviceFilesBasePath(security_level, &device_base_path));
std::string device_certificate_path =
device_base_path + DeviceFiles::GetCertificateFileName();
EXPECT_CALL(file, IsDirectory(StrEq(device_base_path)))
.WillOnce(Return(false));
EXPECT_CALL(file, CreateDirectory(StrEq(device_base_path)))
.WillOnce(Return(true));
EXPECT_CALL(file, Open(StrEq(device_certificate_path),
AllOf(IsCreateFileFlagSet(), IsBinaryFileFlagSet())))
.WillOnce(Return(true));
EXPECT_CALL(file, Write(Contains(certificate, wrapped_private_key),
Gt(certificate.size() + wrapped_private_key.size())))
.WillOnce(ReturnArg<1>());
EXPECT_CALL(file, Close()).Times(1);
EXPECT_CALL(file, Read(_, _)).Times(0);
DeviceFiles device_files;
EXPECT_TRUE(device_files.Init(&file, security_level));
EXPECT_TRUE(device_files.StoreCertificate(certificate, wrapped_private_key));
}
INSTANTIATE_TEST_CASE_P(SecurityLevel, DeviceFilesSecurityLevelTest,
::testing::Values(kSecurityLevelL1, kSecurityLevelL3));
TEST_P(DeviceFilesStoreTest, StoreLicense) {
MockFile file;
size_t license_num = 0;
@@ -1244,6 +1285,81 @@ TEST_F(DeviceFilesTest, RetrieveLicenses) {
}
}
TEST_F(DeviceFilesTest, SecurityLevelPathBackwardCompatibility) {
MockFile file;
std::vector<std::string> security_dirs;
EXPECT_TRUE(Properties::GetSecurityLevelDirectories(&security_dirs));
size_t pos = std::string::npos;
for (size_t i = 0; i < security_dirs.size(); ++i) {
pos = device_base_path_.rfind(security_dirs[i]);
if (std::string::npos != pos) break;
}
EXPECT_NE(std::string::npos, pos);
std::string base_path(device_base_path_, 0, pos);
std::vector<std::string> old_files;
std::string new_path;
for (size_t i = 0; i < security_dirs.size(); ++i) {
old_files.push_back(security_dirs[i]);
new_path = base_path + security_dirs[i];
EXPECT_CALL(file, IsRegularFile(StrEq(new_path))).WillOnce(Return(false));
EXPECT_CALL(file, Exists(StrEq(new_path)))
.WillOnce(Return(false))
.WillRepeatedly(Return(true));
EXPECT_CALL(file, CreateDirectory(StrEq(new_path))).WillOnce(Return(true));
}
std::string old_path = base_path + DeviceFiles::GetCertificateFileName();
old_files.push_back(DeviceFiles::GetCertificateFileName());
EXPECT_CALL(file, IsRegularFile(StrEq(old_path)))
.WillOnce(Return(true));
EXPECT_CALL(file, Remove(StrEq(old_path))).WillOnce(Return(true));
for (size_t i = 0; i < security_dirs.size(); ++i) {
new_path = base_path + security_dirs[i] +
DeviceFiles::GetCertificateFileName();
EXPECT_CALL(file, Copy(StrEq(old_path), StrEq(new_path)))
.WillOnce(Return(true));
}
for (size_t j = 0; j < kNumberOfLicenses; ++j) {
std::string file_name = license_test_data[j].key_set_id +
DeviceFiles::GetLicenseFileNameExtension();
old_path = base_path + file_name;
old_files.push_back(file_name);
EXPECT_CALL(file, IsRegularFile(StrEq(old_path))).WillOnce(Return(true));
EXPECT_CALL(file, Remove(StrEq(old_path))).WillOnce(Return(true));
for (size_t i = 0; i < security_dirs.size(); ++i) {
new_path = base_path + security_dirs[i] + file_name;
EXPECT_CALL(file, Copy(StrEq(old_path), StrEq(new_path)))
.WillOnce(Return(true));
}
}
EXPECT_CALL(file, List(StrEq(base_path), NotNull()))
.WillOnce(DoAll(SetArgPointee<1>(old_files), Return(true)));
std::string data = a2bs_hex(kTestCertificateFileData);
new_path = device_base_path_ + DeviceFiles::GetCertificateFileName();
EXPECT_CALL(file, Exists(StrEq(new_path))).WillOnce(Return(true));
EXPECT_CALL(file, FileSize(_)).WillOnce(Return(data.size()));
EXPECT_CALL(file, Open(_, _)).WillOnce(Return(true));
EXPECT_CALL(file, Read(NotNull(), Eq(data.size()))).WillOnce(DoAll(
SetArrayArgument<0>(data.begin(), data.end()), Return(data.size())));
EXPECT_CALL(file, Close()).Times(1);
EXPECT_CALL(file, Write(_, _)).Times(0);
DeviceFiles device_files;
EXPECT_TRUE(device_files.Init(&file, kSecurityLevelL1));
Properties::Init();
std::string certificate, wrapped_private_key;
ASSERT_TRUE(
device_files.RetrieveCertificate(&certificate, &wrapped_private_key));
}
TEST_F(DeviceFilesTest, UpdateLicenseState) {
MockFile file;
std::string license_path = device_base_path_ +

View File

@@ -7,6 +7,7 @@
#include "test_vectors.h"
namespace {
const std::string kTestDirName = "test";
const std::string kTestFileName = "test.txt";
const std::string kTestFileName2 = "test2.txt";
const std::string kTestFileNameExt = ".txt";
@@ -199,4 +200,67 @@ TEST_F(FileTest, WriteReadBinaryFile) {
EXPECT_EQ(write_data, read_data);
}
TEST_F(FileTest, CopyFile) {
std::string path = test_vectors::kTestDir + kTestFileName;
File file;
file.Remove(path);
std::string write_data = GenerateRandomData(600);
File wr_file;
EXPECT_TRUE(wr_file.Open(path, File::kCreate | File::kBinary));
EXPECT_TRUE(wr_file.Write(write_data.data(), write_data.size()));
wr_file.Close();
EXPECT_TRUE(file.Exists(path));
std::string path_copy = test_vectors::kTestDir + kTestFileName2;
EXPECT_FALSE(file.Exists(path_copy));
EXPECT_TRUE(file.Copy(path, path_copy));
std::string read_data;
read_data.resize(file.FileSize(path_copy));
File rd_file;
EXPECT_TRUE(rd_file.Open(path_copy, File::kReadOnly));
EXPECT_TRUE(rd_file.Read(&read_data[0], read_data.size()));
rd_file.Close();
EXPECT_EQ(write_data, read_data);
EXPECT_EQ(file.FileSize(path), file.FileSize(path_copy));
}
TEST_F(FileTest, ListEmptyDirectory) {
std::vector<std::string> files;
File file;
EXPECT_TRUE(file.List(test_vectors::kTestDir, &files));
EXPECT_EQ(0u, files.size());
}
TEST_F(FileTest, ListFiles) {
File file;
std::string path = test_vectors::kTestDir + kTestDirName;
EXPECT_TRUE(file.CreateDirectory(path));
path = test_vectors::kTestDir + kTestFileName;
std::string write_data = GenerateRandomData(600);
EXPECT_TRUE(file.Open(path, File::kCreate | File::kBinary));
EXPECT_TRUE(file.Write(write_data.data(), write_data.size()));
file.Close();
EXPECT_TRUE(file.Exists(path));
path = test_vectors::kTestDir + kTestFileName2;
write_data = GenerateRandomData(600);
EXPECT_TRUE(file.Open(path, File::kCreate | File::kBinary));
EXPECT_TRUE(file.Write(write_data.data(), write_data.size()));
file.Close();
EXPECT_TRUE(file.Exists(path));
std::vector<std::string> files;
EXPECT_TRUE(file.List(test_vectors::kTestDir, &files));
EXPECT_EQ(3u, files.size());
for (size_t i = 0; i < files.size(); ++i) {
EXPECT_TRUE(files[i].compare(kTestDirName) == 0 ||
files[i].compare(kTestFileName) == 0 ||
files[i].compare(kTestFileName2) == 0);
}
}
} // namespace wvcdm