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:
Alex Dale
2024-06-12 16:36:27 -07:00
parent 11025293aa
commit b282ec92b6
3 changed files with 207 additions and 23 deletions

View File

@@ -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

View File

@@ -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

View File

@@ -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()))