Improved SystemIdExtractor's opened/closed session behavior.
[ Merge of http://go/wvgerrit/201577 ] [ Cherry-pick of http://ag/28133919 ] VIC specific: No DRM reprovisioning support The SystemIdExtractor did not properly define behavior when working with opened/closed CryptoSessions. Due to the CryptoSession's class dual role of being both a session and a general handle into the crypto engine, small bugs relying on undefined behavior which happened to return expected output allowed tests to pass. This CL makes the following changes: 1) Have SystemIdExtractor verify caller expectations when session is open. 2) Improved SystemIdExtractor to operate when CryptoSession is opened or closed. 3) Updates several SystemIdExtractorTest cases to better test defined behavior without relying on undefined behavior. 4) Better code comments; hopefully some which will help prevent future misuse of the internal APIs. Test: system_id_extractor_unittest on Oriole Test: WVTS on oriole Bug: 329713288 Change-Id: I65518fe62f43e8060ea752852eb08a3d7132e2a0
This commit is contained in:
@@ -12,6 +12,7 @@ namespace wvutil {
|
||||
class FileSystem;
|
||||
} // namespace wvutil
|
||||
namespace wvcdm {
|
||||
// Forward declarations.
|
||||
class CryptoSession;
|
||||
class DeviceFiles;
|
||||
|
||||
@@ -20,6 +21,22 @@ class DeviceFiles;
|
||||
// different place.
|
||||
class SystemIdExtractor {
|
||||
public:
|
||||
// The constructor should be provided all the parameters necessary
|
||||
// to find the system ID. Although certain provisioning methods
|
||||
// may not use all parameters, this class must behave in a way which
|
||||
// makes it as easy as possible to obtain the system ID, all
|
||||
// parameters are required.
|
||||
//
|
||||
// Parameters:
|
||||
// |security_level|
|
||||
// - Requested security level, uses the |crypto_session| handle
|
||||
// to convert to a concrete security level.
|
||||
// |crypto_session|
|
||||
// - Handle into the OEMCrypto platform. If handle is open,
|
||||
// then the session's real security level should match
|
||||
// |security_level|.
|
||||
// |fs|
|
||||
// - File system handle to the global file system.
|
||||
SystemIdExtractor(RequestedSecurityLevel security_level,
|
||||
CryptoSession* crypto_session, wvutil::FileSystem* fs);
|
||||
virtual ~SystemIdExtractor() {}
|
||||
@@ -30,12 +47,17 @@ class SystemIdExtractor {
|
||||
SystemIdExtractor& operator=(const SystemIdExtractor&) = delete;
|
||||
SystemIdExtractor& operator=(SystemIdExtractor&&) = delete;
|
||||
|
||||
// Extracts the system ID from the appropriate source.
|
||||
virtual bool ExtractSystemId(uint32_t* system_id);
|
||||
|
||||
// Extracts the system ID from a keybox key data (aka CA token).
|
||||
static bool ExtractSystemIdFromKeyboxData(const std::string& key_data,
|
||||
uint32_t* system_id);
|
||||
// Extracts the system ID from a serialized OEM certificate.
|
||||
// System ID is expected to be in the manufacturer's intermediate
|
||||
// X.509 certificate from the Widevine-defined X.509 v3
|
||||
// Extension found in the TBSCertificate "extensions" attribute.
|
||||
// See RFC 5280 for X.509 certificate structure.
|
||||
static bool ExtractSystemIdFromOemCert(const std::string& oem_cert,
|
||||
uint32_t* system_id);
|
||||
|
||||
@@ -44,13 +66,15 @@ class SystemIdExtractor {
|
||||
}
|
||||
|
||||
private:
|
||||
// Note: All the internal ExtractSystemId*() methods assume
|
||||
// |system_id| is not null.
|
||||
|
||||
// Extracts the system ID from keybox-based OEMCrypto implementations.
|
||||
// System ID is expected to be found in the keybox data. Devices
|
||||
// which require OTA keybox provisioning will return a null system ID.
|
||||
bool ExtractSystemIdProv20(uint32_t* system_id);
|
||||
// Extracts the system ID from OEM certificate-based OEMCrypto
|
||||
// implementations. System ID is expected to be in the manufacturers
|
||||
// intermediate X.509 certificate.
|
||||
// implementations.
|
||||
bool ExtractSystemIdProv30(uint32_t* system_id);
|
||||
// Extracts the system ID from BCC-based OEMCrypto implementations.
|
||||
// System ID is expected to be found in the stored OEM certificate
|
||||
@@ -59,9 +83,20 @@ class SystemIdExtractor {
|
||||
// a null system ID.
|
||||
bool ExtractSystemIdProv40(uint32_t* system_id);
|
||||
|
||||
// Add future extraction methods here.
|
||||
|
||||
// Verifies that if |crypto_session_| is opened, that the
|
||||
// security level is matches the instances |security_level_|.
|
||||
// If unopened, verifies that |security_level_| is a defined
|
||||
// value.
|
||||
// Returns true if security level is valid, false otherwise.
|
||||
bool VerifySecurityLevelExpectations();
|
||||
|
||||
RequestedSecurityLevel security_level_ = kLevelDefault;
|
||||
CryptoSession* crypto_session_ = nullptr;
|
||||
wvutil::FileSystem* fs_ = nullptr;
|
||||
// Test only handle to DeviceFiles. When not null, |fs_| will be
|
||||
// ignored.
|
||||
DeviceFiles* test_device_files_ = nullptr;
|
||||
};
|
||||
} // namespace wvcdm
|
||||
|
||||
@@ -18,16 +18,22 @@ constexpr size_t kKeyboxSystemIdOffset = 4;
|
||||
// Index of certificate within cerificate chain which contains the
|
||||
// system ID (0 = leaf/device cert, 1 = intermediate/device family cert).
|
||||
constexpr size_t kOemCertSystemIdIndex = 1;
|
||||
// OID of X.509 certificate extension containing the Widevine system ID.
|
||||
// OID of X.509 TBSCertificate extension containing the Widevine
|
||||
// system ID.
|
||||
const char kWidevineSystemIdExtensionOid[] = "1.3.6.1.4.1.11129.4.1.1";
|
||||
|
||||
constexpr size_t kSystemIdLength = sizeof(uint32_t);
|
||||
|
||||
constexpr bool IsSupportedSecurityLevel(CdmSecurityLevel security_level) {
|
||||
constexpr bool IsSupportedCdmSecurityLevel(CdmSecurityLevel security_level) {
|
||||
return security_level == kSecurityLevelL1 ||
|
||||
security_level == kSecurityLevelL2 ||
|
||||
security_level == kSecurityLevelL3;
|
||||
}
|
||||
|
||||
constexpr bool IsSupportedRequestedSecurityLevel(
|
||||
RequestedSecurityLevel security_level) {
|
||||
return security_level == kLevelDefault || security_level == kLevel3;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
SystemIdExtractor::SystemIdExtractor(RequestedSecurityLevel security_level,
|
||||
@@ -45,15 +51,21 @@ bool SystemIdExtractor::ExtractSystemId(uint32_t* system_id) {
|
||||
LOGE("Output |system_id| is null");
|
||||
return false;
|
||||
}
|
||||
if (crypto_session_->GetCachedSystemId(system_id)) {
|
||||
if (!VerifySecurityLevelExpectations()) {
|
||||
// VerifySecurityLevelExpectations() will log details.
|
||||
return false;
|
||||
}
|
||||
if (crypto_session_->IsOpen() &&
|
||||
crypto_session_->GetCachedSystemId(system_id)) {
|
||||
return true;
|
||||
}
|
||||
CdmClientTokenType type = kClientTokenUninitialized;
|
||||
const CdmResponseType status =
|
||||
crypto_session_->GetProvisioningMethod(security_level_, &type);
|
||||
if (status != NO_ERROR) {
|
||||
LOGE("Failed to get provisioning method: security_level = %s, status = %d",
|
||||
RequestedSecurityLevelToString(security_level_), status.ToInt());
|
||||
LOGE("Failed to get provisioning method: security_level = %s, status = %s",
|
||||
RequestedSecurityLevelToString(security_level_),
|
||||
status.ToString().c_str());
|
||||
return false;
|
||||
}
|
||||
bool success = false;
|
||||
@@ -90,7 +102,10 @@ bool SystemIdExtractor::ExtractSystemId(uint32_t* system_id) {
|
||||
// static
|
||||
bool SystemIdExtractor::ExtractSystemIdFromKeyboxData(
|
||||
const std::string& key_data, uint32_t* system_id) {
|
||||
if (system_id == nullptr) return false;
|
||||
if (system_id == nullptr) {
|
||||
LOGE("Output |system_id| is null");
|
||||
return false;
|
||||
}
|
||||
if (key_data.size() < (kKeyboxSystemIdOffset + kSystemIdLength)) {
|
||||
LOGE("Keybox data does not contain system ID: key_data_size = %zu",
|
||||
key_data.size());
|
||||
@@ -106,7 +121,10 @@ bool SystemIdExtractor::ExtractSystemIdFromKeyboxData(
|
||||
// static
|
||||
bool SystemIdExtractor::ExtractSystemIdFromOemCert(const std::string& oem_cert,
|
||||
uint32_t* system_id) {
|
||||
if (system_id == nullptr) return false;
|
||||
if (system_id == nullptr) {
|
||||
LOGE("Output |system_id| is null");
|
||||
return false;
|
||||
}
|
||||
return ExtractExtensionValueFromCertificate(oem_cert,
|
||||
kWidevineSystemIdExtensionOid,
|
||||
kOemCertSystemIdIndex, system_id);
|
||||
@@ -122,8 +140,9 @@ bool SystemIdExtractor::ExtractSystemIdProv20(uint32_t* system_id) {
|
||||
return true;
|
||||
}
|
||||
if (status != NO_ERROR) {
|
||||
LOGE("Failed to get keybox data: security_level = %s, status = %d",
|
||||
RequestedSecurityLevelToString(security_level_), status.ToInt());
|
||||
LOGE("Failed to get keybox data: security_level = %s, status = %s",
|
||||
RequestedSecurityLevelToString(security_level_),
|
||||
status.ToString().c_str());
|
||||
return false;
|
||||
}
|
||||
if (!ExtractSystemIdFromKeyboxData(key_data, system_id)) {
|
||||
@@ -138,8 +157,9 @@ bool SystemIdExtractor::ExtractSystemIdProv30(uint32_t* system_id) {
|
||||
const CdmResponseType status =
|
||||
crypto_session_->GetTokenFromOemCert(security_level_, &oem_cert);
|
||||
if (status != NO_ERROR) {
|
||||
LOGE("Failed to get OEM certificate: security_level = %s, status = %d",
|
||||
RequestedSecurityLevelToString(security_level_), status.ToInt());
|
||||
LOGE("Failed to get OEM certificate: security_level = %s, status = %s",
|
||||
RequestedSecurityLevelToString(security_level_),
|
||||
status.ToString().c_str());
|
||||
return false;
|
||||
}
|
||||
if (!ExtractSystemIdFromOemCert(oem_cert, system_id)) {
|
||||
@@ -152,7 +172,7 @@ bool SystemIdExtractor::ExtractSystemIdProv30(uint32_t* system_id) {
|
||||
bool SystemIdExtractor::ExtractSystemIdProv40(uint32_t* system_id) {
|
||||
const CdmSecurityLevel security_level =
|
||||
crypto_session_->GetSecurityLevel(security_level_);
|
||||
if (!IsSupportedSecurityLevel(security_level)) {
|
||||
if (!IsSupportedCdmSecurityLevel(security_level)) {
|
||||
LOGE("Unsupported security level: %s",
|
||||
CdmSecurityLevelToString(security_level));
|
||||
return false;
|
||||
@@ -160,7 +180,7 @@ bool SystemIdExtractor::ExtractSystemIdProv40(uint32_t* system_id) {
|
||||
DeviceFiles real_device_files(fs_);
|
||||
// Mock DeviceFiles for testing.
|
||||
DeviceFiles& device_files =
|
||||
(test_device_files_ ? *test_device_files_ : real_device_files);
|
||||
(test_device_files_ != nullptr ? *test_device_files_ : real_device_files);
|
||||
if (!device_files.Init(security_level)) {
|
||||
LOGE("Failed to initialize device files: security_level = %s",
|
||||
CdmSecurityLevelToString(security_level));
|
||||
@@ -190,4 +210,48 @@ bool SystemIdExtractor::ExtractSystemIdProv40(uint32_t* system_id) {
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SystemIdExtractor::VerifySecurityLevelExpectations() {
|
||||
if (!IsSupportedRequestedSecurityLevel(security_level_)) {
|
||||
LOGE("Unsupported requested extractor security level: %d",
|
||||
static_cast<int>(security_level_));
|
||||
return false;
|
||||
}
|
||||
if (!crypto_session_->IsOpen()) {
|
||||
// Any other issues with the security level should be caught and
|
||||
// handled by the respective extractor methods.
|
||||
return true;
|
||||
}
|
||||
// The SystemIdExtractor is intended to work with unopened
|
||||
// CryptoSessions, but does not restrict this.
|
||||
// If the crypto session is open, it is already tied to a
|
||||
// security level; for the extractor work as expected the
|
||||
// session's security level must be the same as extractor's
|
||||
// requested security level.
|
||||
const CdmSecurityLevel session_security_level =
|
||||
crypto_session_->GetSecurityLevel();
|
||||
if (!IsSupportedCdmSecurityLevel(session_security_level)) {
|
||||
LOGE("Failed to get session security level: %s",
|
||||
CdmSecurityLevelToString(session_security_level));
|
||||
return false;
|
||||
}
|
||||
const CdmSecurityLevel extractor_security_level =
|
||||
crypto_session_->GetSecurityLevel(security_level_);
|
||||
if (!IsSupportedCdmSecurityLevel(extractor_security_level)) {
|
||||
LOGE("Failed to get extractor security level: %s",
|
||||
CdmSecurityLevelToString(extractor_security_level));
|
||||
return false;
|
||||
}
|
||||
if (session_security_level != extractor_security_level) {
|
||||
LOGE(
|
||||
"Extractor and session security levels do not match: "
|
||||
"session_security_level = %s, extractor_security_level = %s, "
|
||||
"requested_security_level = %s",
|
||||
CdmSecurityLevelToString(session_security_level),
|
||||
CdmSecurityLevelToString(extractor_security_level),
|
||||
RequestedSecurityLevelToString(security_level_));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
} // namespace wvcdm
|
||||
|
||||
@@ -270,11 +270,13 @@ class MockCryptoSession : public CryptoSession {
|
||||
MockCryptoSession(metrics::CryptoMetrics* metrics) : CryptoSession(metrics) {}
|
||||
// ~MockCryptoSession() override {}
|
||||
|
||||
bool IsOpen() override { return true; }
|
||||
CdmSecurityLevel GetSecurityLevel() override { return kSecurityLevelL1; }
|
||||
bool IsOpen() override { return is_open_; }
|
||||
CdmSecurityLevel GetSecurityLevel() override {
|
||||
return is_open_ ? open_security_level_ : kSecurityLevelUninitialized;
|
||||
}
|
||||
CdmSecurityLevel GetSecurityLevel(
|
||||
RequestedSecurityLevel security_level) override {
|
||||
return security_level == kLevelDefault ? kSecurityLevelL1
|
||||
return security_level == kLevelDefault ? default_security_level_
|
||||
: kSecurityLevelL3;
|
||||
}
|
||||
|
||||
@@ -291,6 +293,16 @@ class MockCryptoSession : public CryptoSession {
|
||||
(RequestedSecurityLevel, std::string*), (override));
|
||||
MOCK_METHOD(CdmResponseType, GetTokenFromOemCert,
|
||||
(RequestedSecurityLevel, std::string*), (override));
|
||||
|
||||
// These default values should represent good values of a
|
||||
// CryptoSession used for system ID extractions.
|
||||
// Test cases should modify them if needing to test edge cases.
|
||||
|
||||
bool is_open_ = false;
|
||||
// Security level which is returned if the session is opened.
|
||||
CdmSecurityLevel open_security_level_ = kSecurityLevelL1;
|
||||
// Security level of the underlying default OEMCrypto engine.
|
||||
CdmSecurityLevel default_security_level_ = kSecurityLevelL1;
|
||||
};
|
||||
|
||||
class MockDeviceFiles : public DeviceFiles {
|
||||
@@ -328,13 +340,27 @@ class SystemIdExtractorTest : public WvCdmTestBase {
|
||||
return extractor;
|
||||
}
|
||||
|
||||
void ExpectProvisioningType(CdmClientTokenType type) {
|
||||
EXPECT_CALL(*crypto_session_, GetCachedSystemId).WillOnce(Return(false));
|
||||
void ExpectProvisioningType(CdmClientTokenType type, bool is_open = false) {
|
||||
crypto_session_->is_open_ = is_open;
|
||||
if (is_open) {
|
||||
// Extractor should only call GetCachedSystemId if session
|
||||
// is opened.
|
||||
EXPECT_CALL(*crypto_session_, GetCachedSystemId).WillOnce(Return(false));
|
||||
} else {
|
||||
EXPECT_CALL(*crypto_session_, GetCachedSystemId).Times(0);
|
||||
}
|
||||
|
||||
EXPECT_CALL(*crypto_session_, GetProvisioningMethod(_, NotNull()))
|
||||
.WillOnce(
|
||||
DoAll(SetArgPointee<1>(type), Return(CdmResponseType(NO_ERROR))));
|
||||
}
|
||||
|
||||
void ExpectFromCached(uint32_t system_id) {
|
||||
crypto_session_->is_open_ = true;
|
||||
EXPECT_CALL(*crypto_session_, GetCachedSystemId(NotNull()))
|
||||
.WillOnce(DoAll(SetArgPointee<0>(system_id), Return(true)));
|
||||
}
|
||||
|
||||
void ExpectSet(uint32_t system_id) {
|
||||
EXPECT_CALL(*crypto_session_, SetSystemId(system_id));
|
||||
}
|
||||
@@ -361,8 +387,7 @@ TEST_F(SystemIdExtractorTest, ExtractSystemIdFromKeyboxData) {
|
||||
|
||||
TEST_F(SystemIdExtractorTest, CachedSystemId) {
|
||||
const uint32_t kCachedSystemId = 1234;
|
||||
EXPECT_CALL(*crypto_session_, GetCachedSystemId(NotNull()))
|
||||
.WillOnce(DoAll(SetArgPointee<0>(kCachedSystemId), Return(true)));
|
||||
ExpectFromCached(kCachedSystemId);
|
||||
auto extractor = CreateExtractor(kLevelDefault);
|
||||
ASSERT_TRUE(extractor);
|
||||
uint32_t system_id;
|
||||
@@ -372,6 +397,7 @@ TEST_F(SystemIdExtractorTest, CachedSystemId) {
|
||||
|
||||
TEST_F(SystemIdExtractorTest, SetSystemIdMetrics) {
|
||||
const uint32_t kSystemId = 4321;
|
||||
crypto_session_->is_open_ = true; // Must be open to set system ID.
|
||||
crypto_session_->SetSystemIdBase(kSystemId);
|
||||
drm_metrics::WvCdmMetrics::CryptoMetrics metrics_proto;
|
||||
crypto_metrics_.Serialize(&metrics_proto);
|
||||
@@ -380,8 +406,54 @@ TEST_F(SystemIdExtractorTest, SetSystemIdMetrics) {
|
||||
EXPECT_EQ(recorded_system_id, kSystemId);
|
||||
}
|
||||
|
||||
TEST_F(SystemIdExtractorTest,
|
||||
BadSecurityLevelExpectations_InvalidRequestedSecurityLevel) {
|
||||
// Extractor caller is using an invalid requested security level.
|
||||
const RequestedSecurityLevel kBadRequestedSecurityLevel =
|
||||
static_cast<RequestedSecurityLevel>(9999);
|
||||
auto extractor = CreateExtractor(kBadRequestedSecurityLevel);
|
||||
uint32_t system_id;
|
||||
EXPECT_FALSE(extractor->ExtractSystemId(&system_id));
|
||||
}
|
||||
|
||||
TEST_F(SystemIdExtractorTest,
|
||||
BadSecurityLevelExpectations_UnexpectedSessionSecurityLevel) {
|
||||
// CryptoSession is returning an unexpected result for its security
|
||||
// level.
|
||||
crypto_session_->is_open_ = true;
|
||||
crypto_session_->open_security_level_ = kSecurityLevelUnknown;
|
||||
auto extractor = CreateExtractor(kLevelDefault);
|
||||
uint32_t system_id;
|
||||
EXPECT_FALSE(extractor->ExtractSystemId(&system_id));
|
||||
}
|
||||
|
||||
TEST_F(SystemIdExtractorTest,
|
||||
BadSecurityLevelExpectations_UnexpectedExtractorSecurityLevel) {
|
||||
// OEMCrypto (via session-less CryptoSession) is returning an
|
||||
// unexpected result for the default security level.
|
||||
crypto_session_->is_open_ = true;
|
||||
crypto_session_->default_security_level_ = kSecurityLevelUnknown;
|
||||
auto extractor = CreateExtractor(kLevelDefault);
|
||||
uint32_t system_id;
|
||||
EXPECT_FALSE(extractor->ExtractSystemId(&system_id));
|
||||
}
|
||||
|
||||
TEST_F(SystemIdExtractorTest,
|
||||
BadSecurityLevelExpectations_MismatchedSessionSecurityLevel) {
|
||||
// CryptoSession and Extractor are different security levels.
|
||||
crypto_session_->is_open_ = true;
|
||||
// Case 1: Session L3, extractor L1
|
||||
crypto_session_->open_security_level_ = kSecurityLevelL3;
|
||||
auto extractor = CreateExtractor(kLevelDefault);
|
||||
uint32_t system_id;
|
||||
EXPECT_FALSE(extractor->ExtractSystemId(&system_id));
|
||||
// Case 2: Session L1, extractor L3
|
||||
crypto_session_->open_security_level_ = kSecurityLevelL1;
|
||||
extractor = CreateExtractor(kLevel3);
|
||||
EXPECT_FALSE(extractor->ExtractSystemId(&system_id));
|
||||
}
|
||||
|
||||
TEST_F(SystemIdExtractorTest, GetProvisioningMethod_Failed) {
|
||||
EXPECT_CALL(*crypto_session_, GetCachedSystemId).WillOnce(Return(false));
|
||||
EXPECT_CALL(*crypto_session_, GetProvisioningMethod(_, NotNull()))
|
||||
.WillOnce(Return(CdmResponseType(UNKNOWN_ERROR)));
|
||||
auto extractor = CreateExtractor(kLevelDefault);
|
||||
@@ -420,6 +492,19 @@ TEST_F(SystemIdExtractorTest, KeyboxDevice_Success) {
|
||||
EXPECT_EQ(system_id, kKeyboxSystemId);
|
||||
}
|
||||
|
||||
TEST_F(SystemIdExtractorTest, KeyboxDevice_Success_WithCallToGetCached) {
|
||||
ExpectProvisioningType(kClientTokenKeybox, /* is_open = */ true);
|
||||
EXPECT_CALL(*crypto_session_, GetTokenFromKeybox(kLevelDefault, NotNull()))
|
||||
.WillOnce(DoAll(SetArgPointee<1>(kKeyboxDataStr),
|
||||
Return(CdmResponseType(NO_ERROR))));
|
||||
ExpectSet(kKeyboxSystemId);
|
||||
auto extractor = CreateExtractor(kLevelDefault);
|
||||
ASSERT_TRUE(extractor);
|
||||
uint32_t system_id;
|
||||
EXPECT_TRUE(extractor->ExtractSystemId(&system_id));
|
||||
EXPECT_EQ(system_id, kKeyboxSystemId);
|
||||
}
|
||||
|
||||
TEST_F(SystemIdExtractorTest, KeyboxDevice_NeedsOtaKeyboxProvisioning) {
|
||||
ExpectProvisioningType(kClientTokenKeybox);
|
||||
EXPECT_CALL(*crypto_session_, GetTokenFromKeybox(kLevelDefault, NotNull()))
|
||||
|
||||
Reference in New Issue
Block a user