fune/dom/media/eme/KeySystemConfig.cpp
alwu 1f5d7f95bc Bug 1706121 - part5 : prevent using MFCDM under the private browsing mode. r=media-playback-reviewers,padenot
For GMP CDM, we will use in-memory storage for it under the private
browsing mode, but we can't do that for MFCDM.

Therefore, we should disable it under the private browsing mode to
prevent any user data leak.

Differential Revision: https://phabricator.services.mozilla.com/D210070
2024-05-14 18:37:14 +00:00

426 lines
15 KiB
C++

/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim:set ts=2 sw=2 sts=2 et cindent: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "KeySystemConfig.h"
#include "EMEUtils.h"
#include "GMPUtils.h"
#include "KeySystemNames.h"
#include "mozilla/dom/Promise.h"
#include "mozilla/StaticPrefs_media.h"
#include "nsPrintfCString.h"
#ifdef XP_WIN
# include "WMFDecoderModule.h"
#endif
#ifdef MOZ_WIDGET_ANDROID
# include "AndroidDecoderModule.h"
# include "mozilla/java/MediaDrmProxyWrappers.h"
# include "nsMimeTypes.h"
#endif
#ifdef MOZ_WMF_CDM
# include "mediafoundation/WMFCDMImpl.h"
#endif
namespace mozilla {
/* static */
bool KeySystemConfig::Supports(const nsAString& aKeySystem) {
#ifdef MOZ_WIDGET_ANDROID
// No GMP on Android, check if we can use MediaDrm for this keysystem.
if (mozilla::java::MediaDrmProxy::IsSchemeSupported(
NS_ConvertUTF16toUTF8(aKeySystem))) {
return true;
}
#else
# ifdef MOZ_WMF_CDM
// Test only, pretend we have already installed CDMs.
if (StaticPrefs::media_eme_wmf_use_mock_cdm_for_external_cdms()) {
return true;
}
# endif
// Check if Widevine L3 or Clearkey has been downloaded via GMP downloader.
if (IsWidevineKeySystem(aKeySystem) || IsClearkeyKeySystem(aKeySystem)) {
return HaveGMPFor(nsCString(CHROMIUM_CDM_API),
{NS_ConvertUTF16toUTF8(aKeySystem)});
}
#endif
#if MOZ_WMF_CDM
// Check if Widevine L1 has been downloaded via GMP downloader.
if (IsWidevineExperimentKeySystemAndSupported(aKeySystem)) {
return HaveGMPFor(nsCString(kWidevineExperimentAPIName),
{nsCString(kWidevineExperimentKeySystemName)});
}
// PlayReady and WMF-based ClearKey are always installed, we don't need to
// download them.
if (IsPlayReadyKeySystemAndSupported(aKeySystem) ||
IsWMFClearKeySystemAndSupported(aKeySystem)) {
return true;
}
#endif
return false;
}
/* static */ void KeySystemConfig::CreateClearKeyKeySystemConfigs(
const KeySystemConfigRequest& aRequest,
nsTArray<KeySystemConfig>& aOutConfigs) {
KeySystemConfig* config = aOutConfigs.AppendElement();
config->mKeySystem = aRequest.mKeySystem;
config->mInitDataTypes.AppendElement(u"cenc"_ns);
config->mInitDataTypes.AppendElement(u"keyids"_ns);
config->mInitDataTypes.AppendElement(u"webm"_ns);
config->mPersistentState = Requirement::Optional;
config->mDistinctiveIdentifier = Requirement::NotAllowed;
config->mSessionTypes.AppendElement(SessionType::Temporary);
config->mEncryptionSchemes.AppendElement(u"cenc"_ns);
config->mEncryptionSchemes.AppendElement(u"cbcs"_ns);
config->mEncryptionSchemes.AppendElement(u"cbcs-1-9"_ns);
if (StaticPrefs::media_clearkey_persistent_license_enabled()) {
config->mSessionTypes.AppendElement(SessionType::PersistentLicense);
}
#if defined(XP_WIN)
// Clearkey CDM uses WMF's H.264 decoder on Windows.
if (WMFDecoderModule::CanCreateMFTDecoder(WMFStreamType::H264)) {
config->mMP4.SetCanDecryptAndDecode(EME_CODEC_H264);
} else {
config->mMP4.SetCanDecrypt(EME_CODEC_H264);
}
#else
config->mMP4.SetCanDecrypt(EME_CODEC_H264);
#endif
config->mMP4.SetCanDecrypt(EME_CODEC_AAC);
config->mMP4.SetCanDecrypt(EME_CODEC_FLAC);
config->mMP4.SetCanDecrypt(EME_CODEC_OPUS);
config->mMP4.SetCanDecrypt(EME_CODEC_VP9);
#ifdef MOZ_AV1
config->mMP4.SetCanDecrypt(EME_CODEC_AV1);
#endif
config->mWebM.SetCanDecrypt(EME_CODEC_VORBIS);
config->mWebM.SetCanDecrypt(EME_CODEC_OPUS);
config->mWebM.SetCanDecrypt(EME_CODEC_VP8);
config->mWebM.SetCanDecrypt(EME_CODEC_VP9);
#ifdef MOZ_AV1
config->mWebM.SetCanDecrypt(EME_CODEC_AV1);
#endif
if (StaticPrefs::media_clearkey_test_key_systems_enabled()) {
// Add testing key systems. These offer the same capabilities as the
// base clearkey system, so just clone clearkey and change the name.
KeySystemConfig clearkeyWithProtectionQuery{*config};
clearkeyWithProtectionQuery.mKeySystem.AssignLiteral(
kClearKeyWithProtectionQueryKeySystemName);
aOutConfigs.AppendElement(std::move(clearkeyWithProtectionQuery));
}
}
/* static */ void KeySystemConfig::CreateWivineL3KeySystemConfigs(
const KeySystemConfigRequest& aRequest,
nsTArray<KeySystemConfig>& aOutConfigs) {
KeySystemConfig* config = aOutConfigs.AppendElement();
config->mKeySystem = aRequest.mKeySystem;
config->mInitDataTypes.AppendElement(u"cenc"_ns);
config->mInitDataTypes.AppendElement(u"keyids"_ns);
config->mInitDataTypes.AppendElement(u"webm"_ns);
config->mPersistentState = Requirement::Optional;
config->mDistinctiveIdentifier = Requirement::NotAllowed;
config->mSessionTypes.AppendElement(SessionType::Temporary);
#ifdef MOZ_WIDGET_ANDROID
config->mSessionTypes.AppendElement(SessionType::PersistentLicense);
#endif
config->mAudioRobustness.AppendElement(u"SW_SECURE_CRYPTO"_ns);
config->mVideoRobustness.AppendElement(u"SW_SECURE_CRYPTO"_ns);
config->mVideoRobustness.AppendElement(u"SW_SECURE_DECODE"_ns);
config->mEncryptionSchemes.AppendElement(u"cenc"_ns);
config->mEncryptionSchemes.AppendElement(u"cbcs"_ns);
config->mEncryptionSchemes.AppendElement(u"cbcs-1-9"_ns);
#if defined(MOZ_WIDGET_ANDROID)
// MediaDrm.isCryptoSchemeSupported only allows passing
// "video/mp4" or "video/webm" for mimetype string.
// See
// https://developer.android.com/reference/android/media/MediaDrm.html#isCryptoSchemeSupported(java.util.UUID,
// java.lang.String) for more detail.
typedef struct {
const nsCString& mMimeType;
const nsCString& mEMECodecType;
const char16_t* mCodecType;
KeySystemConfig::ContainerSupport* mSupportType;
} DataForValidation;
DataForValidation validationList[] = {
{nsCString(VIDEO_MP4), EME_CODEC_H264, java::MediaDrmProxy::AVC,
&config->mMP4},
{nsCString(VIDEO_MP4), EME_CODEC_VP9, java::MediaDrmProxy::AVC,
&config->mMP4},
# ifdef MOZ_AV1
{nsCString(VIDEO_MP4), EME_CODEC_AV1, java::MediaDrmProxy::AV1,
&config->mMP4},
# endif
{nsCString(AUDIO_MP4), EME_CODEC_AAC, java::MediaDrmProxy::AAC,
&config->mMP4},
{nsCString(AUDIO_MP4), EME_CODEC_FLAC, java::MediaDrmProxy::FLAC,
&config->mMP4},
{nsCString(AUDIO_MP4), EME_CODEC_OPUS, java::MediaDrmProxy::OPUS,
&config->mMP4},
{nsCString(VIDEO_WEBM), EME_CODEC_VP8, java::MediaDrmProxy::VP8,
&config->mWebM},
{nsCString(VIDEO_WEBM), EME_CODEC_VP9, java::MediaDrmProxy::VP9,
&config->mWebM},
# ifdef MOZ_AV1
{nsCString(VIDEO_WEBM), EME_CODEC_AV1, java::MediaDrmProxy::AV1,
&config->mWebM},
# endif
{nsCString(AUDIO_WEBM), EME_CODEC_VORBIS, java::MediaDrmProxy::VORBIS,
&config->mWebM},
{nsCString(AUDIO_WEBM), EME_CODEC_OPUS, java::MediaDrmProxy::OPUS,
&config->mWebM},
};
for (const auto& data : validationList) {
if (java::MediaDrmProxy::IsCryptoSchemeSupported(kWidevineKeySystemName,
data.mMimeType)) {
if (!AndroidDecoderModule::SupportsMimeType(data.mMimeType).isEmpty()) {
data.mSupportType->SetCanDecryptAndDecode(data.mEMECodecType);
} else {
data.mSupportType->SetCanDecrypt(data.mEMECodecType);
}
}
}
#else
# if defined(XP_WIN)
// Widevine CDM doesn't include an AAC decoder. So if WMF can't
// decode AAC, and a codec wasn't specified, be conservative
// and reject the MediaKeys request, since we assume Widevine
// will be used with AAC.
if (WMFDecoderModule::CanCreateMFTDecoder(WMFStreamType::AAC)) {
config->mMP4.SetCanDecrypt(EME_CODEC_AAC);
}
# else
config->mMP4.SetCanDecrypt(EME_CODEC_AAC);
# endif
config->mMP4.SetCanDecrypt(EME_CODEC_FLAC);
config->mMP4.SetCanDecrypt(EME_CODEC_OPUS);
config->mMP4.SetCanDecryptAndDecode(EME_CODEC_H264);
config->mMP4.SetCanDecryptAndDecode(EME_CODEC_VP9);
# ifdef MOZ_AV1
config->mMP4.SetCanDecryptAndDecode(EME_CODEC_AV1);
# endif
config->mWebM.SetCanDecrypt(EME_CODEC_VORBIS);
config->mWebM.SetCanDecrypt(EME_CODEC_OPUS);
config->mWebM.SetCanDecryptAndDecode(EME_CODEC_VP8);
config->mWebM.SetCanDecryptAndDecode(EME_CODEC_VP9);
# ifdef MOZ_AV1
config->mWebM.SetCanDecryptAndDecode(EME_CODEC_AV1);
# endif
#endif
}
/* static */
RefPtr<KeySystemConfig::SupportedConfigsPromise>
KeySystemConfig::CreateKeySystemConfigs(
const nsTArray<KeySystemConfigRequest>& aRequests) {
// Create available configs for all supported key systems in the request, but
// some of them might not be created immediately.
nsTArray<KeySystemConfig> outConfigs;
nsTArray<KeySystemConfigRequest> asyncRequests;
for (const auto& request : aRequests) {
const nsAString& keySystem = request.mKeySystem;
if (!Supports(keySystem)) {
continue;
}
if (IsClearkeyKeySystem(keySystem)) {
CreateClearKeyKeySystemConfigs(request, outConfigs);
} else if (IsWidevineKeySystem(keySystem)) {
CreateWivineL3KeySystemConfigs(request, outConfigs);
}
#ifdef MOZ_WMF_CDM
else if (IsPlayReadyKeySystemAndSupported(keySystem) ||
IsWidevineExperimentKeySystemAndSupported(keySystem)) {
asyncRequests.AppendElement(request);
}
#endif
}
#ifdef MOZ_WMF_CDM
if (!asyncRequests.IsEmpty()) {
RefPtr<SupportedConfigsPromise::Private> promise =
new SupportedConfigsPromise::Private(__func__);
RefPtr<WMFCDMCapabilites> cdm = new WMFCDMCapabilites();
cdm->GetCapabilities(asyncRequests)
->Then(GetMainThreadSerialEventTarget(), __func__,
[syncConfigs = std::move(outConfigs),
promise](SupportedConfigsPromise::ResolveOrRejectValue&&
aResult) mutable {
// Return the capabilities we already know
if (aResult.IsReject()) {
promise->Resolve(std::move(syncConfigs), __func__);
return;
}
// Merge sync results with async results
auto& asyncConfigs = aResult.ResolveValue();
asyncConfigs.AppendElements(std::move(syncConfigs));
promise->Resolve(std::move(asyncConfigs), __func__);
});
return promise;
}
#endif
return SupportedConfigsPromise::CreateAndResolve(std::move(outConfigs),
__func__);
}
/* static */
void KeySystemConfig::GetGMPKeySystemConfigs(dom::Promise* aPromise) {
MOZ_ASSERT(aPromise);
// Generate config requests
const nsTArray<nsString> keySystemNames{
NS_ConvertUTF8toUTF16(kClearKeyKeySystemName),
NS_ConvertUTF8toUTF16(kWidevineKeySystemName),
};
nsTArray<KeySystemConfigRequest> requests;
for (const auto& keySystem : keySystemNames) {
#ifdef MOZ_WMF_CDM
if (IsWMFClearKeySystemAndSupported(keySystem)) {
// Using wmf clearkey, not gmp clearkey.
continue;
}
#endif
requests.AppendElement(KeySystemConfigRequest{
keySystem, DecryptionInfo::Software, false /* IsPrivateBrowsing */});
}
// Get supported configs
KeySystemConfig::CreateKeySystemConfigs(requests)->Then(
GetMainThreadSerialEventTarget(), __func__,
[promise = RefPtr<dom::Promise>{aPromise}](
const SupportedConfigsPromise::ResolveOrRejectValue& aResult) {
if (aResult.IsResolve()) {
// Generate CDMInformation from configs
FallibleTArray<dom::CDMInformation> cdmInfo;
for (const auto& config : aResult.ResolveValue()) {
auto* info = cdmInfo.AppendElement(fallible);
if (!info) {
promise->MaybeReject(NS_ERROR_OUT_OF_MEMORY);
return;
}
info->mKeySystemName = config.mKeySystem;
info->mCapabilities = config.GetDebugInfo();
info->mClearlead = DoesKeySystemSupportClearLead(config.mKeySystem);
// TODO : ask real CDM
info->mIsHDCP22Compatible = false;
}
promise->MaybeResolve(cdmInfo);
} else {
promise->MaybeReject(NS_ERROR_DOM_MEDIA_CDM_ERR);
}
});
}
nsString KeySystemConfig::GetDebugInfo() const {
nsString debugInfo;
debugInfo.AppendLiteral(" key-system=");
debugInfo.Append(mKeySystem);
debugInfo.AppendLiteral(" init-data-type=[");
for (size_t idx = 0; idx < mInitDataTypes.Length(); idx++) {
debugInfo.Append(mInitDataTypes[idx]);
if (idx + 1 < mInitDataTypes.Length()) {
debugInfo.AppendLiteral(",");
}
}
debugInfo.AppendLiteral("]");
debugInfo.AppendASCII(
nsPrintfCString(" persistent=%s", RequirementToStr(mPersistentState))
.get());
debugInfo.AppendASCII(
nsPrintfCString(" distinctive=%s",
RequirementToStr(mDistinctiveIdentifier))
.get());
debugInfo.AppendLiteral(" sessionType=[");
for (size_t idx = 0; idx < mSessionTypes.Length(); idx++) {
debugInfo.AppendASCII(
nsPrintfCString("%s", SessionTypeToStr(mSessionTypes[idx])).get());
if (idx + 1 < mSessionTypes.Length()) {
debugInfo.AppendLiteral(",");
}
}
debugInfo.AppendLiteral("]");
debugInfo.AppendLiteral(" video-robustness=");
for (size_t idx = 0; idx < mVideoRobustness.Length(); idx++) {
debugInfo.Append(mVideoRobustness[idx]);
if (idx + 1 < mVideoRobustness.Length()) {
debugInfo.AppendLiteral(",");
}
}
debugInfo.AppendLiteral(" audio-robustness=");
for (size_t idx = 0; idx < mAudioRobustness.Length(); idx++) {
debugInfo.Append(mAudioRobustness[idx]);
if (idx + 1 < mAudioRobustness.Length()) {
debugInfo.AppendLiteral(",");
}
}
debugInfo.AppendLiteral(" scheme=[");
for (size_t idx = 0; idx < mEncryptionSchemes.Length(); idx++) {
debugInfo.Append(mEncryptionSchemes[idx]);
if (idx + 1 < mEncryptionSchemes.Length()) {
debugInfo.AppendLiteral(",");
}
}
debugInfo.AppendLiteral("]");
debugInfo.AppendLiteral(" MP4={");
debugInfo.Append(NS_ConvertUTF8toUTF16(mMP4.GetDebugInfo()));
debugInfo.AppendLiteral("}");
debugInfo.AppendLiteral(" WEBM={");
debugInfo.Append(NS_ConvertUTF8toUTF16(mWebM.GetDebugInfo()));
debugInfo.AppendLiteral("}");
debugInfo.AppendASCII(
nsPrintfCString(" isHDCP22Compatible=%d", mIsHDCP22Compatible));
return debugInfo;
}
KeySystemConfig::SessionType ConvertToKeySystemConfigSessionType(
dom::MediaKeySessionType aType) {
switch (aType) {
case dom::MediaKeySessionType::Temporary:
return KeySystemConfig::SessionType::Temporary;
case dom::MediaKeySessionType::Persistent_license:
return KeySystemConfig::SessionType::PersistentLicense;
default:
MOZ_ASSERT_UNREACHABLE("Invalid session type");
return KeySystemConfig::SessionType::Temporary;
}
}
const char* SessionTypeToStr(KeySystemConfig::SessionType aType) {
switch (aType) {
case KeySystemConfig::SessionType::Temporary:
return "Temporary";
case KeySystemConfig::SessionType::PersistentLicense:
return "PersistentLicense";
default:
MOZ_ASSERT_UNREACHABLE("Invalid session type");
return "Invalid";
}
}
const char* RequirementToStr(KeySystemConfig::Requirement aRequirement) {
switch (aRequirement) {
case KeySystemConfig::Requirement::Required:
return "required";
case KeySystemConfig::Requirement::Optional:
return "optional";
default:
return "not-allowed";
}
}
} // namespace mozilla