diff --git a/infra/cdm_registration.cc b/infra/cdm_registration.cc new file mode 100644 index 000000000..16f338a32 --- /dev/null +++ b/infra/cdm_registration.cc @@ -0,0 +1,309 @@ +// Copyright 2021 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome/common/media/cdm_registration.h" + +#include "base/check.h" +#include "base/files/file_path.h" +#include "base/files/file_util.h" +#include "base/logging.h" +#include "base/path_service.h" +#include "build/build_config.h" +#include "build/chromeos_buildflags.h" +#include "content/public/common/cdm_info.h" +#include "media/cdm/cdm_capability.h" +#include "media/cdm/cdm_type.h" +#include "third_party/widevine/cdm/buildflags.h" + +#if BUILDFLAG(ENABLE_LIBRARY_CDMS) +#include "base/command_line.h" +#include "media/base/media_switches.h" +#include "media/cdm/cdm_paths.h" // nogncheck +#endif + +#if BUILDFLAG(ENABLE_WIDEVINE) +#include "third_party/widevine/cdm/widevine_cdm_common.h" // nogncheck +#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_WIN) +#include "base/native_library.h" +#include "chrome/common/chrome_paths.h" +#endif // BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_WIN) +#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS) +#include "base/no_destructor.h" +#include "components/cdm/common/cdm_manifest.h" +#include "media/cdm/supported_audio_codecs.h" +// Needed for WIDEVINE_CDM_MIN_GLIBC_VERSION. This file is in +// SHARED_INTERMEDIATE_DIR. +#include "widevine_cdm_version.h" // nogncheck +// The following must be after widevine_cdm_version.h. +#if defined(WIDEVINE_CDM_MIN_GLIBC_VERSION) +#include +#include "base/version.h" +#endif // defined(WIDEVINE_CDM_MIN_GLIBC_VERSION) +#if !BUILDFLAG(IS_CHROMEOS_ASH) +#include "chrome/common/media/component_widevine_cdm_hint_file_linux.h" +#endif // !BUILDFLAG(IS_CHROMEOS_ASH) +#endif // BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS) +#endif // BUILDFLAG(ENABLE_WIDEVINE) + +namespace { + +using Robustness = content::CdmInfo::Robustness; + +#if BUILDFLAG(ENABLE_WIDEVINE) +#if (BUILDFLAG(BUNDLE_WIDEVINE_CDM) || \ + BUILDFLAG(ENABLE_WIDEVINE_CDM_COMPONENT)) && \ + (BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS)) +// Create a CdmInfo for a Widevine CDM, using |version|, |cdm_library_path|, and +// |capability|. +std::unique_ptr CreateWidevineCdmInfo( + const base::Version& version, + const base::FilePath& cdm_library_path, + media::CdmCapability capability) { + return std::make_unique( + kWidevineKeySystem, Robustness::kSoftwareSecure, std::move(capability), + /*supports_sub_key_systems=*/false, kWidevineCdmDisplayName, + kWidevineCdmType, version, cdm_library_path); +} + +// On desktop Linux and ChromeOS, given |cdm_base_path| that points to a folder +// containing the Widevine CDM and associated files, read the manifest included +// in that directory and create a CdmInfo. If that is successful, return the +// CdmInfo. If not, return nullptr. +std::unique_ptr CreateCdmInfoFromWidevineDirectory( + const base::FilePath& cdm_base_path) { + // Library should be inside a platform specific directory. + auto cdm_library_path = + media::GetPlatformSpecificDirectory(cdm_base_path) + .Append(base::GetNativeLibraryName(kWidevineCdmLibraryName)); + if (!base::PathExists(cdm_library_path)) + return nullptr; + + // Manifest should be at the top level. + auto manifest_path = cdm_base_path.Append(FILE_PATH_LITERAL("manifest.json")); + base::Version version; + media::CdmCapability capability; + if (!ParseCdmManifestFromPath(manifest_path, &version, &capability)) + return nullptr; + + return CreateWidevineCdmInfo(version, cdm_library_path, + std::move(capability)); +} +#endif // (BUILDFLAG(BUNDLE_WIDEVINE_CDM) || + // BUILDFLAG(ENABLE_WIDEVINE_CDM_COMPONENT)) && (BUILDFLAG(IS_LINUX) || + // BUILDFLAG(IS_CHROMEOS)) + +#if BUILDFLAG(BUNDLE_WIDEVINE_CDM) && \ + (BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS)) +// On Linux/ChromeOS we have to preload the CDM since it uses the zygote +// sandbox. On Windows and Mac, the bundled CDM is handled by the component +// updater. + +// This code checks to see if the Widevine CDM was bundled with Chrome. If one +// can be found and looks valid, it returns the CdmInfo for the CDM. Otherwise +// it returns nullptr. +content::CdmInfo* GetBundledWidevine() { + // We only want to do this on the first call, as if Widevine wasn't bundled + // with Chrome (or it was deleted/removed) it won't be loaded into the zygote. + static base::NoDestructor> s_cdm_info( + []() -> std::unique_ptr { + base::FilePath install_dir; + CHECK(base::PathService::Get(chrome::DIR_BUNDLED_WIDEVINE_CDM, + &install_dir)); + return CreateCdmInfoFromWidevineDirectory(install_dir); + }()); + return s_cdm_info->get(); +} +#endif // BUILDFLAG(BUNDLE_WIDEVINE_CDM) && (BUILDFLAG(IS_LINUX) || + // BUILDFLAG(IS_CHROMEOS)) + +#if BUILDFLAG(ENABLE_WIDEVINE_CDM_COMPONENT) && \ + (BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS)) +// This code checks to see if a component updated Widevine CDM can be found. If +// there is one and it looks valid, return the CdmInfo for that CDM. Otherwise +// return nullptr. +content::CdmInfo* GetComponentUpdatedWidevine() { + // We only want to do this on the first call, as the component updater may run + // and download a new version once Chrome has been running for a while. Since + // the first returned version will be the one loaded into the zygote, we want + // to return the same thing on subsequent calls. + static base::NoDestructor> s_cdm_info( + []() -> std::unique_ptr { + auto install_dir = GetLatestComponentUpdatedWidevineCdmDirectory(); + if (install_dir.empty()) + return nullptr; + + return CreateCdmInfoFromWidevineDirectory(install_dir); + }()); + return s_cdm_info->get(); +} +#endif // BUILDFLAG(ENABLE_WIDEVINE_CDM_COMPONENT) && (BUILDFLAG(IS_LINUX) || + // BUILDFLAG(IS_CHROMEOS)) + +void AddSoftwareSecureWidevine(std::vector* cdms) { +#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS) +#if defined(WIDEVINE_CDM_MIN_GLIBC_VERSION) + base::Version glibc_version(gnu_get_libc_version()); + DCHECK(glibc_version.IsValid()); + if (glibc_version < base::Version(WIDEVINE_CDM_MIN_GLIBC_VERSION)) { + LOG(WARNING) << "Widevine not registered because glibc version is too low"; + return; + } +#endif // defined(WIDEVINE_CDM_MIN_GLIBC_VERSION) + + // The Widevine CDM on Linux needs to be registered (and loaded) before the + // zygote is locked down. The CDM can be found from the version bundled with + // Chrome (if BUNDLE_WIDEVINE_CDM = true) and/or the version downloaded by + // the component updater (if ENABLE_WIDEVINE_CDM_COMPONENT = true). If two + // versions exist, take the one with the higher version number. + // + // Note that the component updater will detect the bundled version, and if + // there is no newer version available, select the bundled version. In this + // case both versions will be the same and point to the same directory, so + // it doesn't matter which one is loaded. + content::CdmInfo* bundled_widevine = nullptr; +#if BUILDFLAG(BUNDLE_WIDEVINE_CDM) + bundled_widevine = GetBundledWidevine(); +#endif + + content::CdmInfo* updated_widevine = nullptr; +#if BUILDFLAG(ENABLE_WIDEVINE_CDM_COMPONENT) + updated_widevine = GetComponentUpdatedWidevine(); +#endif + + // If only a bundled version is available, or both are available and the + // bundled version is not less than the updated version, register the + // bundled version. If only the updated version is available, or both are + // available and the updated version is greater, then register the updated + // version. If neither are available, then nothing is registered. + if (bundled_widevine && + (!updated_widevine || + bundled_widevine->version >= updated_widevine->version)) { + VLOG(1) << "Registering bundled Widevine " << bundled_widevine->version; + cdms->push_back(*bundled_widevine); + } else if (updated_widevine) { + VLOG(1) << "Registering component updated Widevine " + << updated_widevine->version; + cdms->push_back(*updated_widevine); + } else { + VLOG(1) << "Widevine enabled but no library found"; + } +#endif // BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS) +} + +void AddHardwareSecureWidevine(std::vector* cdms) { +#if BUILDFLAG(USE_CHROMEOS_PROTECTED_MEDIA) +#if BUILDFLAG(IS_CHROMEOS_LACROS) + if (!base::CommandLine::ForCurrentProcess()->HasSwitch( + switches::kLacrosUseChromeosProtectedMedia)) { + return; + } +#endif // BUILDFLAG(IS_CHROMEOS_LACROS) + media::CdmCapability capability; + + // The following audio formats are supported for decrypt-only. + capability.audio_codecs = media::GetCdmSupportedAudioCodecs(); + + // We currently support VP9, H264 and HEVC video formats with + // decrypt-and-decode. Not specifying any profiles to indicate that all + // relevant profiles should be considered supported. + const media::VideoCodecInfo kAllProfiles; + capability.video_codecs.emplace(media::VideoCodec::kVP9, kAllProfiles); +#if BUILDFLAG(USE_PROPRIETARY_CODECS) + capability.video_codecs.emplace(media::VideoCodec::kH264, kAllProfiles); +#endif +#if BUILDFLAG(ENABLE_PLATFORM_HEVC) +#if BUILDFLAG(IS_CHROMEOS_LACROS) + if (base::CommandLine::ForCurrentProcess()->HasSwitch( + switches::kLacrosEnablePlatformHevc)) { + capability.video_codecs.emplace(media::VideoCodec::kHEVC, kAllProfiles); + } +#else + capability.video_codecs.emplace(media::VideoCodec::kHEVC, kAllProfiles); +#endif // BUILDFLAG(IS_CHROMEOS_LACROS) +#endif +#if BUILDFLAG(USE_CHROMEOS_PROTECTED_AV1) + capability.video_codecs.emplace(media::VideoCodec::kAV1, kAllProfiles); +#elif BUILDFLAG(IS_CHROMEOS_LACROS) + if (base::CommandLine::ForCurrentProcess()->HasSwitch( + switches::kLacrosUseChromeosProtectedAv1)) { + capability.video_codecs.emplace(media::VideoCodec::kAV1, kAllProfiles); + } +#endif + + // Both encryption schemes are supported on ChromeOS. + capability.encryption_schemes.insert(media::EncryptionScheme::kCenc); + capability.encryption_schemes.insert(media::EncryptionScheme::kCbcs); + + // Both temporary and persistent sessions are supported on ChromeOS. + capability.session_types.insert(media::CdmSessionType::kTemporary); + capability.session_types.insert(media::CdmSessionType::kPersistentLicense); + + cdms->push_back( + content::CdmInfo(kWidevineKeySystem, Robustness::kHardwareSecure, + std::move(capability), content::kChromeOsCdmType)); +#endif // BUILDFLAG(USE_CHROMEOS_PROTECTED_MEDIA) +} + +void AddWidevine(std::vector* cdms) { + AddSoftwareSecureWidevine(cdms); + AddHardwareSecureWidevine(cdms); +} +#endif // BUILDFLAG(ENABLE_WIDEVINE) + +#if BUILDFLAG(ENABLE_LIBRARY_CDMS) +void AddExternalClearKey(std::vector* cdms) { + // Register Clear Key CDM if specified in command line. + base::CommandLine* command_line = base::CommandLine::ForCurrentProcess(); + base::FilePath clear_key_cdm_path = + command_line->GetSwitchValuePath(switches::kClearKeyCdmPathForTesting); + if (clear_key_cdm_path.empty() || !base::PathExists(clear_key_cdm_path)) + return; + + // TODO(crbug.com/764480): Remove these after we have a central place for + // External Clear Key (ECK) related information. + // Normal External Clear Key key system. + const char kExternalClearKeyKeySystem[] = "org.chromium.externalclearkey"; + // A variant of ECK key system that has a different CDM type. + const char kkExternalClearKeyDifferentCdmTypeTestKeySystem[] = + "org.chromium.externalclearkey.differentcdmtype"; + + // Supported codecs are hard-coded in ExternalClearKeyProperties. + media::CdmCapability capability( + {}, {}, {media::EncryptionScheme::kCenc, media::EncryptionScheme::kCbcs}, + {media::CdmSessionType::kTemporary, + media::CdmSessionType::kPersistentLicense}); + + // Register kkExternalClearKeyDifferentCdmTypeTestKeySystem first separately. + // Otherwise, it'll be treated as a sub-key-system of normal + // kExternalClearKeyKeySystem. See MultipleCdmTypes test in + // ECKEncryptedMediaTest. + cdms->push_back(content::CdmInfo( + kkExternalClearKeyDifferentCdmTypeTestKeySystem, + Robustness::kSoftwareSecure, capability, + /*supports_sub_key_systems=*/false, media::kClearKeyCdmDisplayName, + media::kClearKeyCdmDifferentCdmType, base::Version("0.1.0.0"), + clear_key_cdm_path)); + + cdms->push_back(content::CdmInfo( + kExternalClearKeyKeySystem, Robustness::kSoftwareSecure, capability, + /*supports_sub_key_systems=*/true, media::kClearKeyCdmDisplayName, + media::kClearKeyCdmType, base::Version("0.1.0.0"), clear_key_cdm_path)); +} +#endif // BUILDFLAG(ENABLE_LIBRARY_CDMS) + +} // namespace + +void RegisterCdmInfo(std::vector* cdms) { + DVLOG(1) << __func__; + DCHECK(cdms); + DCHECK(cdms->empty()); + +#if BUILDFLAG(ENABLE_WIDEVINE) + AddWidevine(cdms); +#endif + +#if BUILDFLAG(ENABLE_LIBRARY_CDMS) + AddExternalClearKey(cdms); +#endif +}