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