Home | History | Annotate | Download | only in component_updater
      1 // Copyright (c) 2013 The Chromium Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 #include "chrome/browser/component_updater/widevine_cdm_component_installer.h"
      6 
      7 #include <string.h>
      8 
      9 #include <vector>
     10 
     11 #include "base/base_paths.h"
     12 #include "base/bind.h"
     13 #include "base/command_line.h"
     14 #include "base/compiler_specific.h"
     15 #include "base/file_util.h"
     16 #include "base/files/file_enumerator.h"
     17 #include "base/files/file_path.h"
     18 #include "base/logging.h"
     19 #include "base/path_service.h"
     20 #include "base/strings/string_util.h"
     21 #include "base/values.h"
     22 #include "base/version.h"
     23 #include "build/build_config.h"
     24 #include "chrome/browser/component_updater/component_updater_service.h"
     25 #include "chrome/browser/plugins/plugin_prefs.h"
     26 #include "chrome/common/chrome_constants.h"
     27 #include "chrome/common/chrome_paths.h"
     28 #include "chrome/common/widevine_cdm_constants.h"
     29 #include "content/public/browser/browser_thread.h"
     30 #include "content/public/browser/plugin_service.h"
     31 #include "content/public/common/pepper_plugin_info.h"
     32 #include "third_party/widevine/cdm/widevine_cdm_common.h"
     33 
     34 #include "widevine_cdm_version.h"  // In SHARED_INTERMEDIATE_DIR.
     35 
     36 using content::BrowserThread;
     37 using content::PluginService;
     38 
     39 namespace {
     40 
     41 // TODO(xhwang): Move duplicate code among all component installer
     42 // implementations to some common place.
     43 
     44 #if defined(WIDEVINE_CDM_AVAILABLE) && defined(WIDEVINE_CDM_IS_COMPONENT)
     45 
     46 // CRX hash. The extension id is: oimompecagnajdejgnnjijobebaeigek.
     47 const uint8 kSha2Hash[] = { 0xe8, 0xce, 0xcf, 0x42, 0x06, 0xd0, 0x93, 0x49,
     48                             0x6d, 0xd9, 0x89, 0xe1, 0x41, 0x04, 0x86, 0x4a,
     49                             0x8f, 0xbd, 0x86, 0x12, 0xb9, 0x58, 0x9b, 0xfb,
     50                             0x4f, 0xbb, 0x1b, 0xa9, 0xd3, 0x85, 0x37, 0xef };
     51 
     52 // File name of the Widevine CDM component manifest on different platforms.
     53 const char kWidevineCdmManifestName[] = "WidevineCdm";
     54 
     55 // Name of the Widevine CDM OS in the component manifest.
     56 const char kWidevineCdmPlatform[] =
     57 #if defined(OS_MACOSX)
     58     "mac";
     59 #elif defined(OS_WIN)
     60     "win";
     61 #else  // OS_LINUX, etc. TODO(viettrungluu): Separate out Chrome OS and Android?
     62     "linux";
     63 #endif
     64 
     65 // Name of the Widevine CDM architecture in the component manifest.
     66 const char kWidevineCdmArch[] =
     67 #if defined(ARCH_CPU_X86)
     68     "x86";
     69 #elif defined(ARCH_CPU_X86_64)
     70     "x64";
     71 #else  // TODO(viettrungluu): Support an ARM check?
     72     "???";
     73 #endif
     74 
     75 // If we don't have a Widevine CDM component, this is the version we claim.
     76 const char kNullVersion[] = "0.0.0.0";
     77 
     78 // The base directory on Windows looks like:
     79 // <profile>\AppData\Local\Google\Chrome\User Data\WidevineCdm\.
     80 base::FilePath GetWidevineCdmBaseDirectory() {
     81   base::FilePath result;
     82   PathService::Get(chrome::DIR_COMPONENT_WIDEVINE_CDM, &result);
     83   return result;
     84 }
     85 
     86 // Widevine CDM is packaged as a multi-CRX. Widevine CDM binaries are located in
     87 // _platform_specific/<platform_arch> folder in the package. This function
     88 // returns the platform-specific subdirectory that is part of that multi-CRX.
     89 base::FilePath GetPlatformDirectory(const base::FilePath& base_path) {
     90   std::string platform_arch = kWidevineCdmPlatform;
     91   platform_arch += '_';
     92   platform_arch += kWidevineCdmArch;
     93   return base_path.AppendASCII("_platform_specific").AppendASCII(platform_arch);
     94 }
     95 
     96 // Widevine CDM has the version encoded in the path so we need to enumerate the
     97 // directories to find the full path.
     98 // On success, |latest_dir| returns something like:
     99 // <profile>\AppData\Local\Google\Chrome\User Data\WidevineCdm\10.3.44.555\.
    100 // |latest_version| returns the corresponding version number. |older_dirs|
    101 // returns directories of all older versions.
    102 bool GetWidevineCdmDirectory(base::FilePath* latest_dir,
    103                              base::Version* latest_version,
    104                              std::vector<base::FilePath>* older_dirs) {
    105   base::FilePath base_dir = GetWidevineCdmBaseDirectory();
    106   bool found = false;
    107   base::FileEnumerator file_enumerator(
    108       base_dir, false, base::FileEnumerator::DIRECTORIES);
    109   for (base::FilePath path = file_enumerator.Next(); !path.value().empty();
    110        path = file_enumerator.Next()) {
    111     base::Version version(path.BaseName().MaybeAsASCII());
    112     if (!version.IsValid())
    113       continue;
    114     if (found) {
    115       if (version.CompareTo(*latest_version) > 0) {
    116         older_dirs->push_back(*latest_dir);
    117         *latest_dir = path;
    118         *latest_version = version;
    119       } else {
    120         older_dirs->push_back(path);
    121       }
    122     } else {
    123       *latest_dir = path;
    124       *latest_version = version;
    125       found = true;
    126     }
    127   }
    128   return found;
    129 }
    130 
    131 bool MakeWidevineCdmPluginInfo(const base::FilePath& path,
    132                                const base::Version& version,
    133                                content::PepperPluginInfo* plugin_info) {
    134   if (!version.IsValid() ||
    135       version.components().size() !=
    136           static_cast<size_t>(kWidevineCdmVersionNumComponents)) {
    137     return false;
    138   }
    139 
    140   plugin_info->is_internal = false;
    141   // Widevine CDM must run out of process.
    142   plugin_info->is_out_of_process = true;
    143   plugin_info->path = path;
    144   plugin_info->name = kWidevineCdmDisplayName;
    145   plugin_info->description = kWidevineCdmDescription;
    146   plugin_info->version = version.GetString();
    147   content::WebPluginMimeType widevine_cdm_mime_type(
    148       kWidevineCdmPluginMimeType,
    149       kWidevineCdmPluginExtension,
    150       kWidevineCdmPluginMimeTypeDescription);
    151   plugin_info->mime_types.push_back(widevine_cdm_mime_type);
    152   plugin_info->permissions = kWidevineCdmPluginPermissions;
    153 
    154   return true;
    155 }
    156 
    157 void RegisterWidevineCdmWithChrome(const base::FilePath& path,
    158                                    const base::Version& version) {
    159   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    160   content::PepperPluginInfo plugin_info;
    161   if (!MakeWidevineCdmPluginInfo(path, version, &plugin_info))
    162     return;
    163 
    164   PluginService::GetInstance()->RegisterInternalPlugin(
    165       plugin_info.ToWebPluginInfo(), true);
    166   PluginService::GetInstance()->RefreshPlugins();
    167 }
    168 
    169 // Returns true if this browser is compatible with the given Widevine CDM
    170 // manifest, with the version specified in the manifest in |version_out|.
    171 bool CheckWidevineCdmManifest(const base::DictionaryValue& manifest,
    172                               base::Version* version_out) {
    173   std::string name;
    174   manifest.GetStringASCII("name", &name);
    175 
    176   if (name != kWidevineCdmManifestName)
    177     return false;
    178 
    179   std::string proposed_version;
    180   manifest.GetStringASCII("version", &proposed_version);
    181   base::Version version(proposed_version.c_str());
    182   if (!version.IsValid())
    183     return false;
    184 
    185   *version_out = version;
    186   return true;
    187 }
    188 
    189 class WidevineCdmComponentInstaller : public ComponentInstaller {
    190  public:
    191   explicit WidevineCdmComponentInstaller(const base::Version& version);
    192   virtual ~WidevineCdmComponentInstaller() {}
    193 
    194   virtual void OnUpdateError(int error) OVERRIDE;
    195   virtual bool Install(const base::DictionaryValue& manifest,
    196                        const base::FilePath& unpack_path) OVERRIDE;
    197 
    198   virtual bool GetInstalledFile(const std::string& file,
    199                                 base::FilePath* installed_file) OVERRIDE;
    200 
    201  private:
    202   base::Version current_version_;
    203 };
    204 
    205 WidevineCdmComponentInstaller::WidevineCdmComponentInstaller(
    206     const base::Version& version)
    207     : current_version_(version) {
    208   DCHECK(version.IsValid());
    209 }
    210 
    211 void WidevineCdmComponentInstaller::OnUpdateError(int error) {
    212   NOTREACHED() << "Widevine CDM update error: " << error;
    213 }
    214 
    215 bool WidevineCdmComponentInstaller::Install(
    216     const base::DictionaryValue& manifest,
    217     const base::FilePath& unpack_path) {
    218   base::Version version;
    219   if (!CheckWidevineCdmManifest(manifest, &version))
    220     return false;
    221   if (current_version_.CompareTo(version) > 0)
    222     return false;
    223 
    224   if (!base::PathExists(
    225       GetPlatformDirectory(unpack_path).AppendASCII(kWidevineCdmFileName))) {
    226     return false;
    227   }
    228 
    229   base::FilePath adapter_source_path;
    230   PathService::Get(chrome::FILE_WIDEVINE_CDM_ADAPTER, &adapter_source_path);
    231   if (!base::PathExists(adapter_source_path))
    232     return false;
    233 
    234   // Passed the basic tests. Time to install it.
    235   base::FilePath install_path =
    236       GetWidevineCdmBaseDirectory().AppendASCII(version.GetString());
    237   if (base::PathExists(install_path))
    238     return false;
    239   if (!base::Move(unpack_path, install_path))
    240     return false;
    241 
    242   base::FilePath adapter_install_path = GetPlatformDirectory(install_path)
    243       .AppendASCII(kWidevineCdmAdapterFileName);
    244   if (!base::CopyFile(adapter_source_path, adapter_install_path))
    245     return false;
    246 
    247   // Installation is done. Now register the Widevine CDM with chrome.
    248   current_version_ = version;
    249   BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, base::Bind(
    250       &RegisterWidevineCdmWithChrome, adapter_install_path, version));
    251   return true;
    252 }
    253 
    254 // Given |file|, a path like "_platform_specific/win_x86/widevinecdm.dll",
    255 // returns the assumed install path. The path separator in |file| is '/'
    256 // for all platforms. Caller is responsible for checking that the
    257 // |installed_file| actually exists.
    258 bool WidevineCdmComponentInstaller::GetInstalledFile(
    259     const std::string& file, base::FilePath* installed_file) {
    260   if (current_version_.Equals(base::Version(kNullVersion)))
    261     return false;  // No CDM has been installed yet.
    262 
    263   *installed_file = GetWidevineCdmBaseDirectory().AppendASCII(
    264       current_version_.GetString()).AppendASCII(file);
    265   return true;
    266 }
    267 
    268 void FinishWidevineCdmUpdateRegistration(ComponentUpdateService* cus,
    269                                          const base::Version& version) {
    270   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    271   CrxComponent widevine_cdm;
    272   widevine_cdm.name = "WidevineCdm";
    273   widevine_cdm.installer = new WidevineCdmComponentInstaller(version);
    274   widevine_cdm.version = version;
    275   widevine_cdm.pk_hash.assign(kSha2Hash, &kSha2Hash[sizeof(kSha2Hash)]);
    276   if (cus->RegisterComponent(widevine_cdm) != ComponentUpdateService::kOk) {
    277     NOTREACHED() << "Widevine CDM component registration failed.";
    278     return;
    279   }
    280 }
    281 
    282 void StartWidevineCdmUpdateRegistration(ComponentUpdateService* cus) {
    283   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
    284   base::FilePath base_dir = GetWidevineCdmBaseDirectory();
    285   if (!base::PathExists(base_dir) &&
    286       !file_util::CreateDirectory(base_dir)) {
    287     NOTREACHED() << "Could not create Widevine CDM directory.";
    288     return;
    289   }
    290 
    291   base::FilePath latest_dir;
    292   base::Version version(kNullVersion);
    293   std::vector<base::FilePath> older_dirs;
    294 
    295   if (GetWidevineCdmDirectory(&latest_dir, &version, &older_dirs)) {
    296     base::FilePath latest_platform_dir = GetPlatformDirectory(latest_dir);
    297     base::FilePath adapter_path =
    298         latest_platform_dir.AppendASCII(kWidevineCdmAdapterFileName);
    299     base::FilePath cdm_path =
    300         latest_platform_dir.AppendASCII(kWidevineCdmFileName);
    301 
    302     if (base::PathExists(adapter_path) &&
    303         base::PathExists(cdm_path)) {
    304       BrowserThread::PostTask(
    305           BrowserThread::UI, FROM_HERE,
    306           base::Bind(&RegisterWidevineCdmWithChrome, adapter_path, version));
    307     } else {
    308       base::DeleteFile(latest_dir, true);
    309       version = base::Version(kNullVersion);
    310     }
    311   }
    312 
    313   BrowserThread::PostTask(
    314       BrowserThread::UI, FROM_HERE,
    315       base::Bind(&FinishWidevineCdmUpdateRegistration, cus, version));
    316 
    317   // Remove older versions of Widevine CDM.
    318   for (std::vector<base::FilePath>::iterator iter = older_dirs.begin();
    319        iter != older_dirs.end(); ++iter) {
    320     base::DeleteFile(*iter, true);
    321   }
    322 }
    323 
    324 #endif  // defined(WIDEVINE_CDM_AVAILABLE) && defined(WIDEVINE_CDM_IS_COMPONENT)
    325 
    326 }  // namespace
    327 
    328 void RegisterWidevineCdmComponent(ComponentUpdateService* cus) {
    329 #if defined(WIDEVINE_CDM_AVAILABLE) && defined(WIDEVINE_CDM_IS_COMPONENT)
    330   BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE,
    331                           base::Bind(&StartWidevineCdmUpdateRegistration, cus));
    332 #endif  // defined(WIDEVINE_CDM_AVAILABLE) && defined(WIDEVINE_CDM_IS_COMPONENT)
    333 }
    334