[ Merge of http://go/wvgerrit/208155 ] The MediaDrm plugin API getOfflineLicenseKeySetIds() was listing both L1 and L3 offline licenses. While this is generally acceptable, apps might force set L3 via the setStringProperty(), which should cause the DRM plugin to behave as if it is L3 only. This change will cause the WVDrmPlugin list L3 only if the app had set the security level to L3. Bug: 357863269 Bug: 372105842 Test: DRM Compliance ATP via ABTD Test: libwvdrmdrmplugin_hal_test on Oriole Change-Id: I1a6e10b7eb880eef4ba36ed31b12ebfe8617f002
2288 lines
81 KiB
C++
2288 lines
81 KiB
C++
//
|
|
// Copyright 2021 Google LLC. All Rights Reserved. This file and proprietary
|
|
// source code may only be used and distributed under the Widevine License
|
|
// Agreement.
|
|
//
|
|
|
|
// #define LOG_NDEBUG 0
|
|
#define LOG_TAG "WVCdm"
|
|
|
|
#include "WVDrmPlugin.h"
|
|
|
|
#include <aidl/android/hardware/drm/DrmMetric.h>
|
|
#include <android/binder_ibinder_platform.h>
|
|
#include <binder/IPCThreadState.h>
|
|
#include <stdlib.h>
|
|
#include <utils/Log.h>
|
|
|
|
#include <cstring>
|
|
#include <list>
|
|
#include <sstream>
|
|
#include <string>
|
|
#include <utility>
|
|
|
|
#include "Utils.h"
|
|
#include "android-base/macros.h"
|
|
#include "log.h"
|
|
#include "mapErrors-inl.h"
|
|
#include "media/stagefright/MediaErrors.h"
|
|
#include "openssl/sha.h"
|
|
#include "string_conversions.h"
|
|
#include "widevine_apex_info.h"
|
|
#include "wv_cdm_constants.h"
|
|
#include "wv_metrics.pb.h"
|
|
#include "wv_metrics_adapter.h"
|
|
|
|
namespace {
|
|
|
|
static const char* const kResetSecurityLevel = "";
|
|
static const char* const kEnable = "enable";
|
|
static const char* const kDisable = "disable";
|
|
static const std::string kPsshTag = "pssh";
|
|
static const char* const kSpecialUnprovisionResponse = "unprovision";
|
|
static const std::string kKeyAppPackageName = "application_name";
|
|
static const std::string kKeyOrigin = "origin";
|
|
|
|
} // namespace
|
|
|
|
namespace wvdrm {
|
|
namespace hardware {
|
|
namespace drm {
|
|
namespace widevine {
|
|
|
|
using std::shared_ptr;
|
|
using std::string;
|
|
using std::vector;
|
|
|
|
using ::aidl::android::hardware::drm::DrmMetric;
|
|
using ::aidl::android::hardware::drm::DrmMetricGroup;
|
|
using ::aidl::android::hardware::drm::DrmMetricNamedValue;
|
|
using ::aidl::android::hardware::drm::DrmMetricValue;
|
|
using ::aidl::android::hardware::drm::EventType;
|
|
using ::aidl::android::hardware::drm::HdcpLevel;
|
|
using ::aidl::android::hardware::drm::KeyRequestType;
|
|
using ::aidl::android::hardware::drm::KeySetId;
|
|
using ::aidl::android::hardware::drm::KeyStatus;
|
|
using ::aidl::android::hardware::drm::KeyStatusType;
|
|
using ::aidl::android::hardware::drm::KeyType;
|
|
using ::aidl::android::hardware::drm::KeyValue;
|
|
using ::aidl::android::hardware::drm::OfflineLicenseState;
|
|
using ::aidl::android::hardware::drm::SecureStop;
|
|
using ::aidl::android::hardware::drm::SecureStopId;
|
|
using ::aidl::android::hardware::drm::SecurityLevel;
|
|
using ::aidl::android::hardware::drm::Status;
|
|
|
|
using wvcdm::CdmAppParameterMap;
|
|
using wvcdm::CdmCertificateType;
|
|
using wvcdm::CdmInitData;
|
|
using wvcdm::CdmKeyRequest;
|
|
using wvcdm::CdmKeyRequestType;
|
|
using wvcdm::CdmKeyResponse;
|
|
using wvcdm::CdmKeySetId;
|
|
using wvcdm::CdmKeyStatus;
|
|
using wvcdm::CdmLicenseType;
|
|
using wvcdm::CdmProvisioningRequest;
|
|
using wvcdm::CdmProvisioningResponse;
|
|
using wvcdm::CdmQueryMap;
|
|
using wvcdm::CdmSecureStopId;
|
|
using wvcdm::CdmSecurityLevel;
|
|
using wvcdm::CdmUsageReport;
|
|
using wvcdm::CdmUsageReportList;
|
|
using wvcdm::kDefaultCdmIdentifier;
|
|
using wvcdm::KeyId;
|
|
using wvcdm::RequestedSecurityLevel;
|
|
|
|
namespace {
|
|
|
|
vector<uint8_t> StrToVector(const std::string& str) {
|
|
vector<uint8_t> vec(str.begin(), str.end());
|
|
return vec;
|
|
}
|
|
|
|
KeyRequestType ConvertFromCdmKeyRequestType(CdmKeyRequestType keyRequestType) {
|
|
switch (keyRequestType) {
|
|
case wvcdm::kKeyRequestTypeInitial:
|
|
return KeyRequestType::INITIAL;
|
|
case wvcdm::kKeyRequestTypeRenewal:
|
|
return KeyRequestType::RENEWAL;
|
|
case wvcdm::kKeyRequestTypeRelease:
|
|
return KeyRequestType::RELEASE;
|
|
case wvcdm::kKeyRequestTypeNone:
|
|
return KeyRequestType::NONE;
|
|
case wvcdm::kKeyRequestTypeUpdate:
|
|
return KeyRequestType::UPDATE;
|
|
default:
|
|
return KeyRequestType::UNKNOWN;
|
|
}
|
|
}
|
|
|
|
KeyStatusType ConvertFromCdmKeyStatus(CdmKeyStatus keyStatus) {
|
|
switch (keyStatus) {
|
|
case wvcdm::kKeyStatusUsable:
|
|
return KeyStatusType::USABLE;
|
|
case wvcdm::kKeyStatusExpired:
|
|
return KeyStatusType::EXPIRED;
|
|
case wvcdm::kKeyStatusOutputNotAllowed:
|
|
return KeyStatusType::OUTPUT_NOT_ALLOWED;
|
|
case wvcdm::kKeyStatusPending:
|
|
case wvcdm::kKeyStatusUsableInFuture:
|
|
return KeyStatusType::USABLE_IN_FUTURE;
|
|
case wvcdm::kKeyStatusInternalError:
|
|
default:
|
|
return KeyStatusType::INTERNAL_ERROR;
|
|
}
|
|
}
|
|
|
|
HdcpLevel mapHdcpLevel(const std::string& level) {
|
|
if (level == wvcdm::QUERY_VALUE_HDCP_V1_X ||
|
|
level == wvcdm::QUERY_VALUE_HDCP_V1_0 ||
|
|
level == wvcdm::QUERY_VALUE_HDCP_V1_1 ||
|
|
level == wvcdm::QUERY_VALUE_HDCP_V1_2 ||
|
|
level == wvcdm::QUERY_VALUE_HDCP_V1_3 ||
|
|
level == wvcdm::QUERY_VALUE_HDCP_V1_4)
|
|
return HdcpLevel::HDCP_V1;
|
|
else if (level == wvcdm::QUERY_VALUE_HDCP_V2_0)
|
|
return HdcpLevel::HDCP_V2;
|
|
else if (level == wvcdm::QUERY_VALUE_HDCP_V2_1)
|
|
return HdcpLevel::HDCP_V2_1;
|
|
else if (level == wvcdm::QUERY_VALUE_HDCP_V2_2)
|
|
return HdcpLevel::HDCP_V2_2;
|
|
else if (level == wvcdm::QUERY_VALUE_HDCP_V2_3)
|
|
return HdcpLevel::HDCP_V2_3;
|
|
else if (level == wvcdm::QUERY_VALUE_HDCP_NONE)
|
|
return HdcpLevel::HDCP_NONE;
|
|
else if (level == wvcdm::QUERY_VALUE_HDCP_NO_DIGITAL_OUTPUT)
|
|
return HdcpLevel::HDCP_NO_OUTPUT;
|
|
else {
|
|
ALOGE("Invalid HDCP level=%s", level.c_str());
|
|
return HdcpLevel::HDCP_NONE;
|
|
}
|
|
}
|
|
|
|
bool isCsrAccessAllowed() {
|
|
const uid_t AID_ROOT = 0;
|
|
const uid_t AID_SYSTEM = 1000;
|
|
const uid_t AID_SHELL = 2000;
|
|
const uid_t uid = AIBinder_getCallingUid();
|
|
return (uid == AID_ROOT || uid == AID_SYSTEM || uid == AID_SHELL);
|
|
}
|
|
|
|
bool IsAtscKeySetId(const CdmKeySetId& keySetId) {
|
|
if (keySetId.empty()) return false;
|
|
// Pre-installed licenses might not perfectly match ATSC_KEY_SET_ID_PREFIX.
|
|
// If "atsc" is in the license name, then it is safe to assume
|
|
// it is an ATSC license.
|
|
return keySetId.find("atsc") != std::string::npos ||
|
|
keySetId.find("ATSC") != std::string::npos;
|
|
}
|
|
|
|
bool IsNotAtscKeySetId(const CdmKeySetId& keySetId) {
|
|
return !IsAtscKeySetId(keySetId);
|
|
}
|
|
} // namespace
|
|
|
|
WVDrmPlugin::WVDrmPlugin(const android::sp<WvContentDecryptionModule>& cdm,
|
|
const std::string& appPackageName,
|
|
WVGenericCryptoInterface* crypto, bool useSpoid)
|
|
: mCdmIdentifierBuilder(useSpoid, *this, appPackageName),
|
|
mCDM(cdm),
|
|
mCrypto(crypto),
|
|
mAppPackageName(appPackageName) {
|
|
Terminator::Register(this);
|
|
}
|
|
|
|
WVDrmPlugin::~WVDrmPlugin() {
|
|
wvutil::SetLoggingUid(mCdmIdentifierBuilder.user_id());
|
|
Terminator::Forget(this);
|
|
Close();
|
|
}
|
|
|
|
void WVDrmPlugin::Close() {
|
|
const auto sessionKeys = mSessionInfoMap.getKeysAndClear();
|
|
for (const auto& sessionKey : sessionKeys) {
|
|
const CdmResponseType res = mCDM->CloseSession(sessionKey);
|
|
if (!isCdmResponseTypeSuccess(res)) {
|
|
ALOGE("Failed to close session while destroying WVDrmPlugin: sid = %s",
|
|
sessionKey.c_str());
|
|
}
|
|
}
|
|
|
|
if (mCdmIdentifierBuilder.is_sealed()) {
|
|
CdmIdentifier identifier;
|
|
auto status = mCdmIdentifierBuilder.getCdmIdentifier(&identifier);
|
|
if (status != Status::OK) {
|
|
ALOGE("Failed to get cdm identifier %d", status.get());
|
|
} else {
|
|
status = mapCdmResponseType(mCDM->CloseCdm(identifier));
|
|
if (status != Status::OK) {
|
|
ALOGE("Failed to close cdm. status %d", status.get());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
WvStatus WVDrmPlugin::openSessionCommon(vector<uint8_t>& sessionId) {
|
|
WvStatus status(Status::OK);
|
|
|
|
CdmIdentifier identifier;
|
|
status = mCdmIdentifierBuilder.getCdmIdentifier(&identifier);
|
|
if (status != Status::OK) {
|
|
return status;
|
|
}
|
|
|
|
CdmSessionId cdmSessionId;
|
|
CdmResponseType res = mCDM->OpenSession("com.widevine", &mPropertySet,
|
|
identifier, this, &cdmSessionId);
|
|
|
|
if (!isCdmResponseTypeSuccess(res)) {
|
|
return mapAndNotifyOfCdmResponseType(sessionId, res);
|
|
}
|
|
|
|
bool success = false;
|
|
|
|
// Construct a SessionInfo
|
|
CdmQueryMap info;
|
|
res = mCDM->QueryOemCryptoSessionId(cdmSessionId, &info);
|
|
|
|
if (isCdmResponseTypeSuccess(res) &&
|
|
info.count(wvcdm::QUERY_KEY_OEMCRYPTO_SESSION_ID)) {
|
|
const OEMCrypto_SESSION oecSessionId =
|
|
std::stoul(info[wvcdm::QUERY_KEY_OEMCRYPTO_SESSION_ID]);
|
|
mSessionInfoMap.insert(cdmSessionId, oecSessionId);
|
|
success = true;
|
|
} else {
|
|
ALOGE("Unable to query key control info.");
|
|
}
|
|
|
|
if (success) {
|
|
// Marshal Session ID
|
|
sessionId = StrToVector(cdmSessionId);
|
|
return WvStatus(Status::OK);
|
|
} else {
|
|
mCDM->CloseSession(cdmSessionId);
|
|
|
|
if (!isCdmResponseTypeSuccess(res)) {
|
|
// We got an error code we can return.
|
|
return mapAndNotifyOfCdmResponseType(sessionId, res);
|
|
} else {
|
|
// We got a failure that did not give us an error code, such as a failure
|
|
// of AttachEventListener() or the key being missing from the map.
|
|
ALOGW("Returns UNKNOWN error for legacy status kErrorCDMGeneric");
|
|
return WvStatus(Status::ERROR_DRM_UNKNOWN);
|
|
}
|
|
}
|
|
}
|
|
|
|
SecurityLevel WVDrmPlugin::mapSecurityLevel(const std::string& level) {
|
|
SecurityLevel securityLevel = SecurityLevel::UNKNOWN;
|
|
|
|
if (wvcdm::QUERY_VALUE_SECURITY_LEVEL_L1 == level) {
|
|
securityLevel = SecurityLevel::HW_SECURE_ALL;
|
|
} else if (wvcdm::QUERY_VALUE_SECURITY_LEVEL_L2 == level) {
|
|
securityLevel = SecurityLevel::HW_SECURE_CRYPTO;
|
|
} else if (wvcdm::QUERY_VALUE_SECURITY_LEVEL_L3 == level) {
|
|
securityLevel = SecurityLevel::SW_SECURE_CRYPTO;
|
|
} // else QUERY_VALUE_SECURITY_LEVEL_UNKNOWN returns Security::UNKNOWN
|
|
|
|
return securityLevel;
|
|
}
|
|
|
|
::ndk::ScopedAStatus WVDrmPlugin::openSession(SecurityLevel in_securityLevel,
|
|
vector<uint8_t>* _aidl_return) {
|
|
vector<uint8_t> sessionId;
|
|
if (SecurityLevel::DEFAULT == in_securityLevel) {
|
|
auto err = openSessionCommon(sessionId);
|
|
*_aidl_return = sessionId;
|
|
return toNdkScopedAStatus(err);
|
|
}
|
|
|
|
if (SecurityLevel::UNKNOWN == in_securityLevel) {
|
|
*_aidl_return = sessionId;
|
|
return toNdkScopedAStatus(Status::ERROR_DRM_CANNOT_HANDLE);
|
|
}
|
|
|
|
std::string native_security_level;
|
|
auto status =
|
|
queryProperty(wvcdm::kLevelDefault, wvcdm::QUERY_KEY_SECURITY_LEVEL,
|
|
native_security_level);
|
|
if (status != Status::OK) {
|
|
*_aidl_return = sessionId;
|
|
return toNdkScopedAStatus(status);
|
|
}
|
|
|
|
if (wvcdm::QUERY_VALUE_SECURITY_LEVEL_L3 == native_security_level &&
|
|
in_securityLevel >= SecurityLevel::SW_SECURE_DECODE) {
|
|
*_aidl_return = sessionId;
|
|
return toNdkScopedAStatus(Status::ERROR_DRM_CANNOT_HANDLE);
|
|
}
|
|
|
|
std::string wvcdm_security_level =
|
|
(SecurityLevel::SW_SECURE_CRYPTO == in_securityLevel)
|
|
? wvcdm::QUERY_VALUE_SECURITY_LEVEL_L3
|
|
: wvcdm::QUERY_VALUE_SECURITY_LEVEL_DEFAULT;
|
|
|
|
setPropertyString("securityLevel", std::string(wvcdm_security_level));
|
|
|
|
status = openSessionCommon(sessionId);
|
|
if (status == Status::OK) {
|
|
SecurityLevel currentSecurityLevel = SecurityLevel::UNKNOWN;
|
|
const auto ret = getSecurityLevel(sessionId, ¤tSecurityLevel);
|
|
if (!ret.isOk() || in_securityLevel != currentSecurityLevel) {
|
|
ALOGE("Failed to open session with the requested security level=%d",
|
|
in_securityLevel);
|
|
closeSession(sessionId);
|
|
sessionId.clear();
|
|
status = WvStatus(Status::ERROR_DRM_INVALID_STATE);
|
|
}
|
|
}
|
|
*_aidl_return = sessionId;
|
|
return toNdkScopedAStatus(status);
|
|
}
|
|
|
|
::ndk::ScopedAStatus WVDrmPlugin::closeSession(
|
|
const vector<uint8_t>& in_sessionId) {
|
|
if (!in_sessionId.size()) {
|
|
return toNdkScopedAStatus(Status::BAD_VALUE);
|
|
}
|
|
CdmSessionId cdmSessionId(in_sessionId.begin(), in_sessionId.end());
|
|
CdmResponseType res = mCDM->CloseSession(cdmSessionId);
|
|
mSessionInfoMap.erase(cdmSessionId);
|
|
if (!isCdmResponseTypeSuccess(res)) {
|
|
return toNdkScopedAStatus(Status::ERROR_DRM_SESSION_NOT_OPENED);
|
|
}
|
|
return toNdkScopedAStatus(Status::OK);
|
|
}
|
|
|
|
::ndk::ScopedAStatus WVDrmPlugin::getKeyRequest(
|
|
const vector<uint8_t>& in_scope, const vector<uint8_t>& in_initData,
|
|
const std::string& in_mimeType,
|
|
::aidl::android::hardware::drm::KeyType in_keyType,
|
|
const vector<::aidl::android::hardware::drm::KeyValue>&
|
|
in_optionalParameters,
|
|
::aidl::android::hardware::drm::KeyRequest* _aidl_return) {
|
|
if (!in_scope.size()) {
|
|
_aidl_return->request = vector<uint8_t>();
|
|
_aidl_return->requestType = KeyRequestType::UNKNOWN;
|
|
_aidl_return->defaultUrl = "";
|
|
return toNdkScopedAStatus(Status::BAD_VALUE);
|
|
}
|
|
KeyRequestType requestType = KeyRequestType::UNKNOWN;
|
|
WvStatus status(Status::OK);
|
|
std::string defaultUrl;
|
|
vector<uint8_t> request;
|
|
|
|
CdmIdentifier identifier;
|
|
status = mCdmIdentifierBuilder.getCdmIdentifier(&identifier);
|
|
if (status != Status::OK) {
|
|
_aidl_return->request = request;
|
|
_aidl_return->requestType = KeyRequestType::UNKNOWN;
|
|
_aidl_return->defaultUrl = "";
|
|
return toNdkScopedAStatus(status);
|
|
}
|
|
|
|
CdmLicenseType cdmLicenseType;
|
|
CdmSessionId cdmSessionId;
|
|
CdmKeySetId cdmKeySetId;
|
|
if (in_keyType == KeyType::OFFLINE) {
|
|
cdmLicenseType = wvcdm::kLicenseTypeOffline;
|
|
cdmSessionId.assign(in_scope.begin(), in_scope.end());
|
|
} else if (in_keyType == KeyType::STREAMING) {
|
|
cdmLicenseType = wvcdm::kLicenseTypeStreaming;
|
|
cdmSessionId.assign(in_scope.begin(), in_scope.end());
|
|
} else if (in_keyType == KeyType::RELEASE) {
|
|
cdmLicenseType = wvcdm::kLicenseTypeRelease;
|
|
cdmKeySetId.assign(in_scope.begin(), in_scope.end());
|
|
} else {
|
|
_aidl_return->request = request;
|
|
_aidl_return->requestType = KeyRequestType::UNKNOWN;
|
|
_aidl_return->defaultUrl = "";
|
|
return toNdkScopedAStatus(Status::BAD_VALUE);
|
|
}
|
|
|
|
std::string cdmInitDataType = in_mimeType;
|
|
// Provide backwards-compatibility for apps that pass non-EME-compatible
|
|
// MIME types.
|
|
if (!WvContentDecryptionModule::IsSupported(cdmInitDataType)) {
|
|
cdmInitDataType = wvcdm::ISO_BMFF_VIDEO_MIME_TYPE;
|
|
}
|
|
|
|
CdmInitData processedInitData;
|
|
if (in_initData.size() > 0 &&
|
|
WvContentDecryptionModule::IsCenc(cdmInitDataType) &&
|
|
!initDataResemblesPSSH(in_initData)) {
|
|
// This data was passed in the old format, pre-unwrapped. We need to wrap
|
|
// the init data in a new PSSH header.
|
|
static const uint8_t psshPrefix[] = {
|
|
0, 0, 0, 0, // Total size
|
|
'p', 's', 's', 'h', // "PSSH"
|
|
0, 0, 0, 0, // Flags - must be zero
|
|
0xED, 0xEF, 0x8B, 0xA9, 0x79, 0xD6, 0x4A, 0xCE, // Widevine UUID
|
|
0xA3, 0xC8, 0x27, 0xDC, 0xD5, 0x1D, 0x21, 0xED,
|
|
0, 0, 0, 0 // Size of initData
|
|
};
|
|
processedInitData.assign(reinterpret_cast<const char*>(psshPrefix),
|
|
sizeof(psshPrefix) / sizeof(uint8_t));
|
|
processedInitData.append(reinterpret_cast<const char*>(in_initData.data()),
|
|
in_initData.size());
|
|
const size_t kPsshBoxSizeLocation = 0;
|
|
const size_t kInitDataSizeLocation = sizeof(psshPrefix) - sizeof(uint32_t);
|
|
uint32_t psshBoxSize = htonl(processedInitData.size());
|
|
uint32_t initDataSize = htonl(in_initData.size());
|
|
memcpy(&processedInitData[kPsshBoxSizeLocation], &psshBoxSize,
|
|
sizeof(uint32_t));
|
|
memcpy(&processedInitData[kInitDataSizeLocation], &initDataSize,
|
|
sizeof(uint32_t));
|
|
} else {
|
|
// For other formats, we can pass the init data through unmodified.
|
|
processedInitData.assign(reinterpret_cast<const char*>(in_initData.data()),
|
|
in_initData.size());
|
|
}
|
|
|
|
CdmAppParameterMap cdmParameters;
|
|
for (size_t i = 0; i < in_optionalParameters.size(); ++i) {
|
|
const std::string& key(in_optionalParameters[i].key);
|
|
const std::string& value(in_optionalParameters[i].value);
|
|
|
|
std::string cdmKey(key.c_str(), key.size());
|
|
std::string cdmValue(value.c_str(), value.size());
|
|
|
|
cdmParameters[cdmKey] = cdmValue;
|
|
}
|
|
|
|
// Inserting additional client ID parameters here, this will appear
|
|
// in the license request.
|
|
// Note: This will overwrite user parameters of the same key.
|
|
cdmParameters[kKeyAppPackageName] = mAppPackageName;
|
|
cdmParameters[kKeyOrigin] = mCdmIdentifierBuilder.origin();
|
|
|
|
CdmKeyRequest keyRequest;
|
|
CdmResponseType res = mCDM->GenerateKeyRequest(
|
|
cdmSessionId, cdmKeySetId, cdmInitDataType, processedInitData,
|
|
cdmLicenseType, cdmParameters, &mPropertySet, identifier, &keyRequest);
|
|
|
|
requestType = ConvertFromCdmKeyRequestType(keyRequest.type);
|
|
|
|
if (isCdmResponseTypeSuccess(res)) {
|
|
defaultUrl.clear();
|
|
defaultUrl.assign(keyRequest.url.data(), keyRequest.url.size());
|
|
|
|
request = StrToVector(keyRequest.message);
|
|
}
|
|
|
|
if (in_keyType == KeyType::RELEASE) {
|
|
// When releasing keys, we do not have a session ID.
|
|
status = mapCdmResponseType(res);
|
|
} else {
|
|
// For all other requests, we have a session ID.
|
|
status = mapAndNotifyOfCdmResponseType(in_scope, res);
|
|
}
|
|
_aidl_return->request = request;
|
|
_aidl_return->requestType = requestType;
|
|
_aidl_return->defaultUrl = defaultUrl;
|
|
return toNdkScopedAStatus(status);
|
|
}
|
|
|
|
::ndk::ScopedAStatus WVDrmPlugin::provideKeyResponse(
|
|
const vector<uint8_t>& in_scope, const vector<uint8_t>& in_response,
|
|
::aidl::android::hardware::drm::KeySetId* _aidl_return) {
|
|
if (in_scope.size() == 0 || in_response.size() == 0) {
|
|
*_aidl_return = {};
|
|
return toNdkScopedAStatus(Status::BAD_VALUE);
|
|
}
|
|
|
|
CdmKeySetId cdmKeySetId;
|
|
CdmSessionId cdmSessionId;
|
|
CdmKeyResponse cdmResponse(in_response.begin(), in_response.end());
|
|
|
|
bool isRequest = (memcmp(in_scope.data(), wvcdm::SESSION_ID_PREFIX,
|
|
sizeof(wvcdm::SESSION_ID_PREFIX) - 1) == 0);
|
|
bool isRelease = (memcmp(in_scope.data(), wvcdm::KEY_SET_ID_PREFIX,
|
|
sizeof(wvcdm::KEY_SET_ID_PREFIX) - 1) == 0);
|
|
|
|
vector<uint8_t> keySetId;
|
|
|
|
if (isRequest) {
|
|
cdmSessionId.assign(in_scope.begin(), in_scope.end());
|
|
} else if (isRelease) {
|
|
cdmKeySetId.assign(in_scope.begin(), in_scope.end());
|
|
} else {
|
|
_aidl_return->keySetId = keySetId;
|
|
return toNdkScopedAStatus(Status::ERROR_DRM_CANNOT_HANDLE);
|
|
}
|
|
|
|
CdmResponseType res = mCDM->AddKey(cdmSessionId, cdmResponse, &cdmKeySetId);
|
|
|
|
if (isRequest && isCdmResponseTypeSuccess(res)) {
|
|
keySetId = StrToVector(cdmKeySetId);
|
|
}
|
|
|
|
WvStatus status(Status::OK);
|
|
if (isRelease) {
|
|
// When releasing keys, we do not have a session ID.
|
|
status = mapCdmResponseType(res);
|
|
} else {
|
|
// For all other requests, we have a session ID.
|
|
status = mapAndNotifyOfCdmResponseType(in_scope, res);
|
|
// For "NEED_KEY," we still want to send the notification, but then we
|
|
// don't return the error. This is because "NEED_KEY" from AddKey() is an
|
|
// expected behavior when sending a privacy certificate.
|
|
if (res == wvcdm::NEED_KEY && mPropertySet.use_privacy_mode()) {
|
|
status = WvStatus(Status::OK);
|
|
}
|
|
}
|
|
_aidl_return->keySetId = keySetId;
|
|
return toNdkScopedAStatus(status);
|
|
}
|
|
|
|
::ndk::ScopedAStatus WVDrmPlugin::removeKeys(
|
|
const vector<uint8_t>& in_sessionId) {
|
|
if (!in_sessionId.size()) {
|
|
return toNdkScopedAStatus(Status::BAD_VALUE);
|
|
}
|
|
CdmSessionId cdmSessionId(in_sessionId.begin(), in_sessionId.end());
|
|
|
|
CdmResponseType res = mCDM->RemoveKeys(cdmSessionId);
|
|
|
|
return toNdkScopedAStatus(mapAndNotifyOfCdmResponseType(in_sessionId, res));
|
|
}
|
|
|
|
::ndk::ScopedAStatus WVDrmPlugin::restoreKeys(
|
|
const vector<uint8_t>& in_sessionId,
|
|
const ::aidl::android::hardware::drm::KeySetId& in_keySetId) {
|
|
if (!in_sessionId.size() || !in_keySetId.keySetId.size()) {
|
|
return toNdkScopedAStatus(Status::BAD_VALUE);
|
|
}
|
|
CdmSessionId cdmSessionId(in_sessionId.begin(), in_sessionId.end());
|
|
CdmKeySetId cdmKeySetId(in_keySetId.keySetId.begin(),
|
|
in_keySetId.keySetId.end());
|
|
|
|
CdmResponseType res = mCDM->RestoreKey(cdmSessionId, cdmKeySetId);
|
|
|
|
return toNdkScopedAStatus(mapAndNotifyOfCdmResponseType(in_sessionId, res));
|
|
}
|
|
|
|
::ndk::ScopedAStatus WVDrmPlugin::queryKeyStatus(
|
|
const vector<uint8_t>& in_sessionId,
|
|
vector<::aidl::android::hardware::drm::KeyValue>* _aidl_return) {
|
|
if (in_sessionId.size() == 0) {
|
|
*_aidl_return = vector<KeyValue>();
|
|
return toNdkScopedAStatus(Status::BAD_VALUE);
|
|
}
|
|
CdmSessionId cdmSessionId(in_sessionId.begin(), in_sessionId.end());
|
|
CdmQueryMap cdmLicenseInfo;
|
|
|
|
CdmResponseType res = mCDM->QueryKeyStatus(cdmSessionId, &cdmLicenseInfo);
|
|
|
|
vector<KeyValue> infoMapVec;
|
|
if (isCdmResponseTypeSuccess(res)) {
|
|
infoMapVec.clear();
|
|
|
|
KeyValue keyValuePair;
|
|
for (CdmQueryMap::const_iterator iter = cdmLicenseInfo.begin();
|
|
iter != cdmLicenseInfo.end(); ++iter) {
|
|
const std::string& cdmKey = iter->first;
|
|
const std::string& cdmValue = iter->second;
|
|
keyValuePair.key = std::string(cdmKey.data(), cdmKey.size());
|
|
keyValuePair.value = std::string(cdmValue.data(), cdmValue.size());
|
|
infoMapVec.push_back(keyValuePair);
|
|
}
|
|
}
|
|
|
|
*_aidl_return = infoMapVec;
|
|
return toNdkScopedAStatus(mapCdmResponseType(res));
|
|
}
|
|
|
|
::ndk::ScopedAStatus WVDrmPlugin::getProvisionRequest(
|
|
const std::string& in_certificateType,
|
|
const std::string& in_certificateAuthority,
|
|
::aidl::android::hardware::drm::ProvisionRequest* _aidl_return) {
|
|
WvStatus status(Status::OK);
|
|
std::string defaultUrl;
|
|
vector<uint8_t> request;
|
|
|
|
if (mPropertySet.use_atsc_mode()) {
|
|
_aidl_return->defaultUrl = defaultUrl;
|
|
_aidl_return->request = request;
|
|
return toNdkScopedAStatus(
|
|
mapCdmResponseType(wvcdm::PROVISIONING_NOT_ALLOWED_FOR_ATSC));
|
|
}
|
|
|
|
CdmIdentifier identifier;
|
|
status = mCdmIdentifierBuilder.getCdmIdentifier(&identifier);
|
|
if (status != Status::OK) {
|
|
_aidl_return->defaultUrl = defaultUrl;
|
|
_aidl_return->request = request;
|
|
return toNdkScopedAStatus(status);
|
|
}
|
|
|
|
CdmProvisioningRequest cdmProvisionRequest;
|
|
std::string cdmDefaultUrl;
|
|
|
|
CdmCertificateType cdmCertType = wvcdm::kCertificateWidevine;
|
|
if (in_certificateType == "X.509") {
|
|
cdmCertType = wvcdm::kCertificateX509;
|
|
}
|
|
|
|
std::string cdmCertAuthority = in_certificateAuthority;
|
|
|
|
CdmResponseType res = mCDM->GetProvisioningRequest(
|
|
cdmCertType, cdmCertAuthority, identifier,
|
|
mProvisioningServiceCertificate, getRequestedSecurityLevel(),
|
|
&cdmProvisionRequest, &cdmDefaultUrl);
|
|
if (isCdmResponseTypeSuccess(res)) {
|
|
request = StrToVector(cdmProvisionRequest);
|
|
defaultUrl.clear();
|
|
defaultUrl.assign(cdmDefaultUrl.data(), cdmDefaultUrl.size());
|
|
}
|
|
|
|
_aidl_return->defaultUrl = defaultUrl;
|
|
_aidl_return->request = request;
|
|
return toNdkScopedAStatus(mapCdmResponseType(res));
|
|
}
|
|
|
|
::ndk::ScopedAStatus WVDrmPlugin::provideProvisionResponse(
|
|
const vector<uint8_t>& in_response,
|
|
::aidl::android::hardware::drm::ProvideProvisionResponseResult*
|
|
_aidl_return) {
|
|
if (!in_response.size()) {
|
|
_aidl_return->certificate = vector<uint8_t>();
|
|
_aidl_return->wrappedKey = vector<uint8_t>();
|
|
return toNdkScopedAStatus(Status::BAD_VALUE);
|
|
}
|
|
|
|
vector<uint8_t> certificate;
|
|
vector<uint8_t> wrappedKey;
|
|
|
|
CdmIdentifier identifier;
|
|
auto status = mCdmIdentifierBuilder.getCdmIdentifier(&identifier);
|
|
if (status != Status::OK) {
|
|
_aidl_return->certificate = certificate;
|
|
_aidl_return->wrappedKey = wrappedKey;
|
|
return toNdkScopedAStatus(status);
|
|
}
|
|
|
|
CdmProvisioningResponse cdmResponse(in_response.begin(), in_response.end());
|
|
if (cdmResponse == kSpecialUnprovisionResponse) {
|
|
if (identifier.IsEquivalentToDefault()) {
|
|
ALOGW("Returns UNKNOWN error for legacy status kErrorNoOriginSpecified");
|
|
_aidl_return->certificate = certificate;
|
|
_aidl_return->wrappedKey = wrappedKey;
|
|
return toNdkScopedAStatus(Status::ERROR_DRM_UNKNOWN);
|
|
}
|
|
_aidl_return->certificate = certificate;
|
|
_aidl_return->wrappedKey = wrappedKey;
|
|
return toNdkScopedAStatus(unprovision(identifier));
|
|
} else {
|
|
std::string cdmCertificate;
|
|
std::string cdmWrappedKey;
|
|
CdmResponseType res = mCDM->HandleProvisioningResponse(
|
|
identifier, cdmResponse, getRequestedSecurityLevel(), &cdmCertificate,
|
|
&cdmWrappedKey);
|
|
if (isCdmResponseTypeSuccess(res)) {
|
|
certificate = StrToVector(cdmCertificate);
|
|
wrappedKey = StrToVector(cdmWrappedKey);
|
|
}
|
|
_aidl_return->certificate = certificate;
|
|
_aidl_return->wrappedKey = wrappedKey;
|
|
return toNdkScopedAStatus(mapCdmResponseType(res));
|
|
}
|
|
}
|
|
|
|
Status WVDrmPlugin::unprovisionDevice() {
|
|
return unprovision(kDefaultCdmIdentifier).get();
|
|
}
|
|
|
|
::ndk::ScopedAStatus WVDrmPlugin::getSecureStop(
|
|
const ::aidl::android::hardware::drm::SecureStopId& in_secureStopId,
|
|
::aidl::android::hardware::drm::SecureStop* _aidl_return) {
|
|
*_aidl_return = SecureStop();
|
|
if (in_secureStopId.secureStopId.empty()) {
|
|
return toNdkScopedAStatus(Status::BAD_VALUE);
|
|
}
|
|
|
|
CdmIdentifier identifier;
|
|
auto status = mCdmIdentifierBuilder.getCdmIdentifier(&identifier);
|
|
if (status != Status::OK) {
|
|
return toNdkScopedAStatus(status);
|
|
}
|
|
|
|
const CdmSecureStopId cdmSsId(in_secureStopId.secureStopId.begin(),
|
|
in_secureStopId.secureStopId.end());
|
|
CdmUsageReport cdmUsageReport;
|
|
CdmResponseType res = mCDM->GetUsageInfo(mPropertySet.app_id(), cdmSsId,
|
|
identifier, &cdmUsageReport);
|
|
|
|
SecureStop secureStop;
|
|
if (isCdmResponseTypeSuccess(res)) {
|
|
secureStop.opaqueData = StrToVector(cdmUsageReport);
|
|
*_aidl_return = std::move(secureStop);
|
|
}
|
|
|
|
return toNdkScopedAStatus(mapCdmResponseType(res));
|
|
}
|
|
|
|
::ndk::ScopedAStatus WVDrmPlugin::getSecureStops(
|
|
vector<::aidl::android::hardware::drm::SecureStop>* _aidl_return) {
|
|
_aidl_return->clear();
|
|
CdmIdentifier identifier;
|
|
auto status = mCdmIdentifierBuilder.getCdmIdentifier(&identifier);
|
|
if (status != Status::OK) {
|
|
return toNdkScopedAStatus(status);
|
|
}
|
|
|
|
CdmUsageReportList cdmUsageReports;
|
|
const CdmResponseType res =
|
|
mCDM->GetUsageInfo(mPropertySet.app_id(), identifier, &cdmUsageReports);
|
|
|
|
vector<SecureStop> secureStops;
|
|
if (isCdmResponseTypeSuccess(res)) {
|
|
for (const CdmUsageReport& cdmUsageReport : cdmUsageReports) {
|
|
SecureStop secureStop;
|
|
secureStop.opaqueData = StrToVector(cdmUsageReport);
|
|
secureStops.push_back(std::move(secureStop));
|
|
}
|
|
}
|
|
*_aidl_return = std::move(secureStops);
|
|
|
|
return toNdkScopedAStatus(mapCdmResponseType(res));
|
|
}
|
|
|
|
::ndk::ScopedAStatus WVDrmPlugin::releaseAllSecureStops() {
|
|
const auto res = removeAllSecureStops();
|
|
Status status = Status::OK;
|
|
if (!res.isOk() && res.getExceptionCode() == EX_SERVICE_SPECIFIC) {
|
|
status = static_cast<Status>(res.getServiceSpecificError());
|
|
}
|
|
return toNdkScopedAStatus(status);
|
|
}
|
|
|
|
::ndk::ScopedAStatus WVDrmPlugin::releaseSecureStop(
|
|
const ::aidl::android::hardware::drm::SecureStopId& in_secureStopId) {
|
|
if (!in_secureStopId.secureStopId.size()) {
|
|
return toNdkScopedAStatus(Status::BAD_VALUE);
|
|
}
|
|
|
|
CdmIdentifier identifier;
|
|
auto status = mCdmIdentifierBuilder.getCdmIdentifier(&identifier);
|
|
if (status != Status::OK) {
|
|
return toNdkScopedAStatus(status);
|
|
}
|
|
|
|
const CdmKeyResponse cdmMessage(in_secureStopId.secureStopId.begin(),
|
|
in_secureStopId.secureStopId.end());
|
|
CdmResponseType res = mCDM->ReleaseUsageInfo(cdmMessage, identifier);
|
|
return toNdkScopedAStatus(mapCdmResponseType(res));
|
|
}
|
|
|
|
::ndk::ScopedAStatus WVDrmPlugin::getMetrics(
|
|
vector<::aidl::android::hardware::drm::DrmMetricGroup>* _aidl_return) {
|
|
_aidl_return->clear();
|
|
CdmIdentifier identifier;
|
|
auto status = mCdmIdentifierBuilder.getCdmIdentifier(&identifier);
|
|
if (status != Status::OK) {
|
|
return toNdkScopedAStatus(status);
|
|
}
|
|
|
|
drm_metrics::WvCdmMetrics proto_metrics;
|
|
const CdmResponseType result =
|
|
mCDM->GetCurrentMetrics(identifier, &proto_metrics);
|
|
if (result != wvcdm::NO_ERROR) {
|
|
return toNdkScopedAStatus(mapCdmResponseType(result));
|
|
}
|
|
|
|
vector<DrmMetricGroup> wvMetrics;
|
|
::wvcdm::WvMetricsAdapter adapter;
|
|
::wvcdm::WvMetricsAdapter::ToWvMetrics(proto_metrics, &wvMetrics);
|
|
*_aidl_return = std::move(wvMetrics);
|
|
return toNdkScopedAStatus(Status::OK);
|
|
}
|
|
|
|
::ndk::ScopedAStatus WVDrmPlugin::getSecureStopIds(
|
|
vector<::aidl::android::hardware::drm::SecureStopId>* _aidl_return) {
|
|
vector<SecureStopId> secureStopIds;
|
|
|
|
CdmIdentifier identifier;
|
|
auto status = mCdmIdentifierBuilder.getCdmIdentifier(&identifier);
|
|
if (status != Status::OK) {
|
|
*_aidl_return = secureStopIds;
|
|
return toNdkScopedAStatus(status);
|
|
}
|
|
|
|
vector<CdmSecureStopId> ssids;
|
|
CdmResponseType res =
|
|
mCDM->GetSecureStopIds(mPropertySet.app_id(), identifier, &ssids);
|
|
|
|
if (isCdmResponseTypeSuccess(res)) {
|
|
for (auto itr = ssids.begin(); itr != ssids.end(); ++itr) {
|
|
const CdmSecureStopId& cdmSsid = *itr;
|
|
SecureStopId ssid;
|
|
ssid.secureStopId = StrToVector(cdmSsid);
|
|
secureStopIds.push_back(ssid);
|
|
}
|
|
}
|
|
|
|
*_aidl_return = secureStopIds;
|
|
return toNdkScopedAStatus(mapCdmResponseType(res));
|
|
}
|
|
|
|
::ndk::ScopedAStatus WVDrmPlugin::releaseSecureStops(
|
|
const ::aidl::android::hardware::drm::OpaqueData& in_ssRelease) {
|
|
if (in_ssRelease.opaqueData.size() == 0) {
|
|
return toNdkScopedAStatus(Status::BAD_VALUE);
|
|
}
|
|
|
|
CdmIdentifier identifier;
|
|
auto status = mCdmIdentifierBuilder.getCdmIdentifier(&identifier);
|
|
if (status != Status::OK) {
|
|
return toNdkScopedAStatus(status);
|
|
}
|
|
|
|
const vector<uint8_t>& data = in_ssRelease.opaqueData;
|
|
const CdmKeyResponse cdmMessage(data.begin(), data.end());
|
|
// Only releases a single secure stop.
|
|
const CdmResponseType res = mCDM->ReleaseUsageInfo(cdmMessage, identifier);
|
|
return toNdkScopedAStatus(mapCdmResponseType(res));
|
|
}
|
|
|
|
::ndk::ScopedAStatus WVDrmPlugin::removeSecureStop(
|
|
const ::aidl::android::hardware::drm::SecureStopId& in_secureStopId) {
|
|
if (!in_secureStopId.secureStopId.size()) {
|
|
return toNdkScopedAStatus(Status::BAD_VALUE);
|
|
}
|
|
|
|
CdmIdentifier identifier;
|
|
auto status = mCdmIdentifierBuilder.getCdmIdentifier(&identifier);
|
|
if (status != Status::OK) {
|
|
return toNdkScopedAStatus(status);
|
|
}
|
|
|
|
const CdmSecureStopId ssid(in_secureStopId.secureStopId.begin(),
|
|
in_secureStopId.secureStopId.end());
|
|
const CdmResponseType res =
|
|
mCDM->RemoveUsageInfo(mPropertySet.app_id(), identifier, ssid);
|
|
return toNdkScopedAStatus(mapCdmResponseType(res));
|
|
}
|
|
|
|
::ndk::ScopedAStatus WVDrmPlugin::removeAllSecureStops() {
|
|
CdmIdentifier identifier;
|
|
auto status = mCdmIdentifierBuilder.getCdmIdentifier(&identifier);
|
|
if (status != Status::OK) {
|
|
return toNdkScopedAStatus(status);
|
|
}
|
|
|
|
const CdmResponseType res =
|
|
mCDM->RemoveAllUsageInfo(mPropertySet.app_id(), identifier);
|
|
return toNdkScopedAStatus(mapCdmResponseType(res));
|
|
}
|
|
|
|
::ndk::ScopedAStatus WVDrmPlugin::getHdcpLevels(
|
|
::aidl::android::hardware::drm::HdcpLevels* _aidl_return) {
|
|
HdcpLevel connectedLevel = HdcpLevel::HDCP_NONE;
|
|
HdcpLevel maxLevel = HdcpLevel::HDCP_NO_OUTPUT;
|
|
|
|
std::string level;
|
|
auto status = queryProperty(wvcdm::QUERY_KEY_CURRENT_HDCP_LEVEL, level);
|
|
if (status == Status::OK) {
|
|
connectedLevel = mapHdcpLevel(level);
|
|
} else {
|
|
ALOGE("Failed to query current hdcp level.");
|
|
_aidl_return->connectedLevel = connectedLevel;
|
|
_aidl_return->maxLevel = maxLevel;
|
|
return toNdkScopedAStatus(Status::ERROR_DRM_INVALID_STATE);
|
|
}
|
|
|
|
status = queryProperty(wvcdm::QUERY_KEY_MAX_HDCP_LEVEL, level);
|
|
if (status == Status::OK) {
|
|
maxLevel = mapHdcpLevel(level);
|
|
} else {
|
|
ALOGE("Failed to query maximum hdcp level.");
|
|
_aidl_return->connectedLevel = connectedLevel;
|
|
_aidl_return->maxLevel = maxLevel;
|
|
return toNdkScopedAStatus(Status::ERROR_DRM_INVALID_STATE);
|
|
}
|
|
|
|
_aidl_return->connectedLevel = connectedLevel;
|
|
_aidl_return->maxLevel = maxLevel;
|
|
return toNdkScopedAStatus(Status::OK);
|
|
}
|
|
|
|
::ndk::ScopedAStatus WVDrmPlugin::getNumberOfSessions(
|
|
::aidl::android::hardware::drm::NumberOfSessions* _aidl_return) {
|
|
uint32_t currentSessions = 0;
|
|
uint32_t maxSessions = 1;
|
|
|
|
std::string value;
|
|
auto status = queryProperty(wvcdm::QUERY_KEY_NUMBER_OF_OPEN_SESSIONS, value);
|
|
if (status == Status::OK) {
|
|
currentSessions = std::strtoul(value.c_str(), nullptr, 10);
|
|
} else {
|
|
ALOGE("Failed to query currently opened sessions.");
|
|
_aidl_return->currentSessions = currentSessions;
|
|
_aidl_return->maxSessions = maxSessions;
|
|
return toNdkScopedAStatus(Status::ERROR_DRM_INVALID_STATE);
|
|
}
|
|
|
|
status = queryProperty(wvcdm::QUERY_KEY_MAX_NUMBER_OF_SESSIONS, value);
|
|
if (status == Status::OK) {
|
|
maxSessions = std::strtoul(value.c_str(), nullptr, 10);
|
|
} else {
|
|
ALOGE(
|
|
"Failed to query maximum number of sessions that the device can "
|
|
"support.");
|
|
_aidl_return->currentSessions = currentSessions;
|
|
_aidl_return->maxSessions = maxSessions;
|
|
return toNdkScopedAStatus(Status::ERROR_DRM_INVALID_STATE);
|
|
}
|
|
|
|
_aidl_return->currentSessions = currentSessions;
|
|
_aidl_return->maxSessions = maxSessions;
|
|
return toNdkScopedAStatus(Status::OK);
|
|
}
|
|
|
|
::ndk::ScopedAStatus WVDrmPlugin::getSecurityLevel(
|
|
const vector<uint8_t>& in_sessionId,
|
|
::aidl::android::hardware::drm::SecurityLevel* _aidl_return) {
|
|
if (in_sessionId.size() == 0) {
|
|
*_aidl_return = SecurityLevel::UNKNOWN;
|
|
return toNdkScopedAStatus(Status::BAD_VALUE);
|
|
}
|
|
|
|
CdmQueryMap info;
|
|
SecurityLevel securityLevel = SecurityLevel::UNKNOWN;
|
|
|
|
CdmResponseType status = mCDM->QuerySessionStatus(
|
|
std::string(in_sessionId.begin(), in_sessionId.end()), &info);
|
|
if (wvcdm::NO_ERROR == status) {
|
|
std::string level = info[wvcdm::QUERY_KEY_SECURITY_LEVEL];
|
|
securityLevel = mapSecurityLevel(level);
|
|
} else {
|
|
ALOGE("Failed to query security level, status=%d",
|
|
static_cast<int>(status));
|
|
}
|
|
|
|
*_aidl_return = securityLevel;
|
|
return toNdkScopedAStatus(mapCdmResponseType(status));
|
|
}
|
|
|
|
::ndk::ScopedAStatus WVDrmPlugin::getOfflineLicenseKeySetIds(
|
|
vector<::aidl::android::hardware::drm::KeySetId>* keySetIds) {
|
|
keySetIds->clear();
|
|
CdmIdentifier identifier;
|
|
const auto status = mCdmIdentifierBuilder.getCdmIdentifier(&identifier);
|
|
if (status != Status::OK) {
|
|
return toNdkScopedAStatus(status);
|
|
}
|
|
|
|
std::vector<CdmSecurityLevel> levelsToList;
|
|
if (mPropertySet.security_level() != wvcdm::QUERY_VALUE_SECURITY_LEVEL_L3) {
|
|
// Do not list L1 offline licenses if the DRM plugin is in
|
|
// L3-only mode.
|
|
levelsToList.push_back(wvcdm::kSecurityLevelL1);
|
|
}
|
|
// Always list L3, as "default" may imply either.
|
|
levelsToList.push_back(wvcdm::kSecurityLevelL3);
|
|
|
|
std::vector<CdmKeySetId> allKeySetIds;
|
|
CdmResponseType res(wvcdm::UNKNOWN_ERROR);
|
|
bool success = false;
|
|
for (const auto& level : levelsToList) {
|
|
std::vector<CdmKeySetId> levelKeySetIds;
|
|
res = mCDM->ListStoredLicenses(level, identifier, &levelKeySetIds);
|
|
|
|
if (!isCdmResponseTypeSuccess(res)) continue;
|
|
success = true;
|
|
if (levelKeySetIds.empty()) continue;
|
|
if (allKeySetIds.empty()) {
|
|
allKeySetIds = std::move(levelKeySetIds);
|
|
} else {
|
|
allKeySetIds.reserve(allKeySetIds.size() + levelKeySetIds.size());
|
|
for (CdmKeySetId& keySetId : levelKeySetIds) {
|
|
allKeySetIds.push_back(std::move(keySetId));
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!success) {
|
|
// Return whatever the last error was.
|
|
return toNdkScopedAStatus(mapCdmResponseType(res));
|
|
}
|
|
|
|
// Filter out key sets based on ATSC mode.
|
|
const auto isAllowedKeySetId =
|
|
mPropertySet.use_atsc_mode() ? IsAtscKeySetId : IsNotAtscKeySetId;
|
|
keySetIds->reserve(allKeySetIds.size());
|
|
for (const CdmKeySetId& keySetId : allKeySetIds) {
|
|
if (isAllowedKeySetId(keySetId)) {
|
|
keySetIds->push_back(KeySetId{StrToVector(keySetId)});
|
|
}
|
|
}
|
|
return ::ndk::ScopedAStatus::ok();
|
|
}
|
|
|
|
::ndk::ScopedAStatus WVDrmPlugin::getOfflineLicenseState(
|
|
const ::aidl::android::hardware::drm::KeySetId& in_keySetId,
|
|
::aidl::android::hardware::drm::OfflineLicenseState* _aidl_return) {
|
|
OfflineLicenseState licenseState = OfflineLicenseState::UNKNOWN;
|
|
|
|
if (!in_keySetId.keySetId.size()) {
|
|
*_aidl_return = licenseState;
|
|
return toNdkScopedAStatus(Status::BAD_VALUE);
|
|
}
|
|
|
|
CdmIdentifier identifier;
|
|
auto status = mCdmIdentifierBuilder.getCdmIdentifier(&identifier);
|
|
if (status != Status::OK) {
|
|
*_aidl_return = licenseState;
|
|
return toNdkScopedAStatus(status);
|
|
}
|
|
|
|
CdmResponseType res = wvcdm::CdmResponseType(wvcdm::UNKNOWN_ERROR);
|
|
CdmKeySetId keySetIdStr(in_keySetId.keySetId.begin(),
|
|
in_keySetId.keySetId.end());
|
|
|
|
wvcdm::CdmOfflineLicenseState state = wvcdm::kLicenseStateUnknown;
|
|
res = mCDM->GetOfflineLicenseState(keySetIdStr, wvcdm::kSecurityLevelL1,
|
|
identifier, &state);
|
|
if (!isCdmResponseTypeSuccess(res)) {
|
|
// try L3
|
|
res = mCDM->GetOfflineLicenseState(keySetIdStr, wvcdm::kSecurityLevelL3,
|
|
identifier, &state);
|
|
if (!isCdmResponseTypeSuccess(res)) {
|
|
*_aidl_return = licenseState;
|
|
return toNdkScopedAStatus(Status::BAD_VALUE);
|
|
}
|
|
}
|
|
|
|
switch (state) {
|
|
case wvcdm::kLicenseStateActive:
|
|
licenseState = OfflineLicenseState::USABLE;
|
|
break;
|
|
case wvcdm::kLicenseStateReleasing:
|
|
licenseState = OfflineLicenseState::INACTIVE;
|
|
break;
|
|
default:
|
|
licenseState = OfflineLicenseState::UNKNOWN;
|
|
ALOGE("Return unknown offline license state for %s", keySetIdStr.c_str());
|
|
break;
|
|
}
|
|
|
|
*_aidl_return = licenseState;
|
|
return toNdkScopedAStatus(mapCdmResponseType(res));
|
|
}
|
|
|
|
::ndk::ScopedAStatus WVDrmPlugin::removeOfflineLicense(
|
|
const ::aidl::android::hardware::drm::KeySetId& in_keySetId) {
|
|
if (in_keySetId.keySetId.empty()) {
|
|
return toNdkScopedAStatus(Status::BAD_VALUE);
|
|
}
|
|
|
|
CdmIdentifier identifier;
|
|
const auto status = mCdmIdentifierBuilder.getCdmIdentifier(&identifier);
|
|
if (status != Status::OK) {
|
|
return toNdkScopedAStatus(status);
|
|
}
|
|
|
|
const std::vector<CdmSecurityLevel> levels = {wvcdm::kSecurityLevelL1,
|
|
wvcdm::kSecurityLevelL3};
|
|
const CdmKeySetId cdmKeySetId(in_keySetId.keySetId.begin(),
|
|
in_keySetId.keySetId.end());
|
|
|
|
for (const CdmSecurityLevel level : levels) {
|
|
std::vector<CdmKeySetId> keySetIds;
|
|
const CdmResponseType res =
|
|
mCDM->ListStoredLicenses(level, identifier, &keySetIds);
|
|
if (!isCdmResponseTypeSuccess(res)) {
|
|
// This could failure for several reasons, but none that are
|
|
// worth returning to the app at this time.
|
|
ALOGW("Failed to list stored licenses: res = %d", static_cast<int>(res));
|
|
continue;
|
|
}
|
|
// Check if exists.
|
|
if (keySetIds.empty() || std::find(keySetIds.begin(), keySetIds.end(),
|
|
cdmKeySetId) == keySetIds.end()) {
|
|
// Does not exist for this security level.
|
|
continue;
|
|
}
|
|
return toNdkScopedAStatus(mapCdmResponseType(
|
|
mCDM->RemoveOfflineLicense(cdmKeySetId, level, identifier)));
|
|
}
|
|
// Could only reach this state if the key set could not be found.
|
|
return toNdkScopedAStatus(Status::BAD_VALUE);
|
|
}
|
|
|
|
::ndk::ScopedAStatus WVDrmPlugin::getPropertyString(
|
|
const std::string& in_propertyName, std::string* _aidl_return) {
|
|
WvStatus status(Status::OK);
|
|
std::string name(in_propertyName.c_str());
|
|
std::string value;
|
|
|
|
if (name == "vendor") {
|
|
#ifdef __ANDROID_APEX__
|
|
auto info = ::widevine::apex::GetApexInfo();
|
|
if (info.ok()) {
|
|
value = info->name;
|
|
} else {
|
|
auto err = info.error();
|
|
ALOGW("apex info error %d: %s", err.code().value(),
|
|
err.message().c_str());
|
|
value = "Google";
|
|
}
|
|
#else
|
|
value = "Google";
|
|
#endif
|
|
} else if (name == "version") {
|
|
status = queryProperty(wvcdm::QUERY_KEY_WVCDM_VERSION, value);
|
|
} else if (name == "description") {
|
|
value = "Widevine CDM";
|
|
} else if (name == "algorithms") {
|
|
value = "AES/CBC/NoPadding,HmacSHA256";
|
|
} else if (name == "securityLevel") {
|
|
std::string requestedLevel = mPropertySet.security_level();
|
|
|
|
if (requestedLevel.length() > 0) {
|
|
value = requestedLevel.c_str();
|
|
} else {
|
|
status = queryProperty(wvcdm::QUERY_KEY_SECURITY_LEVEL, value);
|
|
}
|
|
} else if (name == "systemId") {
|
|
status = queryProperty(wvcdm::QUERY_KEY_SYSTEM_ID, value);
|
|
} else if (name == "privacyMode") {
|
|
if (mPropertySet.use_privacy_mode()) {
|
|
value = kEnable;
|
|
} else {
|
|
value = kDisable;
|
|
}
|
|
} else if (name == "sessionSharing") {
|
|
if (mPropertySet.is_session_sharing_enabled()) {
|
|
value = kEnable;
|
|
} else {
|
|
value = kDisable;
|
|
}
|
|
} else if (name == "hdcpLevel") {
|
|
status = queryProperty(wvcdm::QUERY_KEY_CURRENT_HDCP_LEVEL, value);
|
|
} else if (name == "maxHdcpLevel") {
|
|
status = queryProperty(wvcdm::QUERY_KEY_MAX_HDCP_LEVEL, value);
|
|
} else if (name == "usageReportingSupport") {
|
|
status = queryProperty(wvcdm::QUERY_KEY_USAGE_SUPPORT, value);
|
|
} else if (name == "numberOfOpenSessions") {
|
|
status = queryProperty(wvcdm::QUERY_KEY_NUMBER_OF_OPEN_SESSIONS, value);
|
|
} else if (name == "maxNumberOfSessions") {
|
|
status = queryProperty(wvcdm::QUERY_KEY_MAX_NUMBER_OF_SESSIONS, value);
|
|
} else if (name == "oemCryptoApiVersion") {
|
|
status = queryProperty(wvcdm::QUERY_KEY_OEMCRYPTO_API_VERSION, value);
|
|
} else if (name == "appId") {
|
|
value = mPropertySet.app_id().c_str();
|
|
} else if (name == "origin") {
|
|
value = mCdmIdentifierBuilder.origin().c_str();
|
|
} else if (name == "CurrentSRMVersion") {
|
|
status = queryProperty(wvcdm::QUERY_KEY_CURRENT_SRM_VERSION, value);
|
|
} else if (name == "SRMUpdateSupport") {
|
|
status = queryProperty(wvcdm::QUERY_KEY_SRM_UPDATE_SUPPORT, value);
|
|
} else if (name == "resourceRatingTier") {
|
|
status = queryProperty(wvcdm::QUERY_KEY_RESOURCE_RATING_TIER, value);
|
|
} else if (name == "oemCryptoBuildInformation") {
|
|
status = queryProperty(wvcdm::QUERY_KEY_OEMCRYPTO_BUILD_INFORMATION, value);
|
|
} else if (name == "decryptHashSupport") {
|
|
status = queryProperty(wvcdm::QUERY_KEY_DECRYPT_HASH_SUPPORT, value);
|
|
} else if (name == "decryptHashError") {
|
|
status = mapCdmResponseType(
|
|
mCDM->GetDecryptHashError(mDecryptHashSessionId, &value));
|
|
} else if (name == "maxUsageEntriesSupported") {
|
|
status = queryProperty(wvcdm::QUERY_KEY_MAX_USAGE_TABLE_ENTRIES, value);
|
|
} else if (name == "oemCryptoApiMinorVersion") {
|
|
status = queryProperty(wvcdm::QUERY_KEY_OEMCRYPTO_API_MINOR_VERSION, value);
|
|
} else if (name == "analogOutputCapabilities") {
|
|
status = queryProperty(wvcdm::QUERY_KEY_ANALOG_OUTPUT_CAPABILITIES, value);
|
|
} else if (name == "canDisableAnalogOutput") {
|
|
status = queryProperty(wvcdm::QUERY_KEY_CAN_DISABLE_ANALOG_OUTPUT, value);
|
|
} else if (name == "atscMode") {
|
|
if (mPropertySet.use_atsc_mode()) {
|
|
value = kEnable;
|
|
} else {
|
|
value = kDisable;
|
|
}
|
|
} else if (name == "watermarkingSupport") {
|
|
status = queryProperty(wvcdm::QUERY_KEY_WATERMARKING_SUPPORT, value);
|
|
} else if (name == "productionReady") {
|
|
status = queryProperty(wvcdm::QUERY_KEY_PRODUCTION_READY, value);
|
|
} else if (name == "provisioningModel") {
|
|
status = queryProperty(wvcdm::QUERY_KEY_PROVISIONING_MODEL, value);
|
|
} else if (name == "processorWidth") {
|
|
#ifdef __LP64__
|
|
value = "64";
|
|
#else
|
|
value = "32";
|
|
#endif
|
|
} else {
|
|
ALOGE("App requested unknown string property %s", name.c_str());
|
|
*_aidl_return = value;
|
|
return toNdkScopedAStatus(Status::ERROR_DRM_CANNOT_HANDLE);
|
|
}
|
|
|
|
*_aidl_return = value;
|
|
return toNdkScopedAStatus(status);
|
|
}
|
|
|
|
static WvStatus getDeviceSignedCsrPayload(
|
|
android::sp<wvcdm::WvContentDecryptionModule> const cdm,
|
|
std::string& certificate_signing_request_challenge,
|
|
std::string& device_info, std::string* csr) {
|
|
if (certificate_signing_request_challenge.empty() || device_info.empty()) {
|
|
ALOGW("CSR challenge or device info is empty.");
|
|
return WvStatus(Status::BAD_VALUE);
|
|
}
|
|
std::string signed_csr_payload;
|
|
CdmResponseType res = cdm->QueryDeviceSignedCsrPayload(
|
|
certificate_signing_request_challenge, device_info, &signed_csr_payload);
|
|
if (res != wvcdm::NO_ERROR) {
|
|
ALOGE("Error querying CDM device_signed_csr_payload: %d",
|
|
static_cast<int>(res));
|
|
return mapCdmResponseType(res);
|
|
}
|
|
*csr = signed_csr_payload;
|
|
return WvStatus(Status::OK);
|
|
}
|
|
|
|
::ndk::ScopedAStatus WVDrmPlugin::getPropertyByteArray(
|
|
const std::string& in_propertyName, vector<uint8_t>* _aidl_return) {
|
|
WvStatus status(Status::OK);
|
|
std::string name(in_propertyName.c_str());
|
|
vector<uint8_t> value;
|
|
|
|
if (name == "deviceUniqueId") {
|
|
std::string id;
|
|
status = mCdmIdentifierBuilder.getDeviceUniqueId(&id);
|
|
if (status == Status::OK) {
|
|
value = StrToVector(id);
|
|
}
|
|
} else if (name == "provisioningUniqueId") {
|
|
std::string id;
|
|
status = mCdmIdentifierBuilder.getProvisioningUniqueId(&id);
|
|
if (status == Status::OK) {
|
|
value = StrToVector(id);
|
|
}
|
|
} else if (name == "serviceCertificate") {
|
|
value = StrToVector(mPropertySet.service_certificate());
|
|
} else if (name == "provisioningServiceCertificate") {
|
|
value = StrToVector(mProvisioningServiceCertificate);
|
|
} else if (name == "metrics") {
|
|
drm_metrics::WvCdmMetrics metrics;
|
|
// If the cdm identifier is not yet sealed, then there are no metrics
|
|
// for that cdm engine. Avoid calling getCdmIdentifier and sealing
|
|
// the identifier builder.
|
|
if (mCdmIdentifierBuilder.is_sealed()) {
|
|
CdmIdentifier identifier;
|
|
status = mCdmIdentifierBuilder.getCdmIdentifier(&identifier);
|
|
if (status != Status::OK) {
|
|
ALOGE("Unexpected error retrieving cdm identifier: %d", status.get());
|
|
} else {
|
|
status =
|
|
mapCdmResponseType(mCDM->GetCurrentMetrics(identifier, &metrics));
|
|
}
|
|
}
|
|
if (status == Status::OK) {
|
|
std::string serialized_metrics;
|
|
if (!metrics.SerializeToString(&serialized_metrics)) {
|
|
status = WvStatus(Status::ERROR_DRM_UNKNOWN);
|
|
} else {
|
|
value = StrToVector(serialized_metrics);
|
|
}
|
|
}
|
|
} else if (name == "bootCertificateChain" && isCsrAccessAllowed()) {
|
|
std::string boot_certificate_chain;
|
|
CdmResponseType res = mCDM->QueryStatus(
|
|
wvcdm::kLevelDefault, wvcdm::QUERY_KEY_DEBUG_BOOT_CERTIFICATE_CHAIN,
|
|
&boot_certificate_chain);
|
|
if (res != wvcdm::NO_ERROR) {
|
|
ALOGE("Error querying CDM boot certificate chain: %d",
|
|
static_cast<int>(res));
|
|
status = mapCdmResponseType(res);
|
|
} else {
|
|
value = StrToVector(boot_certificate_chain);
|
|
}
|
|
} else if (name == "verifiedDeviceInfo" && isCsrAccessAllowed()) {
|
|
std::string verified_device_info;
|
|
CdmResponseType res = mCDM->QueryStatus(wvcdm::kLevelDefault,
|
|
wvcdm::QUERY_KEY_DEVICE_INFORMATION,
|
|
&verified_device_info);
|
|
if (res != wvcdm::NO_ERROR) {
|
|
ALOGE("Error querying CDM verified device info: %d",
|
|
static_cast<int>(res));
|
|
status = mapCdmResponseType(res);
|
|
} else {
|
|
value = StrToVector(verified_device_info);
|
|
}
|
|
} else if (name == "deviceSignedCsrPayload" && isCsrAccessAllowed()) {
|
|
std::string signed_csr_payload;
|
|
status =
|
|
getDeviceSignedCsrPayload(mCDM, mCertificateSigningRequestChallenge,
|
|
mDeviceInfo, &signed_csr_payload);
|
|
if (status != Status::OK) {
|
|
ALOGE("Error querying CDM signed CSR payload: %d", status.get());
|
|
} else {
|
|
value = StrToVector(signed_csr_payload);
|
|
}
|
|
mCertificateSigningRequestChallenge.clear();
|
|
mDeviceInfo.clear();
|
|
} else {
|
|
ALOGE("App requested unknown byte array property %s", name.c_str());
|
|
status = WvStatus(Status::ERROR_DRM_CANNOT_HANDLE);
|
|
}
|
|
|
|
*_aidl_return = value;
|
|
return toNdkScopedAStatus(status);
|
|
}
|
|
|
|
::ndk::ScopedAStatus WVDrmPlugin::setPropertyString(
|
|
const std::string& in_propertyName, const std::string& in_value) {
|
|
std::string name(in_propertyName.c_str());
|
|
std::string _value(in_value.c_str());
|
|
|
|
if (name == "securityLevel") {
|
|
if (mSessionInfoMap.empty()) {
|
|
if (_value == wvcdm::QUERY_VALUE_SECURITY_LEVEL_L3.c_str()) {
|
|
mPropertySet.set_security_level(wvcdm::QUERY_VALUE_SECURITY_LEVEL_L3);
|
|
} else if (_value == wvcdm::QUERY_VALUE_SECURITY_LEVEL_L1.c_str()) {
|
|
// We must be sure we CAN set the security level to L1.
|
|
std::string current_security_level;
|
|
auto status =
|
|
queryProperty(wvcdm::kLevelDefault, wvcdm::QUERY_KEY_SECURITY_LEVEL,
|
|
current_security_level);
|
|
if (status != Status::OK) {
|
|
return toNdkScopedAStatus(status);
|
|
}
|
|
if (current_security_level != wvcdm::QUERY_VALUE_SECURITY_LEVEL_L1) {
|
|
ALOGE("App requested L1 security on a non-L1 device.");
|
|
return toNdkScopedAStatus(Status::BAD_VALUE);
|
|
} else {
|
|
mPropertySet.set_security_level(kResetSecurityLevel);
|
|
}
|
|
} else if (_value == kResetSecurityLevel ||
|
|
_value == wvcdm::QUERY_VALUE_SECURITY_LEVEL_DEFAULT) {
|
|
mPropertySet.set_security_level(kResetSecurityLevel);
|
|
} else {
|
|
ALOGE("App requested invalid security level %s", _value.c_str());
|
|
return toNdkScopedAStatus(Status::BAD_VALUE);
|
|
}
|
|
} else {
|
|
ALOGE("App tried to change security level while sessions are open.");
|
|
ALOGW("Returns UNKNOWN error for legacy status kErrorSessionIsOpen");
|
|
return toNdkScopedAStatus(Status::ERROR_DRM_UNKNOWN);
|
|
}
|
|
} else if (name == "privacyMode") {
|
|
if (_value == kEnable) {
|
|
mPropertySet.set_use_privacy_mode(true);
|
|
} else if (_value == kDisable) {
|
|
mPropertySet.set_use_privacy_mode(false);
|
|
} else {
|
|
ALOGE("App requested unknown privacy mode %s", _value.c_str());
|
|
return toNdkScopedAStatus(Status::BAD_VALUE);
|
|
}
|
|
} else if (name == "sessionSharing") {
|
|
if (mSessionInfoMap.empty()) {
|
|
if (_value == kEnable) {
|
|
mPropertySet.set_is_session_sharing_enabled(true);
|
|
} else if (_value == kDisable) {
|
|
mPropertySet.set_is_session_sharing_enabled(false);
|
|
} else {
|
|
ALOGE("App requested unknown sharing type %s", _value.c_str());
|
|
return toNdkScopedAStatus(Status::BAD_VALUE);
|
|
}
|
|
} else {
|
|
ALOGE("App tried to change key sharing while sessions are open.");
|
|
ALOGW("Returns UNKNOWN error for legacy status kErrorSessionIsOpen");
|
|
return toNdkScopedAStatus(Status::ERROR_DRM_UNKNOWN);
|
|
}
|
|
} else if (name == "appId") {
|
|
if (mSessionInfoMap.empty()) {
|
|
mPropertySet.set_app_id(_value.c_str());
|
|
} else {
|
|
ALOGE("App tried to set the application id while sessions are opened.");
|
|
ALOGW("Returns UNKNOWN error for legacy status kErrorSessionIsOpen");
|
|
return toNdkScopedAStatus(Status::ERROR_DRM_UNKNOWN);
|
|
}
|
|
} else if (name == "origin") {
|
|
if (!mSessionInfoMap.empty()) {
|
|
ALOGE("App tried to set the origin while sessions are opened.");
|
|
ALOGW("Returns UNKNOWN error for legacy status kErrorSessionIsOpen");
|
|
return toNdkScopedAStatus(Status::ERROR_DRM_UNKNOWN);
|
|
} else {
|
|
if (!mCdmIdentifierBuilder.set_origin(_value.c_str())) {
|
|
return toNdkScopedAStatus(Status::BAD_VALUE);
|
|
}
|
|
}
|
|
} else if (name == "debugIgnoreKeyboxCount") {
|
|
std::istringstream ss(_value);
|
|
uint32_t count = 0;
|
|
ss >> count;
|
|
if (ss.fail()) {
|
|
ALOGE("Could not parse an integer from '%s'", _value.c_str());
|
|
count = 0;
|
|
return toNdkScopedAStatus(Status::BAD_VALUE);
|
|
}
|
|
CdmResponseType res = mCDM->SetDebugIgnoreKeyboxCount(count);
|
|
return toNdkScopedAStatus(mapCdmResponseType(res));
|
|
} else if (name == "decryptHash") {
|
|
wvcdm::CdmSessionId sessionId;
|
|
CdmResponseType res = mCDM->SetDecryptHash(_value.c_str(), &sessionId);
|
|
|
|
if (wvcdm::NO_ERROR == res) mDecryptHashSessionId = sessionId;
|
|
|
|
return toNdkScopedAStatus(mapCdmResponseType(res));
|
|
} else if (name == "decryptHashSessionId") {
|
|
mDecryptHashSessionId = _value.c_str();
|
|
} else if (name == "atscMode") {
|
|
if (_value == kEnable) {
|
|
if (!mCdmIdentifierBuilder.set_use_atsc_mode(true)) {
|
|
ALOGE("Cdm identifier builder is sealed. Setting ATSC mode prohibited");
|
|
return toNdkScopedAStatus(Status::BAD_VALUE);
|
|
}
|
|
mPropertySet.set_use_atsc_mode(true);
|
|
} else if (_value == kDisable) {
|
|
if (!mCdmIdentifierBuilder.set_use_atsc_mode(false)) {
|
|
ALOGE("Cdm identifier builder is sealed. Setting ATSC mode prohibited");
|
|
return toNdkScopedAStatus(Status::BAD_VALUE);
|
|
}
|
|
mPropertySet.set_use_atsc_mode(false);
|
|
} else {
|
|
ALOGE("App requested unknown ATSC mode %s", _value.c_str());
|
|
return toNdkScopedAStatus(Status::BAD_VALUE);
|
|
}
|
|
} else if (name == "debugOtaKeyboxFallbackDuration") {
|
|
bool success = false;
|
|
if (_value == "default") {
|
|
success = mCDM->SetDefaultOtaKeyboxFallbackDurationRules();
|
|
} else if (_value == "fast") {
|
|
success = mCDM->SetFastOtaKeyboxFallbackDurationRules();
|
|
} else {
|
|
ALOGE("Unknown OTA fallback duration value %s", _value.c_str());
|
|
return toNdkScopedAStatus(Status::BAD_VALUE);
|
|
}
|
|
if (!success) {
|
|
return toNdkScopedAStatus(Status::ERROR_DRM_UNKNOWN);
|
|
}
|
|
} else if (name == "storeAtscLicense") {
|
|
if (!mCdmIdentifierBuilder.is_sealed()) {
|
|
ALOGE(
|
|
"Cdm identifier builder is not sealed. Storing ATSC license "
|
|
"prohibited");
|
|
return toNdkScopedAStatus(Status::ERROR_DRM_CANNOT_HANDLE);
|
|
}
|
|
if (!mPropertySet.use_atsc_mode()) {
|
|
ALOGE("ATSC mode not enabled. Storing ATSC license prohibited");
|
|
return toNdkScopedAStatus(Status::ERROR_DRM_CANNOT_HANDLE);
|
|
}
|
|
|
|
CdmIdentifier identifier;
|
|
auto status = mCdmIdentifierBuilder.getCdmIdentifier(&identifier);
|
|
if (status != Status::OK) {
|
|
ALOGE("Unable to get CDM Identifier = %d", status.get());
|
|
return toNdkScopedAStatus(status);
|
|
}
|
|
std::string key_set_id, license_data;
|
|
status = parseAtscLicenseData(_value, &key_set_id, &license_data);
|
|
if (status != Status::OK) return toNdkScopedAStatus(status);
|
|
|
|
const CdmResponseType res = mCDM->StoreAtscLicense(
|
|
identifier, getRequestedSecurityLevel(), key_set_id, license_data);
|
|
|
|
return toNdkScopedAStatus(mapCdmResponseType(res));
|
|
} else {
|
|
ALOGE("App set unknown string property %s", name.c_str());
|
|
return toNdkScopedAStatus(Status::ERROR_DRM_CANNOT_HANDLE);
|
|
}
|
|
|
|
return toNdkScopedAStatus(Status::OK);
|
|
}
|
|
|
|
::ndk::ScopedAStatus WVDrmPlugin::setPropertyByteArray(
|
|
const std::string& in_propertyName, const vector<uint8_t>& in_value) {
|
|
std::string name(in_propertyName.c_str());
|
|
vector<uint8_t> _value = in_value;
|
|
|
|
if (name == "serviceCertificate") {
|
|
std::string cert(_value.begin(), _value.end());
|
|
if (_value.empty() || mCDM->IsValidServiceCertificate(cert)) {
|
|
mPropertySet.set_service_certificate(cert);
|
|
} else {
|
|
return toNdkScopedAStatus(Status::BAD_VALUE);
|
|
}
|
|
} else if (name == "provisioningServiceCertificate") {
|
|
std::string cert(_value.begin(), _value.end());
|
|
if (_value.empty() || mCDM->IsValidServiceCertificate(cert)) {
|
|
mProvisioningServiceCertificate = cert;
|
|
} else {
|
|
return toNdkScopedAStatus(Status::BAD_VALUE);
|
|
}
|
|
} else if (name == "certificateSigningRequestChallenge" && isCsrAccessAllowed()) {
|
|
mCertificateSigningRequestChallenge =
|
|
std::string(_value.begin(), _value.end());
|
|
} else if (name == "deviceInfo" && isCsrAccessAllowed()) {
|
|
mDeviceInfo = std::string(_value.begin(), _value.end());
|
|
} else {
|
|
ALOGE("App set unknown byte array property %s", name.c_str());
|
|
return toNdkScopedAStatus(Status::ERROR_DRM_CANNOT_HANDLE);
|
|
}
|
|
|
|
return toNdkScopedAStatus(Status::OK);
|
|
}
|
|
|
|
::ndk::ScopedAStatus WVDrmPlugin::setCipherAlgorithm(
|
|
const vector<uint8_t>& in_sessionId, const std::string& in_algorithm) {
|
|
if (in_sessionId.empty() || in_algorithm.empty()) {
|
|
return toNdkScopedAStatus(Status::BAD_VALUE);
|
|
}
|
|
const std::string algo(in_algorithm.c_str());
|
|
|
|
const CdmSessionId cdmSessionId(in_sessionId.begin(), in_sessionId.end());
|
|
shared_ptr<SessionInfo> sessionInfo = mSessionInfoMap.get(cdmSessionId);
|
|
if (!sessionInfo) {
|
|
return toNdkScopedAStatus(Status::ERROR_DRM_SESSION_NOT_OPENED);
|
|
}
|
|
|
|
if (algo == "AES/CBC/NoPadding") {
|
|
sessionInfo->setEncryptionAlgorithm(wvcdm::kEncryptionAlgorithmAesCbc128);
|
|
return toNdkScopedAStatus(Status::OK);
|
|
}
|
|
return toNdkScopedAStatus(Status::ERROR_DRM_CANNOT_HANDLE);
|
|
}
|
|
|
|
::ndk::ScopedAStatus WVDrmPlugin::setMacAlgorithm(
|
|
const vector<uint8_t>& in_sessionId, const std::string& in_algorithm) {
|
|
if (in_sessionId.empty() || in_algorithm.empty()) {
|
|
return toNdkScopedAStatus(Status::BAD_VALUE);
|
|
}
|
|
const std::string algo(in_algorithm.c_str());
|
|
|
|
const CdmSessionId cdmSessionId(in_sessionId.begin(), in_sessionId.end());
|
|
shared_ptr<SessionInfo> sessionInfo = mSessionInfoMap.get(cdmSessionId);
|
|
if (!sessionInfo) {
|
|
return toNdkScopedAStatus(Status::ERROR_DRM_SESSION_NOT_OPENED);
|
|
}
|
|
|
|
if (algo == "HmacSHA256") {
|
|
sessionInfo->setSigningAlgorithm(wvcdm::kSigningAlgorithmHmacSha256);
|
|
return toNdkScopedAStatus(Status::OK);
|
|
}
|
|
return toNdkScopedAStatus(Status::ERROR_DRM_CANNOT_HANDLE);
|
|
}
|
|
|
|
::ndk::ScopedAStatus WVDrmPlugin::encrypt(const vector<uint8_t>& in_sessionId,
|
|
const vector<uint8_t>& in_keyId,
|
|
const vector<uint8_t>& in_input,
|
|
const vector<uint8_t>& in_iv,
|
|
vector<uint8_t>* _aidl_return) {
|
|
_aidl_return->clear();
|
|
const CdmSessionId cdmSessionId(in_sessionId.begin(), in_sessionId.end());
|
|
shared_ptr<SessionInfo> sessionInfo = mSessionInfoMap.get(cdmSessionId);
|
|
if (!sessionInfo) {
|
|
return toNdkScopedAStatus(Status::ERROR_DRM_SESSION_NOT_OPENED);
|
|
}
|
|
|
|
if (!sessionInfo->hasEncryptionAlgorithm()) {
|
|
ALOGW("Encryption algorithm not set");
|
|
return toNdkScopedAStatus(Status::ERROR_DRM_UNKNOWN);
|
|
}
|
|
|
|
std::string output;
|
|
const CdmResponseType result = mCDM->GenericEncrypt(
|
|
cdmSessionId, KeyId(in_keyId.begin(), in_keyId.end()),
|
|
std::string(in_input.begin(), in_input.end()),
|
|
std::string(in_iv.begin(), in_iv.end()),
|
|
sessionInfo->getEncryptionAlgorithm(), &output);
|
|
|
|
if (!result.IsOk()) {
|
|
ALOGE("Generic encryption failed: %s", result.ToString().c_str());
|
|
return toNdkScopedAStatus(
|
|
mapAndNotifyOfCdmResponseType(in_sessionId, result));
|
|
}
|
|
_aidl_return->assign(output.begin(), output.end());
|
|
return toNdkScopedAStatus(Status::OK);
|
|
}
|
|
|
|
::ndk::ScopedAStatus WVDrmPlugin::decrypt(const vector<uint8_t>& in_sessionId,
|
|
const vector<uint8_t>& in_keyId,
|
|
const vector<uint8_t>& in_input,
|
|
const vector<uint8_t>& in_iv,
|
|
vector<uint8_t>* _aidl_return) {
|
|
_aidl_return->clear();
|
|
const CdmSessionId cdmSessionId(in_sessionId.begin(), in_sessionId.end());
|
|
shared_ptr<SessionInfo> sessionInfo = mSessionInfoMap.get(cdmSessionId);
|
|
if (!sessionInfo) {
|
|
return toNdkScopedAStatus(Status::ERROR_DRM_SESSION_NOT_OPENED);
|
|
}
|
|
|
|
if (!sessionInfo->hasEncryptionAlgorithm()) {
|
|
ALOGW("Encryption algorithm not set");
|
|
return toNdkScopedAStatus(Status::ERROR_DRM_UNKNOWN);
|
|
}
|
|
|
|
std::string output;
|
|
const CdmResponseType result = mCDM->GenericDecrypt(
|
|
cdmSessionId, KeyId(in_keyId.begin(), in_keyId.end()),
|
|
std::string(in_input.begin(), in_input.end()),
|
|
std::string(in_iv.begin(), in_iv.end()),
|
|
sessionInfo->getEncryptionAlgorithm(), &output);
|
|
|
|
if (!result.IsOk()) {
|
|
ALOGE("Generic decryption failed: %s", result.ToString().c_str());
|
|
return toNdkScopedAStatus(
|
|
mapAndNotifyOfCdmResponseType(in_sessionId, result));
|
|
}
|
|
_aidl_return->assign(output.begin(), output.end());
|
|
return toNdkScopedAStatus(Status::OK);
|
|
}
|
|
|
|
::ndk::ScopedAStatus WVDrmPlugin::sign(const vector<uint8_t>& in_sessionId,
|
|
const vector<uint8_t>& in_keyId,
|
|
const vector<uint8_t>& in_message,
|
|
vector<uint8_t>* _aidl_return) {
|
|
_aidl_return->clear();
|
|
const CdmSessionId cdmSessionId(in_sessionId.begin(), in_sessionId.end());
|
|
shared_ptr<SessionInfo> sessionInfo = mSessionInfoMap.get(cdmSessionId);
|
|
if (!sessionInfo) {
|
|
return toNdkScopedAStatus(Status::ERROR_DRM_SESSION_NOT_OPENED);
|
|
}
|
|
|
|
if (!sessionInfo->hasSigningAlgorithm()) {
|
|
ALOGW("Signing algorithm not set");
|
|
return toNdkScopedAStatus(Status::ERROR_DRM_UNKNOWN);
|
|
}
|
|
|
|
std::string signature;
|
|
const CdmResponseType result =
|
|
mCDM->GenericSign(cdmSessionId, KeyId(in_keyId.begin(), in_keyId.end()),
|
|
std::string(in_message.begin(), in_message.end()),
|
|
sessionInfo->getSigningAlgorithm(), &signature);
|
|
|
|
if (!result.IsOk()) {
|
|
ALOGE("Generic signature failed: %s", result.ToString().c_str());
|
|
return toNdkScopedAStatus(
|
|
mapAndNotifyOfCdmResponseType(in_sessionId, result));
|
|
}
|
|
_aidl_return->assign(signature.begin(), signature.end());
|
|
return toNdkScopedAStatus(Status::OK);
|
|
}
|
|
|
|
::ndk::ScopedAStatus WVDrmPlugin::verify(const vector<uint8_t>& in_sessionId,
|
|
const vector<uint8_t>& in_keyId,
|
|
const vector<uint8_t>& in_message,
|
|
const vector<uint8_t>& in_signature,
|
|
bool* _aidl_return) {
|
|
*_aidl_return = false;
|
|
const CdmSessionId cdmSessionId(in_sessionId.begin(), in_sessionId.end());
|
|
shared_ptr<SessionInfo> sessionInfo = mSessionInfoMap.get(cdmSessionId);
|
|
if (!sessionInfo) {
|
|
return toNdkScopedAStatus(Status::ERROR_DRM_SESSION_NOT_OPENED);
|
|
}
|
|
|
|
if (!sessionInfo->hasSigningAlgorithm()) {
|
|
ALOGW("Signing algorithm not set");
|
|
return toNdkScopedAStatus(Status::ERROR_DRM_UNKNOWN);
|
|
}
|
|
|
|
const CdmResponseType result = mCDM->GenericVerify(
|
|
cdmSessionId, KeyId(in_keyId.begin(), in_keyId.end()),
|
|
std::string(in_message.begin(), in_message.end()),
|
|
sessionInfo->getSigningAlgorithm(),
|
|
std::string(in_signature.begin(), in_signature.end()));
|
|
|
|
if (result.IsOk()) {
|
|
*_aidl_return = true;
|
|
return toNdkScopedAStatus(Status::OK);
|
|
}
|
|
|
|
if (result == wvcdm::UNKNOWN_ERROR &&
|
|
result.oemc_result() == OEMCrypto_ERROR_SIGNATURE_FAILURE) {
|
|
// TODO(b/279245250): Use a better error code.
|
|
return toNdkScopedAStatus(Status::OK);
|
|
}
|
|
|
|
ALOGE("Generic verify failed: %s", result.ToString().c_str());
|
|
return toNdkScopedAStatus(
|
|
mapAndNotifyOfCdmResponseType(in_sessionId, result));
|
|
}
|
|
|
|
::ndk::ScopedAStatus WVDrmPlugin::signRSA(const vector<uint8_t>& in_sessionId,
|
|
const std::string& in_algorithm,
|
|
const vector<uint8_t>& in_message,
|
|
const vector<uint8_t>& in_wrappedkey,
|
|
vector<uint8_t>* _aidl_return) {
|
|
*_aidl_return = vector<uint8_t>();
|
|
if (in_sessionId.size() == 0 || in_algorithm.size() == 0 ||
|
|
in_message.size() == 0 || in_wrappedkey.size() == 0) {
|
|
return toNdkScopedAStatus(Status::BAD_VALUE);
|
|
}
|
|
|
|
const char* sid = AIBinder_getCallingSid();
|
|
if (!sid ||
|
|
(!strstr(sid, ":mediashell_app:") && !strstr(sid, ":mediadrmserver:") &&
|
|
!strstr(sid, ":setupwraith_app:") && !strstr(sid, ":gmscore_app:"))) {
|
|
ALOGE(
|
|
"Only mediashell/mediadrmserver/setupwraith_app/gmscore_app can call signRSA, "
|
|
"but actually: %s",
|
|
sid);
|
|
return toNdkScopedAStatus(Status::ERROR_DRM_UNKNOWN);
|
|
}
|
|
|
|
const std::string algo(in_algorithm.c_str());
|
|
vector<uint8_t> signature;
|
|
*_aidl_return = signature;
|
|
|
|
RSA_Padding_Scheme padding_scheme;
|
|
if (algo == "RSASSA-PSS-SHA1") {
|
|
padding_scheme = kSign_RSASSA_PSS;
|
|
} else if (algo == "PKCS1-BlockType1") {
|
|
padding_scheme = kSign_PKCS1_Block1;
|
|
} else {
|
|
ALOGE("Unknown RSA Algorithm %s", algo.c_str());
|
|
return toNdkScopedAStatus(Status::ERROR_DRM_CANNOT_HANDLE);
|
|
}
|
|
|
|
Status status = Status::OK;
|
|
OEMCryptoResult res = mCrypto->signRSA(
|
|
in_wrappedkey.data(), in_wrappedkey.size(), in_message.data(),
|
|
in_message.size(), signature, padding_scheme);
|
|
|
|
*_aidl_return = signature;
|
|
if (res != OEMCrypto_SUCCESS) {
|
|
ALOGE("OEMCrypto_GenerateRSASignature failed with %u", res);
|
|
status = mapOEMCryptoResult(res);
|
|
}
|
|
return toNdkScopedAStatus(status);
|
|
}
|
|
|
|
::ndk::ScopedAStatus WVDrmPlugin::setListener(
|
|
const shared_ptr<::aidl::android::hardware::drm::IDrmPluginListener>&
|
|
in_listener) {
|
|
mListener = in_listener;
|
|
::ndk::ScopedAStatus _aidl_status;
|
|
_aidl_status.set(AStatus_fromStatus(STATUS_OK));
|
|
return _aidl_status;
|
|
}
|
|
|
|
void WVDrmPlugin::sendEvent(
|
|
::aidl::android::hardware::drm::EventType in_eventType,
|
|
const vector<uint8_t>& in_sessionId, const vector<uint8_t>& in_data) {
|
|
::ndk::ScopedAStatus err = ::ndk::ScopedAStatus::ok();
|
|
if (mListener != nullptr) {
|
|
err = mListener->onEvent(in_eventType, in_sessionId, in_data);
|
|
} else {
|
|
ALOGE("Null event listener, event not sent");
|
|
}
|
|
if (!err.isOk()) {
|
|
ALOGW("sendEvent failed %s", err.getDescription().c_str());
|
|
}
|
|
return;
|
|
}
|
|
|
|
void WVDrmPlugin::sendExpirationUpdate(const vector<uint8_t>& in_sessionId,
|
|
int64_t in_expiryTimeInMS) {
|
|
::ndk::ScopedAStatus err = ::ndk::ScopedAStatus::ok();
|
|
if (mListener != nullptr) {
|
|
err = mListener->onExpirationUpdate(in_sessionId, in_expiryTimeInMS);
|
|
} else {
|
|
ALOGE("Null event listener, event not sent");
|
|
}
|
|
if (!err.isOk()) {
|
|
ALOGW("sendExpirationUpdate failed %s", err.getDescription().c_str());
|
|
}
|
|
return;
|
|
}
|
|
|
|
void WVDrmPlugin::sendKeysChange(
|
|
const vector<uint8_t>& in_sessionId,
|
|
const vector<::aidl::android::hardware::drm::KeyStatus>& in_keyStatusList,
|
|
bool in_hasNewUsableKey) {
|
|
::ndk::ScopedAStatus err = ::ndk::ScopedAStatus::ok();
|
|
if (mListener != nullptr) {
|
|
err = mListener->onKeysChange(in_sessionId, in_keyStatusList,
|
|
in_hasNewUsableKey);
|
|
} else {
|
|
ALOGE("Null event listener, event not sent");
|
|
}
|
|
if (!err.isOk()) {
|
|
ALOGW("sendKeysChange failed %s", err.getDescription().c_str());
|
|
}
|
|
return;
|
|
}
|
|
|
|
void WVDrmPlugin::sendSessionLostState(const vector<uint8_t>& in_sessionId) {
|
|
::ndk::ScopedAStatus err = ::ndk::ScopedAStatus::ok();
|
|
if (mListener != nullptr) {
|
|
err = mListener->onSessionLostState(in_sessionId);
|
|
} else {
|
|
ALOGE("Null event listener, event not sent");
|
|
}
|
|
if (!err.isOk()) {
|
|
ALOGW("sendSessionLostState failed %s", err.getDescription().c_str());
|
|
}
|
|
return;
|
|
}
|
|
|
|
::ndk::ScopedAStatus WVDrmPlugin::getLogMessages(
|
|
vector<::aidl::android::hardware::drm::LogMessage>* _aidl_return) {
|
|
const vector<wvutil::LogMessage>& logs(wvutil::g_logbuf.getLogs());
|
|
vector<::aidl::android::hardware::drm::LogMessage> msgs;
|
|
for (auto log : logs) {
|
|
msgs.push_back(
|
|
{log.time_ms_, toAidlLogPriority(log.priority_), log.message_});
|
|
}
|
|
*_aidl_return = msgs;
|
|
return toNdkScopedAStatus(Status::OK);
|
|
}
|
|
|
|
::ndk::ScopedAStatus WVDrmPlugin::requiresSecureDecoder(
|
|
const std::string& in_mime, SecurityLevel in_level, bool* _aidl_return) {
|
|
if (!strncasecmp(in_mime.c_str(), "video/", 6)) {
|
|
if (in_level == SecurityLevel::DEFAULT) {
|
|
std::string level(mPropertySet.security_level());
|
|
if (level == kResetSecurityLevel) {
|
|
mCDM->QueryStatus(wvcdm::kLevelDefault, wvcdm::QUERY_KEY_SECURITY_LEVEL,
|
|
&level);
|
|
}
|
|
*_aidl_return = level == wvcdm::QUERY_VALUE_SECURITY_LEVEL_L1;
|
|
} else {
|
|
// Type is video, so check level to see if we require a secure decoder.
|
|
*_aidl_return = in_level == SecurityLevel::HW_SECURE_ALL ||
|
|
in_level == SecurityLevel::HW_SECURE_DECODE;
|
|
}
|
|
} else {
|
|
// Type is not video, so never require a secure decoder.
|
|
*_aidl_return = false;
|
|
}
|
|
return ::ndk::ScopedAStatus::ok();
|
|
}
|
|
|
|
::ndk::ScopedAStatus WVDrmPlugin::setPlaybackId(
|
|
const vector<uint8_t>& in_sessionId, const std::string& in_playbackId) {
|
|
CdmSessionId cdmSessionId(in_sessionId.begin(), in_sessionId.end());
|
|
CdmResponseType res = mCDM->SetPlaybackId(cdmSessionId, in_playbackId);
|
|
return toNdkScopedAStatus(mapCdmResponseType(res));
|
|
}
|
|
|
|
void WVDrmPlugin::OnSessionRenewalNeeded(const CdmSessionId& cdmSessionId) {
|
|
const vector<uint8_t> sessionId = StrToVector(cdmSessionId);
|
|
const vector<uint8_t> data; // data is ignored
|
|
sendEvent(EventType::KEY_NEEDED, sessionId, data);
|
|
}
|
|
|
|
void WVDrmPlugin::OnSessionKeysChange(const CdmSessionId& cdmSessionId,
|
|
const CdmKeyStatusMap& cdmKeysStatus,
|
|
bool hasNewUsableKey) {
|
|
bool expired = false;
|
|
vector<KeyStatus> keyStatusList;
|
|
for (CdmKeyStatusMap::const_iterator it = cdmKeysStatus.begin();
|
|
it != cdmKeysStatus.end(); ++it) {
|
|
const KeyId& keyId = it->first;
|
|
const CdmKeyStatus cdmKeyStatus = it->second;
|
|
if (cdmKeyStatus == wvcdm::kKeyStatusExpired) expired = true;
|
|
|
|
KeyStatus keyStatus;
|
|
keyStatus.keyId = StrToVector(keyId);
|
|
keyStatus.type = ConvertFromCdmKeyStatus(cdmKeyStatus);
|
|
keyStatusList.push_back(keyStatus);
|
|
}
|
|
|
|
const vector<uint8_t> sessionId = StrToVector(cdmSessionId);
|
|
const vector<uint8_t> data; // data is ignored
|
|
sendKeysChange(sessionId, keyStatusList, hasNewUsableKey);
|
|
// For backward compatibility.
|
|
if (expired) {
|
|
sendEvent(EventType::KEY_EXPIRED, sessionId, data);
|
|
}
|
|
}
|
|
|
|
void WVDrmPlugin::OnExpirationUpdate(const CdmSessionId& cdmSessionId,
|
|
int64_t newExpiryTimeSeconds) {
|
|
const vector<uint8_t> sessionId = StrToVector(cdmSessionId);
|
|
int64_t newExpiryTimeMilliseconds =
|
|
newExpiryTimeSeconds == wvcdm::NEVER_EXPIRES
|
|
? newExpiryTimeSeconds
|
|
: newExpiryTimeSeconds * 1000;
|
|
|
|
sendExpirationUpdate(sessionId, newExpiryTimeMilliseconds);
|
|
}
|
|
|
|
void WVDrmPlugin::OnSessionLostState(const CdmSessionId& cdmSessionId) {
|
|
const vector<uint8_t> sessionId = StrToVector(cdmSessionId);
|
|
sendSessionLostState(sessionId);
|
|
}
|
|
|
|
WvStatus WVDrmPlugin::queryProperty(const std::string& property,
|
|
std::string& stringValue) const {
|
|
return queryProperty(getRequestedSecurityLevel(), property, stringValue);
|
|
}
|
|
|
|
WvStatus WVDrmPlugin::queryProperty(RequestedSecurityLevel securityLevel,
|
|
const std::string& property,
|
|
std::string& stringValue) const {
|
|
CdmResponseType res =
|
|
mCDM->QueryStatus(securityLevel, property, &stringValue);
|
|
|
|
if (res != wvcdm::NO_ERROR) {
|
|
ALOGE("Error querying CDM status: %d", static_cast<int>(res));
|
|
}
|
|
return mapCdmResponseType(res);
|
|
}
|
|
|
|
::ndk::SpAIBinder WVDrmPlugin::createBinder() {
|
|
auto binder = BnDrmPlugin::createBinder();
|
|
AIBinder_setRequestingSid(binder.get(), true);
|
|
return binder;
|
|
}
|
|
|
|
WvStatus WVDrmPlugin::queryProperty(const std::string& property,
|
|
vector<uint8_t>& vector_value) const {
|
|
std::string string_value;
|
|
auto status = queryProperty(property, string_value);
|
|
if (status != Status::OK) return status;
|
|
vector_value = StrToVector(string_value);
|
|
return WvStatus(Status::OK);
|
|
}
|
|
|
|
bool WVDrmPlugin::isProvisioned(wvcdm::CdmSecurityLevel securityLevel,
|
|
const std::string& origin,
|
|
const std::string& spoid,
|
|
bool atsc_mode_enabled) const {
|
|
return mCDM->IsProvisioned(securityLevel, origin, spoid, atsc_mode_enabled);
|
|
}
|
|
|
|
WvStatus WVDrmPlugin::parseAtscLicenseData(
|
|
const std::string& in_value, std::string* key_set_id,
|
|
std::string* serialized_license_data) {
|
|
if (key_set_id == nullptr) {
|
|
ALOGE("key_set_id null");
|
|
return WvStatus(Status::ERROR_DRM_CANNOT_HANDLE);
|
|
}
|
|
|
|
if (serialized_license_data == nullptr) {
|
|
ALOGE("serialized_license_data null");
|
|
return WvStatus(Status::ERROR_DRM_CANNOT_HANDLE);
|
|
}
|
|
|
|
if (in_value.compare(0, strlen(wvcdm::ATSC_KEY_SET_ID_PREFIX),
|
|
&wvcdm::ATSC_KEY_SET_ID_PREFIX[0]) != 0) {
|
|
ALOGE(
|
|
"ATSC license input does not conform to expectations. Key set does "
|
|
"not have a valid ATSC Key set prefix %s",
|
|
in_value.c_str());
|
|
return WvStatus(Status::BAD_VALUE);
|
|
}
|
|
const char kColon = ':';
|
|
const size_t pos = in_value.find(kColon);
|
|
if (pos == std::string::npos) {
|
|
ALOGE(
|
|
"ATSC license input does not conform to expectations. Missing colon "
|
|
"= %s",
|
|
in_value.c_str());
|
|
return WvStatus(Status::BAD_VALUE);
|
|
}
|
|
|
|
if (pos == in_value.length()) {
|
|
ALOGE(
|
|
"ATSC license input does not conform to expectations. No data after "
|
|
"colon");
|
|
return WvStatus(Status::BAD_VALUE);
|
|
}
|
|
|
|
*key_set_id = in_value.substr(0, pos);
|
|
const std::vector<uint8_t> license_data_binary =
|
|
wvutil::Base64Decode(in_value.substr(pos + 1));
|
|
|
|
if (license_data_binary.empty()) {
|
|
ALOGE(
|
|
"ATSC license input does not conform to expectations. License data "
|
|
"failed to decode from Base64");
|
|
return WvStatus(Status::BAD_VALUE);
|
|
}
|
|
serialized_license_data->assign(license_data_binary.begin(),
|
|
license_data_binary.end());
|
|
|
|
return WvStatus(Status::OK);
|
|
}
|
|
|
|
WvStatus WVDrmPlugin::mapAndNotifyOfCdmResponseType(
|
|
const vector<uint8_t>& sessionId, CdmResponseType res) {
|
|
notifyOfCdmResponseType(sessionId, res);
|
|
return mapCdmResponseType(res);
|
|
}
|
|
|
|
void WVDrmPlugin::notifyOfCdmResponseType(const vector<uint8_t>& sessionId,
|
|
CdmResponseType res) {
|
|
const vector<uint8_t> data; // data is ignored
|
|
if (res == wvcdm::NEED_PROVISIONING) {
|
|
sendEvent(EventType::PROVISION_REQUIRED, sessionId, data);
|
|
} else if (res == wvcdm::NEED_KEY) {
|
|
sendEvent(EventType::KEY_NEEDED, sessionId, data);
|
|
}
|
|
}
|
|
|
|
Status WVDrmPlugin::mapAndNotifyOfOEMCryptoResult(
|
|
const vector<uint8_t>& sessionId, OEMCryptoResult res) {
|
|
const vector<uint8_t> data; // data is ignored
|
|
if (res == OEMCrypto_ERROR_NO_DEVICE_KEY) {
|
|
sendEvent(EventType::PROVISION_REQUIRED, sessionId, data);
|
|
}
|
|
return mapOEMCryptoResult(res);
|
|
}
|
|
|
|
Status WVDrmPlugin::mapOEMCryptoResult(OEMCryptoResult res) {
|
|
switch (res) {
|
|
case OEMCrypto_SUCCESS:
|
|
return Status::OK;
|
|
|
|
case OEMCrypto_ERROR_SIGNATURE_FAILURE:
|
|
return Status::ERROR_DRM_INVALID_STATE;
|
|
|
|
case OEMCrypto_ERROR_NO_DEVICE_KEY:
|
|
return Status::ERROR_DRM_NOT_PROVISIONED;
|
|
|
|
case OEMCrypto_ERROR_INVALID_SESSION:
|
|
return Status::ERROR_DRM_SESSION_NOT_OPENED;
|
|
|
|
case OEMCrypto_ERROR_TOO_MANY_SESSIONS:
|
|
case OEMCrypto_ERROR_INSUFFICIENT_RESOURCES:
|
|
return Status::ERROR_DRM_RESOURCE_BUSY;
|
|
|
|
case OEMCrypto_ERROR_NOT_IMPLEMENTED:
|
|
return Status::ERROR_DRM_CANNOT_HANDLE;
|
|
|
|
case OEMCrypto_ERROR_INVALID_RSA_KEY:
|
|
case OEMCrypto_ERROR_SHORT_BUFFER:
|
|
case OEMCrypto_ERROR_UNKNOWN_FAILURE:
|
|
case OEMCrypto_ERROR_OPEN_SESSION_FAILED:
|
|
FALLTHROUGH_INTENDED; /* FALLTHROUGH */
|
|
default:
|
|
ALOGW("Returns UNKNOWN error for legacy status: %d", res);
|
|
return static_cast<Status>(Status::GENERAL_OEM_ERROR);
|
|
}
|
|
}
|
|
|
|
RequestedSecurityLevel WVDrmPlugin::getRequestedSecurityLevel() const {
|
|
return mPropertySet.security_level().compare(
|
|
wvcdm::QUERY_VALUE_SECURITY_LEVEL_L3) == 0
|
|
? wvcdm::kLevel3
|
|
: wvcdm::kLevelDefault;
|
|
}
|
|
|
|
bool WVDrmPlugin::initDataResemblesPSSH(const vector<uint8_t>& initData) {
|
|
const uint8_t* const initDataArray = initData.data();
|
|
|
|
if (sizeof(uint32_t) + kPsshTag.size() > initData.size()) {
|
|
// The init data is so small that it couldn't contain a size and PSSH tag.
|
|
return false;
|
|
}
|
|
|
|
// Extract the size field
|
|
const uint8_t* const sizeField = &initDataArray[0];
|
|
uint32_t nboSize;
|
|
memcpy(&nboSize, sizeField, sizeof(nboSize));
|
|
uint32_t size = ntohl(nboSize);
|
|
|
|
if (size > initData.size()) {
|
|
return false;
|
|
}
|
|
|
|
// Extract the ID field
|
|
const char* const idField =
|
|
reinterpret_cast<const char* const>(&initDataArray[sizeof(nboSize)]);
|
|
std::string id(idField, kPsshTag.size());
|
|
return id == kPsshTag;
|
|
}
|
|
|
|
WvStatus WVDrmPlugin::unprovision(const CdmIdentifier& identifier) {
|
|
if (mPropertySet.use_atsc_mode())
|
|
return mapCdmResponseType(wvcdm::PROVISIONING_NOT_ALLOWED_FOR_ATSC);
|
|
|
|
CdmResponseType res1 = mCDM->Unprovision(wvcdm::kSecurityLevelL1, identifier);
|
|
CdmResponseType res3 = mCDM->Unprovision(wvcdm::kSecurityLevelL3, identifier);
|
|
if (!isCdmResponseTypeSuccess(res1)) {
|
|
return mapCdmResponseType(res1);
|
|
} else {
|
|
return mapCdmResponseType(res3);
|
|
}
|
|
}
|
|
|
|
// Implementation for the CdmIdentifierBuilder inner class
|
|
WVDrmPlugin::CdmIdentifierBuilder::CdmIdentifierBuilder(
|
|
bool useSpoid, const WVDrmPlugin& parent, const std::string& appPackageName)
|
|
: mCdmIdentifier(),
|
|
mIsIdentifierSealed(false),
|
|
mUseSpoid(useSpoid),
|
|
mAppPackageName(appPackageName),
|
|
mParent(parent) {
|
|
mCdmIdentifier.app_package_name = mAppPackageName;
|
|
mCdmIdentifier.unique_id = getNextUniqueId();
|
|
mCdmIdentifier.user_id = wvutil::GetIpcCallingUid();
|
|
}
|
|
|
|
WvStatus WVDrmPlugin::CdmIdentifierBuilder::getCdmIdentifier(
|
|
CdmIdentifier* identifier) {
|
|
if (!mIsIdentifierSealed) {
|
|
auto res = calculateSpoid();
|
|
if (res != Status::OK) return res;
|
|
mIsIdentifierSealed = true;
|
|
}
|
|
*identifier = mCdmIdentifier;
|
|
return WvStatus(Status::OK);
|
|
}
|
|
|
|
WvStatus WVDrmPlugin::CdmIdentifierBuilder::getDeviceUniqueId(std::string* id) {
|
|
if (mUseSpoid) {
|
|
CdmIdentifier identifier;
|
|
auto res = getCdmIdentifier(&identifier);
|
|
if (res != Status::OK) return res;
|
|
|
|
*id = identifier.spoid;
|
|
return WvStatus(Status::OK);
|
|
} else {
|
|
return getOemcryptoDeviceId(id);
|
|
}
|
|
}
|
|
|
|
WvStatus WVDrmPlugin::CdmIdentifierBuilder::getProvisioningUniqueId(
|
|
std::string* id) {
|
|
if (mUseSpoid) {
|
|
// To fake a provisioning-unique ID on SPOID devices where we can't expose
|
|
// the real provisioning-unique ID, we just use the SPOID and invert all
|
|
// the bits.
|
|
auto res = getDeviceUniqueId(id);
|
|
if (res != Status::OK) return res;
|
|
|
|
for (char& c : *id) {
|
|
c = ~c;
|
|
}
|
|
|
|
return WvStatus(Status::OK);
|
|
} else {
|
|
return mParent.queryProperty(wvcdm::QUERY_KEY_PROVISIONING_ID, *id);
|
|
}
|
|
}
|
|
|
|
bool WVDrmPlugin::CdmIdentifierBuilder::set_origin(const std::string& id) {
|
|
if (mIsIdentifierSealed) return false;
|
|
mCdmIdentifier.origin = id;
|
|
return true;
|
|
}
|
|
|
|
bool WVDrmPlugin::CdmIdentifierBuilder::set_use_atsc_mode(bool enable) {
|
|
if (is_sealed()) return false;
|
|
mCdmIdentifier.app_package_name =
|
|
enable ? wvcdm::ATSC_APP_PACKAGE_NAME : mAppPackageName;
|
|
return true;
|
|
}
|
|
|
|
WvStatus WVDrmPlugin::CdmIdentifierBuilder::calculateSpoid() {
|
|
if (!mUseSpoid) return WvStatus(Status::OK);
|
|
|
|
// Calculate SPOID for default security level if appropriate
|
|
std::string deviceId;
|
|
if (mParent.getRequestedSecurityLevel() == wvcdm::kLevelDefault) {
|
|
auto res = getOemcryptoDeviceId(&deviceId);
|
|
if (res != Status::OK) return res;
|
|
|
|
return WvStatus(calculateSpoid(deviceId, &mCdmIdentifier.spoid));
|
|
}
|
|
|
|
// If requested security level is L3, possibilities are
|
|
// (a) L3 has not been provisioned
|
|
// (b) L3 was provisioned with L3 device ID in the CdmIdentifier
|
|
// (c) L3 was provisioned (incorrectly) with L1 device ID in the
|
|
// CdmIdentifier Check (b) first. Get L3 device ID, calculate SPOID and if
|
|
// provisioned with this SPOID, return this SPOID. Check (c) next. Get L1
|
|
// device ID, calculate SPOID and if provisioned with this SPOID, return
|
|
// this SPOID. On any errors in (c) or not provisioned return L3 SPOID.
|
|
auto res = getOemcryptoDeviceId(wvcdm::kLevel3, &deviceId);
|
|
if (res != Status::OK) return res;
|
|
|
|
std::string spoidL3;
|
|
auto status = calculateSpoid(deviceId, &spoidL3);
|
|
if (status != Status::OK) return WvStatus(status);
|
|
|
|
bool atsc_mode_enabled =
|
|
mCdmIdentifier.app_package_name == wvcdm::ATSC_APP_PACKAGE_NAME;
|
|
|
|
if (mParent.isProvisioned(wvcdm::kSecurityLevelL3, origin(), spoidL3,
|
|
atsc_mode_enabled)) {
|
|
mCdmIdentifier.spoid = spoidL3;
|
|
return WvStatus(Status::OK);
|
|
}
|
|
|
|
// Not provisioned with CdmIdentifier containing SPOID with L3 device ID.
|
|
// Try SPOID with L1 device ID.
|
|
std::string deviceIdLevelDefault;
|
|
res = getOemcryptoDeviceId(wvcdm::kLevelDefault, &deviceIdLevelDefault);
|
|
if (res != Status::OK) {
|
|
mCdmIdentifier.spoid = spoidL3;
|
|
return WvStatus(Status::OK);
|
|
}
|
|
|
|
// If the L3 and default security level IDs are identical then the
|
|
// device does not support L1.
|
|
if (deviceId == deviceIdLevelDefault) {
|
|
mCdmIdentifier.spoid = spoidL3;
|
|
return WvStatus(Status::OK);
|
|
}
|
|
|
|
std::string spoidLevelDefault;
|
|
status = calculateSpoid(deviceIdLevelDefault, &spoidLevelDefault);
|
|
if (status != Status::OK) {
|
|
mCdmIdentifier.spoid = spoidL3;
|
|
return WvStatus(Status::OK);
|
|
}
|
|
|
|
if (mParent.isProvisioned(wvcdm::kSecurityLevelL1, origin(),
|
|
spoidLevelDefault, atsc_mode_enabled)) {
|
|
mCdmIdentifier.spoid = spoidLevelDefault;
|
|
return WvStatus(Status::OK);
|
|
}
|
|
|
|
// Not provisioned with CdmIdentifier containing SPOID with L1 or L3
|
|
// device ID. Return L3 SPOID.
|
|
mCdmIdentifier.spoid = spoidL3;
|
|
return WvStatus(Status::OK);
|
|
}
|
|
|
|
Status WVDrmPlugin::CdmIdentifierBuilder::calculateSpoid(
|
|
const std::string& deviceId, std::string* spoid) {
|
|
if (spoid == nullptr) return Status::ERROR_DRM_CANNOT_HANDLE;
|
|
|
|
if (!mUseSpoid) {
|
|
spoid->clear();
|
|
return Status::OK;
|
|
}
|
|
|
|
uint8_t hash[SHA256_DIGEST_LENGTH];
|
|
SHA256_CTX ctx;
|
|
SHA256_Init(&ctx);
|
|
SHA256_Update(&ctx, deviceId.data(), deviceId.length());
|
|
SHA256_Update(&ctx, mAppPackageName.data(), mAppPackageName.length());
|
|
SHA256_Update(&ctx, origin().data(), origin().length());
|
|
SHA256_Final(hash, &ctx);
|
|
|
|
*spoid = std::string(reinterpret_cast<char*>(hash), SHA256_DIGEST_LENGTH);
|
|
return Status::OK;
|
|
}
|
|
|
|
WvStatus WVDrmPlugin::CdmIdentifierBuilder::getOemcryptoDeviceId(
|
|
std::string* id) {
|
|
return mParent.queryProperty(wvcdm::QUERY_KEY_DEVICE_ID, *id);
|
|
}
|
|
|
|
WvStatus WVDrmPlugin::CdmIdentifierBuilder::getOemcryptoDeviceId(
|
|
wvcdm::RequestedSecurityLevel securityLevel, std::string* id) {
|
|
return mParent.queryProperty(securityLevel, wvcdm::QUERY_KEY_DEVICE_ID, *id);
|
|
}
|
|
|
|
uint32_t WVDrmPlugin::CdmIdentifierBuilder::getNextUniqueId() {
|
|
// Start with 1. 0 is reserved for the default cdm identifier.
|
|
static uint32_t unique_id = 1;
|
|
return ++unique_id;
|
|
}
|
|
|
|
} // namespace widevine
|
|
} // namespace drm
|
|
} // namespace hardware
|
|
} // namespace wvdrm
|