widevine_cdm_component_installer.cc revision ca12bfac764ba476d6cd062bf1dde12cc64c3f40
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 36using content::BrowserThread; 37using content::PluginService; 38 39namespace { 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: pdkaonnflpjcgibpgaanildgengnihcm. 47const uint8 kSha2Hash[] = { 0xf3, 0xa0, 0xed, 0xd5, 0xbf, 0x92, 0x68, 0x1f, 48 0x60, 0x0d, 0x8b, 0x36, 0x4d, 0x6d, 0x87, 0x2c, 49 0x86, 0x61, 0x12, 0x20, 0x21, 0xf8, 0x94, 0xdd, 50 0xe1, 0xb6, 0xb4, 0x55, 0x34, 0x8c, 0x2e, 0x20 }; 51 52// File name of the Widevine CDM component manifest on different platforms. 53const char kWidevineCdmManifestName[] = "WidevineCdm"; 54 55// Name of the Widevine CDM OS in the component manifest. 56const char kWidevineCdmOperatingSystem[] = 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. 66const char kWidevineCdmArch[] = 67#if defined(ARCH_CPU_X86) 68 "ia32"; 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. 76const char kNullVersion[] = "0.0.0.0"; 77 78// The base directory on Windows looks like: 79// <profile>\AppData\Local\Google\Chrome\User Data\WidevineCdm\. 80base::FilePath GetWidevineCdmBaseDirectory() { 81 base::FilePath result; 82 PathService::Get(chrome::DIR_COMPONENT_WIDEVINE_CDM, &result); 83 return result; 84} 85 86// Widevine CDM has the version encoded in the path so we need to enumerate the 87// directories to find the full path. 88// On success, |latest_dir| returns something like: 89// <profile>\AppData\Local\Google\Chrome\User Data\WidevineCdm\10.3.44.555\. 90// |latest_version| returns the corresponding version number. |older_dirs| 91// returns directories of all older versions. 92bool GetWidevineCdmDirectory(base::FilePath* latest_dir, 93 base::Version* latest_version, 94 std::vector<base::FilePath>* older_dirs) { 95 base::FilePath base_dir = GetWidevineCdmBaseDirectory(); 96 bool found = false; 97 base::FileEnumerator file_enumerator( 98 base_dir, false, base::FileEnumerator::DIRECTORIES); 99 for (base::FilePath path = file_enumerator.Next(); !path.value().empty(); 100 path = file_enumerator.Next()) { 101 base::Version version(path.BaseName().MaybeAsASCII()); 102 if (!version.IsValid()) 103 continue; 104 if (found) { 105 if (version.CompareTo(*latest_version) > 0) { 106 older_dirs->push_back(*latest_dir); 107 *latest_dir = path; 108 *latest_version = version; 109 } else { 110 older_dirs->push_back(path); 111 } 112 } else { 113 *latest_dir = path; 114 *latest_version = version; 115 found = true; 116 } 117 } 118 return found; 119} 120 121bool MakeWidevineCdmPluginInfo(const base::FilePath& path, 122 const base::Version& version, 123 content::PepperPluginInfo* plugin_info) { 124 if (!version.IsValid() || 125 version.components().size() != 126 static_cast<size_t>(kWidevineCdmVersionNumComponents)) { 127 return false; 128 } 129 130 plugin_info->is_internal = false; 131 // Widevine CDM must run out of process. 132 plugin_info->is_out_of_process = true; 133 plugin_info->path = path; 134 plugin_info->name = kWidevineCdmDisplayName; 135 plugin_info->description = kWidevineCdmDescription; 136 plugin_info->version = version.GetString(); 137 content::WebPluginMimeType widevine_cdm_mime_type( 138 kWidevineCdmPluginMimeType, 139 kWidevineCdmPluginExtension, 140 kWidevineCdmPluginMimeTypeDescription); 141 plugin_info->mime_types.push_back(widevine_cdm_mime_type); 142 plugin_info->permissions = kWidevineCdmPluginPermissions; 143 144 return true; 145} 146 147void RegisterWidevineCdmWithChrome(const base::FilePath& path, 148 const base::Version& version) { 149 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 150 content::PepperPluginInfo plugin_info; 151 if (!MakeWidevineCdmPluginInfo(path, version, &plugin_info)) 152 return; 153 154 PluginService::GetInstance()->RegisterInternalPlugin( 155 plugin_info.ToWebPluginInfo(), true); 156 PluginService::GetInstance()->RefreshPlugins(); 157} 158 159// Returns true if this browser is compatible with the given Widevine CDM 160// manifest, with the version specified in the manifest in |version_out|. 161bool CheckWidevineCdmManifest(const base::DictionaryValue& manifest, 162 base::Version* version_out) { 163 std::string name; 164 manifest.GetStringASCII("name", &name); 165 166 if (name != kWidevineCdmManifestName) 167 return false; 168 169 std::string proposed_version; 170 manifest.GetStringASCII("version", &proposed_version); 171 base::Version version(proposed_version.c_str()); 172 if (!version.IsValid()) 173 return false; 174 175 std::string os; 176 manifest.GetStringASCII("x-widevine-cdm-os", &os); 177 if (os != kWidevineCdmOperatingSystem) 178 return false; 179 180 std::string arch; 181 manifest.GetStringASCII("x-widevine-cdm-arch", &arch); 182 if (arch != kWidevineCdmArch) 183 return false; 184 185 *version_out = version; 186 return true; 187} 188 189class 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 205WidevineCdmComponentInstaller::WidevineCdmComponentInstaller( 206 const base::Version& version) 207 : current_version_(version) { 208 DCHECK(version.IsValid()); 209} 210 211void WidevineCdmComponentInstaller::OnUpdateError(int error) { 212 NOTREACHED() << "Widevine CDM update error: " << error; 213} 214 215bool 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(unpack_path.AppendASCII(kWidevineCdmFileName))) 225 return false; 226 227 base::FilePath adapter_source_path; 228 PathService::Get(chrome::FILE_WIDEVINE_CDM_ADAPTER, &adapter_source_path); 229 if (!base::PathExists(adapter_source_path)) 230 return false; 231 232 // Passed the basic tests. Time to install it. 233 base::FilePath install_path = 234 GetWidevineCdmBaseDirectory().AppendASCII(version.GetString()); 235 if (base::PathExists(install_path)) 236 return false; 237 if (!base::Move(unpack_path, install_path)) 238 return false; 239 240 base::FilePath adapter_install_path = 241 install_path.AppendASCII(kWidevineCdmAdapterFileName); 242 if (!base::CopyFile(adapter_source_path, adapter_install_path)) 243 return false; 244 245 // Installation is done. Now register the Widevine CDM with chrome. 246 current_version_ = version; 247 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, base::Bind( 248 &RegisterWidevineCdmWithChrome, adapter_install_path, version)); 249 return true; 250} 251 252bool WidevineCdmComponentInstaller::GetInstalledFile( 253 const std::string& file, base::FilePath* installed_file) { 254 // Only the CDM is component-updated. 255 if (file != kWidevineCdmFileName) 256 return false; 257 258 if (current_version_.Equals(base::Version(kNullVersion))) 259 return false; // No CDM has been installed yet. 260 261 *installed_file = 262 GetWidevineCdmBaseDirectory().AppendASCII(current_version_.GetString()) 263 .AppendASCII(kWidevineCdmFileName); 264 return true; 265} 266 267void FinishWidevineCdmUpdateRegistration(ComponentUpdateService* cus, 268 const base::Version& version) { 269 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 270 CrxComponent widevine_cdm; 271 widevine_cdm.name = "WidevineCdm"; 272 widevine_cdm.installer = new WidevineCdmComponentInstaller(version); 273 widevine_cdm.version = version; 274 widevine_cdm.pk_hash.assign(kSha2Hash, &kSha2Hash[sizeof(kSha2Hash)]); 275 if (cus->RegisterComponent(widevine_cdm) != ComponentUpdateService::kOk) { 276 NOTREACHED() << "Widevine CDM component registration failed."; 277 return; 278 } 279} 280 281void StartWidevineCdmUpdateRegistration(ComponentUpdateService* cus) { 282 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); 283 base::FilePath base_dir = GetWidevineCdmBaseDirectory(); 284 if (!base::PathExists(base_dir) && 285 !file_util::CreateDirectory(base_dir)) { 286 NOTREACHED() << "Could not create Widevine CDM directory."; 287 return; 288 } 289 290 base::FilePath latest_dir; 291 base::Version version(kNullVersion); 292 std::vector<base::FilePath> older_dirs; 293 294 if (GetWidevineCdmDirectory(&latest_dir, &version, &older_dirs)) { 295 base::FilePath adapter_path = 296 latest_dir.AppendASCII(kWidevineCdmAdapterFileName); 297 base::FilePath cdm_path = latest_dir.AppendASCII(kWidevineCdmFileName); 298 299 if (base::PathExists(adapter_path) && 300 base::PathExists(cdm_path)) { 301 BrowserThread::PostTask( 302 BrowserThread::UI, FROM_HERE, 303 base::Bind(&RegisterWidevineCdmWithChrome, adapter_path, version)); 304 } else { 305 base::DeleteFile(latest_dir, true); 306 version = base::Version(kNullVersion); 307 } 308 } 309 310 BrowserThread::PostTask( 311 BrowserThread::UI, FROM_HERE, 312 base::Bind(&FinishWidevineCdmUpdateRegistration, cus, version)); 313 314 // Remove older versions of Widevine CDM. 315 for (std::vector<base::FilePath>::iterator iter = older_dirs.begin(); 316 iter != older_dirs.end(); ++iter) { 317 base::DeleteFile(*iter, true); 318 } 319} 320 321#endif // defined(WIDEVINE_CDM_AVAILABLE) && defined(WIDEVINE_CDM_IS_COMPONENT) 322 323} // namespace 324 325void RegisterWidevineCdmComponent(ComponentUpdateService* cus) { 326#if defined(WIDEVINE_CDM_AVAILABLE) && defined(WIDEVINE_CDM_IS_COMPONENT) 327 BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE, 328 base::Bind(&StartWidevineCdmUpdateRegistration, cus)); 329#endif // defined(WIDEVINE_CDM_AVAILABLE) && defined(WIDEVINE_CDM_IS_COMPONENT) 330} 331