diff --git a/libwvdrmengine/cdm/core/include/system_id_extractor.h b/libwvdrmengine/cdm/core/include/system_id_extractor.h index e98f03b8..e89cd39d 100644 --- a/libwvdrmengine/cdm/core/include/system_id_extractor.h +++ b/libwvdrmengine/cdm/core/include/system_id_extractor.h @@ -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 diff --git a/libwvdrmengine/cdm/core/src/system_id_extractor.cpp b/libwvdrmengine/cdm/core/src/system_id_extractor.cpp index 4ca2c639..93ca441e 100644 --- a/libwvdrmengine/cdm/core/src/system_id_extractor.cpp +++ b/libwvdrmengine/cdm/core/src/system_id_extractor.cpp @@ -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. -const std::string kWidevineSystemIdExtensionOid = "1.3.6.1.4.1.11129.4.1.1"; +// 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; @@ -88,7 +100,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()); @@ -104,7 +119,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); @@ -120,8 +138,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)) { @@ -136,8 +155,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)) { @@ -150,7 +170,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; @@ -158,7 +178,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)); @@ -188,4 +208,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(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 diff --git a/libwvdrmengine/cdm/core/test/system_id_extractor_unittest.cpp b/libwvdrmengine/cdm/core/test/system_id_extractor_unittest.cpp index 0a66935e..b7398606 100644 --- a/libwvdrmengine/cdm/core/test/system_id_extractor_unittest.cpp +++ b/libwvdrmengine/cdm/core/test/system_id_extractor_unittest.cpp @@ -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(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()))