// // 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 #include #include #include #include #include #include #include #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 "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::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::CdmUsageInfo; using wvcdm::CdmUsageInfoReleaseMessage; using wvcdm::kDefaultCdmIdentifier; using wvcdm::KeyId; using wvcdm::RequestedSecurityLevel; namespace { vector StrToVector(const std::string& str) { vector 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) 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; } } } // namespace WVDrmPlugin::WVDrmPlugin(const android::sp& cdm, const std::string& appPackageName, WVGenericCryptoInterface* crypto, bool useSpoid) : mCdmIdentifierBuilder(useSpoid, *this, appPackageName), mCDM(cdm), mCrypto(crypto), mCryptoSessions(), mAppPackageName(appPackageName) { Terminator::Register(this); } WVDrmPlugin::~WVDrmPlugin() { wvutil::SetLoggingUid(mCdmIdentifierBuilder.user_id()); Terminator::Forget(this); Close(); } void WVDrmPlugin::Close() { typedef std::map::iterator mapIterator; for (mapIterator iter = mCryptoSessions.begin(); iter != mCryptoSessions.end(); ++iter) { CdmResponseType res = mCDM->CloseSession(iter->first); if (!isCdmResponseTypeSuccess(res)) { ALOGE("Failed to close session while destroying WVDrmPlugin"); } } mCryptoSessions.clear(); if (mCdmIdentifierBuilder.is_sealed()) { CdmIdentifier identifier; Status status = mCdmIdentifierBuilder.getCdmIdentifier(&identifier); if (status != Status::OK) { ALOGE("Failed to get cdm identifier %d", status); } else { status = mapCdmResponseType(mCDM->CloseCdm(identifier)); if (status != Status::OK) { ALOGE("Failed to close cdm. status %d", status); } } } } Status WVDrmPlugin::openSessionCommon(vector& sessionId) { Status 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)) { status = mapAndNotifyOfCdmResponseType(sessionId, res); return status; } bool success = false; // Construct a CryptoSession CdmQueryMap info; res = mCDM->QueryOemCryptoSessionId(cdmSessionId, &info); if (isCdmResponseTypeSuccess(res) && info.count(wvcdm::QUERY_KEY_OEMCRYPTO_SESSION_ID)) { OEMCrypto_SESSION oecSessionId = std::stoul(info[wvcdm::QUERY_KEY_OEMCRYPTO_SESSION_ID]); mCryptoSessions[cdmSessionId] = CryptoSession(oecSessionId); success = true; } else { ALOGE("Unable to query key control info."); } if (success) { // Marshal Session ID sessionId = StrToVector(cdmSessionId); return Status::OK; } else { mCDM->CloseSession(cdmSessionId); if (!isCdmResponseTypeSuccess(res)) { // We got an error code we can return. status = 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"); status = Status::ERROR_DRM_UNKNOWN; } } return status; } 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* _aidl_return) { vector sessionId; if (SecurityLevel::UNKNOWN == in_securityLevel) { *_aidl_return = sessionId; return toNdkScopedAStatus(Status::ERROR_DRM_CANNOT_HANDLE); } std::string native_security_level; Status status = queryProperty(wvcdm::kLevelDefault, wvcdm::QUERY_KEY_SECURITY_LEVEL, native_security_level); if (Status::OK != status) { *_aidl_return = sessionId; return toNdkScopedAStatus(status); } if (wvcdm::QUERY_VALUE_SECURITY_LEVEL_L3 == native_security_level && in_securityLevel >= SecurityLevel::SW_SECURE_DECODE && in_securityLevel != SecurityLevel::DEFAULT) { *_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)); SecurityLevel securityLevel = in_securityLevel; if (SecurityLevel::DEFAULT == in_securityLevel) { std::string level; Status status = queryProperty(wvcdm::kLevelDefault, wvcdm::QUERY_KEY_SECURITY_LEVEL, level); if (status == Status::OK) { securityLevel = mapSecurityLevel(level); } else { ALOGE("openSession: failed to query security level, status=%d", status); } } status = openSessionCommon(sessionId); if (Status::OK == status) { SecurityLevel currentSecurityLevel = SecurityLevel::UNKNOWN; const auto ret = getSecurityLevel(sessionId, ¤tSecurityLevel); if (!ret.isOk() || securityLevel != currentSecurityLevel) { ALOGE("Failed to open session with the requested security level=%d", securityLevel); closeSession(sessionId); sessionId.clear(); status = Status::ERROR_DRM_INVALID_STATE; } } *_aidl_return = sessionId; return toNdkScopedAStatus(status); } ::ndk::ScopedAStatus WVDrmPlugin::closeSession( const vector& in_sessionId) { if (!in_sessionId.size()) { return toNdkScopedAStatus(Status::BAD_VALUE); } CdmSessionId cdmSessionId(in_sessionId.begin(), in_sessionId.end()); CdmResponseType res = mCDM->CloseSession(cdmSessionId); mCryptoSessions.erase(cdmSessionId); if (!isCdmResponseTypeSuccess(res)) { return toNdkScopedAStatus(Status::ERROR_DRM_SESSION_NOT_OPENED); } return toNdkScopedAStatus(Status::OK); } ::ndk::ScopedAStatus WVDrmPlugin::getKeyRequest( const vector& in_scope, const vector& 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(); _aidl_return->requestType = KeyRequestType::UNKNOWN; _aidl_return->defaultUrl = ""; return toNdkScopedAStatus(Status::BAD_VALUE); } KeyRequestType requestType = KeyRequestType::UNKNOWN; Status status = Status::OK; std::string defaultUrl; vector request; CdmIdentifier identifier; status = static_cast(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(psshPrefix), sizeof(psshPrefix) / sizeof(uint8_t)); processedInitData.append(reinterpret_cast(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(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& in_scope, const vector& 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 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); } Status 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 = Status::OK; } } _aidl_return->keySetId = keySetId; return toNdkScopedAStatus(status); } ::ndk::ScopedAStatus WVDrmPlugin::removeKeys( const vector& 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& 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& in_sessionId, vector<::aidl::android::hardware::drm::KeyValue>* _aidl_return) { if (in_sessionId.size() == 0) { *_aidl_return = vector(); return toNdkScopedAStatus(Status::BAD_VALUE); } CdmSessionId cdmSessionId(in_sessionId.begin(), in_sessionId.end()); CdmQueryMap cdmLicenseInfo; CdmResponseType res = mCDM->QueryKeyStatus(cdmSessionId, &cdmLicenseInfo); vector 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) { Status status = Status::OK; std::string defaultUrl; vector 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& in_response, ::aidl::android::hardware::drm::ProvideProvisionResponseResult* _aidl_return) { if (!in_response.size()) { _aidl_return->certificate = vector(); _aidl_return->wrappedKey = vector(); return toNdkScopedAStatus(Status::BAD_VALUE); } vector certificate; vector wrappedKey; CdmIdentifier identifier; Status 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); } ::ndk::ScopedAStatus WVDrmPlugin::getSecureStop( const ::aidl::android::hardware::drm::SecureStopId& in_secureStopId, ::aidl::android::hardware::drm::SecureStop* _aidl_return) { if (!in_secureStopId.secureStopId.size()) { *_aidl_return = SecureStop(); return toNdkScopedAStatus(Status::BAD_VALUE); } vector cdmStopVec; SecureStop secureStop; CdmIdentifier identifier; Status status = mCdmIdentifierBuilder.getCdmIdentifier(&identifier); if (status != Status::OK) { *_aidl_return = SecureStop(); return toNdkScopedAStatus(status); } CdmUsageInfo cdmUsageInfo; CdmSecureStopId cdmSsId(in_secureStopId.secureStopId.begin(), in_secureStopId.secureStopId.end()); CdmResponseType res = mCDM->GetUsageInfo(mPropertySet.app_id(), cdmSsId, identifier, &cdmUsageInfo); if (isCdmResponseTypeSuccess(res)) { for (CdmUsageInfo::const_iterator iter = cdmUsageInfo.begin(); iter != cdmUsageInfo.end(); ++iter) { const std::string& cdmStop = *iter; cdmStopVec = StrToVector(cdmStop); } secureStop.opaqueData = cdmStopVec; } *_aidl_return = secureStop; return toNdkScopedAStatus(mapCdmResponseType(res)); } ::ndk::ScopedAStatus WVDrmPlugin::getSecureStops( vector<::aidl::android::hardware::drm::SecureStop>* _aidl_return) { std::list> secureStops; vector secureStopsVec; CdmIdentifier identifier; Status status = mCdmIdentifierBuilder.getCdmIdentifier(&identifier); if (status != Status::OK) { *_aidl_return = secureStopsVec; return toNdkScopedAStatus(status); } CdmUsageInfo cdmUsageInfo; CdmResponseType res = mCDM->GetUsageInfo(mPropertySet.app_id(), identifier, &cdmUsageInfo); if (isCdmResponseTypeSuccess(res)) { secureStops.clear(); for (CdmUsageInfo::const_iterator iter = cdmUsageInfo.begin(); iter != cdmUsageInfo.end(); ++iter) { const std::string& cdmStop = *iter; secureStops.push_back(StrToVector(cdmStop)); } } std::list>::iterator iter = secureStops.begin(); while (iter != secureStops.end()) { SecureStop secureStop; secureStop.opaqueData = *iter++; secureStopsVec.push_back(secureStop); } *_aidl_return = secureStopsVec; 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(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; Status status = mCdmIdentifierBuilder.getCdmIdentifier(&identifier); if (status != Status::OK) { return toNdkScopedAStatus(status); } CdmUsageInfoReleaseMessage 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) { CdmIdentifier identifier; Status status = mCdmIdentifierBuilder.getCdmIdentifier(&identifier); if (status != Status::OK) { *_aidl_return = vector(); return toNdkScopedAStatus(status); } drm_metrics::WvCdmMetrics proto_metrics; CdmResponseType result = mCDM->GetMetrics(identifier, &proto_metrics); if (result != wvcdm::NO_ERROR) { *_aidl_return = vector(); return toNdkScopedAStatus(mapCdmResponseType(result)); } vector wvMetrics; ::wvcdm::WvMetricsAdapter adapter; ::wvcdm::WvMetricsAdapter::ToWvMetrics(proto_metrics, &wvMetrics); *_aidl_return = wvMetrics; return toNdkScopedAStatus(Status::OK); } ::ndk::ScopedAStatus WVDrmPlugin::getSecureStopIds( vector<::aidl::android::hardware::drm::SecureStopId>* _aidl_return) { vector secureStopIds; CdmIdentifier identifier; Status status = mCdmIdentifierBuilder.getCdmIdentifier(&identifier); if (status != Status::OK) { *_aidl_return = secureStopIds; return toNdkScopedAStatus(status); } vector 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; Status status = mCdmIdentifierBuilder.getCdmIdentifier(&identifier); if (status != Status::OK) { return toNdkScopedAStatus(status); } const vector data = in_ssRelease.opaqueData; CdmUsageInfoReleaseMessage cdmMessage(data.begin(), data.end()); 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; Status status = mCdmIdentifierBuilder.getCdmIdentifier(&identifier); if (status != Status::OK) { return toNdkScopedAStatus(status); } CdmSecureStopId id(in_secureStopId.secureStopId.begin(), in_secureStopId.secureStopId.end()); CdmResponseType res = mCDM->RemoveUsageInfo(mPropertySet.app_id(), identifier, id); return toNdkScopedAStatus(mapCdmResponseType(res)); } ::ndk::ScopedAStatus WVDrmPlugin::removeAllSecureStops() { CdmIdentifier identifier; Status status = mCdmIdentifierBuilder.getCdmIdentifier(&identifier); if (status != Status::OK) { return toNdkScopedAStatus(status); } 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; Status 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; Status 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& 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", status); } *_aidl_return = securityLevel; return toNdkScopedAStatus(mapCdmResponseType(status)); } ::ndk::ScopedAStatus WVDrmPlugin::getOfflineLicenseKeySetIds( vector<::aidl::android::hardware::drm::KeySetId>* _aidl_return) { vector> keySetIds; vector keySetIdsVec; CdmIdentifier identifier; Status status = mCdmIdentifierBuilder.getCdmIdentifier(&identifier); if (status != Status::OK) { *_aidl_return = keySetIdsVec; return toNdkScopedAStatus(status); } vector levels = {wvcdm::kSecurityLevelL1, wvcdm::kSecurityLevelL3}; CdmResponseType res = wvcdm::UNKNOWN_ERROR; for (auto level : levels) { vector cdmKeySetIds; res = mCDM->ListStoredLicenses(level, identifier, &cdmKeySetIds); if (isCdmResponseTypeSuccess(res)) { keySetIds.clear(); for (auto id : cdmKeySetIds) { keySetIds.push_back(StrToVector(id)); } KeySetId kid; for (auto id : keySetIds) { kid.keySetId = id; keySetIdsVec.push_back(kid); } } } *_aidl_return = keySetIdsVec; return toNdkScopedAStatus(mapCdmResponseType(res)); } ::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; Status status = mCdmIdentifierBuilder.getCdmIdentifier(&identifier); if (status != Status::OK) { *_aidl_return = licenseState; return toNdkScopedAStatus(status); } CdmResponseType res = 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.size()) { return toNdkScopedAStatus(Status::BAD_VALUE); } CdmIdentifier identifier; Status status = mCdmIdentifierBuilder.getCdmIdentifier(&identifier); if (status != Status::OK) { return toNdkScopedAStatus(status); } CdmResponseType res = wvcdm::UNKNOWN_ERROR; res = mCDM->RemoveOfflineLicense( std::string(in_keySetId.keySetId.begin(), in_keySetId.keySetId.end()), wvcdm::kSecurityLevelL1, identifier); if (!isCdmResponseTypeSuccess(res)) { CdmResponseType res = mCDM->RemoveOfflineLicense( std::string(in_keySetId.keySetId.begin(), in_keySetId.keySetId.end()), wvcdm::kSecurityLevelL3, identifier); status = mapCdmResponseType(res); } return toNdkScopedAStatus(status); } ::ndk::ScopedAStatus WVDrmPlugin::getPropertyString( const std::string& in_propertyName, std::string* _aidl_return) { Status status = Status::OK; std::string name(in_propertyName.c_str()); std::string value; if (name == "vendor") { value = "Google"; } 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 { 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); } ::ndk::ScopedAStatus WVDrmPlugin::getPropertyByteArray( const std::string& in_propertyName, vector* _aidl_return) { Status status = Status::OK; std::string name(in_propertyName.c_str()); vector 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); } else { status = mapCdmResponseType(mCDM->GetMetrics(identifier, &metrics)); } } if (status == Status::OK) { std::string serialized_metrics; if (!metrics.SerializeToString(&serialized_metrics)) { status = Status::ERROR_DRM_UNKNOWN; } else { value = StrToVector(serialized_metrics); } } } else { ALOGE("App requested unknown byte array property %s", name.c_str()); status = 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 (mCryptoSessions.size() == 0) { 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; Status 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 (mCryptoSessions.size() == 0) { 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 (mCryptoSessions.size() == 0) { 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 (mCryptoSessions.size() != 0) { 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 { 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& in_value) { std::string name(in_propertyName.c_str()); vector _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 { 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& in_sessionId, const std::string& in_algorithm) { if (in_sessionId.size() == 0 || in_algorithm.size() == 0) { return toNdkScopedAStatus(Status::BAD_VALUE); } std::string algo(in_algorithm.c_str()); CdmSessionId cdmSessionId(in_sessionId.begin(), in_sessionId.end()); if (!mCryptoSessions.count(cdmSessionId)) { return toNdkScopedAStatus(Status::ERROR_DRM_SESSION_NOT_OPENED); } CryptoSession& cryptoSession = mCryptoSessions[cdmSessionId]; if (algo == "AES/CBC/NoPadding") { cryptoSession.setCipherAlgorithm(OEMCrypto_AES_CBC_128_NO_PADDING); } else { return toNdkScopedAStatus(Status::ERROR_DRM_CANNOT_HANDLE); } return toNdkScopedAStatus(Status::OK); } ::ndk::ScopedAStatus WVDrmPlugin::setMacAlgorithm( const vector& in_sessionId, const std::string& in_algorithm) { if (in_sessionId.size() == 0 || in_algorithm.size() == 0) { return toNdkScopedAStatus(Status::BAD_VALUE); } std::string algo(in_algorithm.c_str()); CdmSessionId cdmSessionId(in_sessionId.begin(), in_sessionId.end()); if (!mCryptoSessions.count(cdmSessionId)) { return toNdkScopedAStatus(Status::ERROR_DRM_SESSION_NOT_OPENED); } CryptoSession& cryptoSession = mCryptoSessions[cdmSessionId]; if (algo == "HmacSHA256") { cryptoSession.setMacAlgorithm(OEMCrypto_HMAC_SHA256); } else { return toNdkScopedAStatus(Status::ERROR_DRM_CANNOT_HANDLE); } return toNdkScopedAStatus(Status::OK); } ::ndk::ScopedAStatus WVDrmPlugin::encrypt(const vector& in_sessionId, const vector& in_keyId, const vector& in_input, const vector& in_iv, vector* _aidl_return) { vector output; *_aidl_return = output; CdmSessionId cdmSessionId(in_sessionId.begin(), in_sessionId.end()); if (!mCryptoSessions.count(cdmSessionId)) { return toNdkScopedAStatus(Status::ERROR_DRM_SESSION_NOT_OPENED); } const CryptoSession& cryptoSession = mCryptoSessions[cdmSessionId]; if (cryptoSession.cipherAlgorithm() == kInvalidCryptoAlgorithm) { ALOGW("Returns UNKNOWN error for legacy status NO_INIT"); return toNdkScopedAStatus(Status::ERROR_DRM_UNKNOWN); } OEMCryptoResult res = mCrypto->selectKey(cryptoSession.oecSessionId(), in_keyId.data(), in_keyId.size()); if (res != OEMCrypto_SUCCESS) { ALOGE("OEMCrypto_SelectKey failed with %u", res); return toNdkScopedAStatus(mapAndNotifyOfOEMCryptoResult(in_sessionId, res)); } output.resize(in_input.size()); Status status = Status::OK; res = mCrypto->encrypt(cryptoSession.oecSessionId(), in_input.data(), in_input.size(), in_iv.data(), cryptoSession.cipherAlgorithm(), output.data()); *_aidl_return = output; if (res == OEMCrypto_SUCCESS) { status = Status::OK; } else { ALOGE("OEMCrypto_Generic_Encrypt failed with %u", res); status = mapAndNotifyOfOEMCryptoResult(in_sessionId, res); } return toNdkScopedAStatus(status); } ::ndk::ScopedAStatus WVDrmPlugin::decrypt(const vector& in_sessionId, const vector& in_keyId, const vector& in_input, const vector& in_iv, vector* _aidl_return) { vector output; *_aidl_return = output; CdmSessionId cdmSessionId(in_sessionId.begin(), in_sessionId.end()); if (!mCryptoSessions.count(cdmSessionId)) { return toNdkScopedAStatus(Status::ERROR_DRM_SESSION_NOT_OPENED); } const CryptoSession& cryptoSession = mCryptoSessions[cdmSessionId]; if (cryptoSession.cipherAlgorithm() == kInvalidCryptoAlgorithm) { ALOGW("Returns UNKNOWN error for legacy status NO_INIT"); return toNdkScopedAStatus(Status::ERROR_DRM_UNKNOWN); } OEMCryptoResult res = mCrypto->selectKey(cryptoSession.oecSessionId(), in_keyId.data(), in_keyId.size()); if (res != OEMCrypto_SUCCESS) { ALOGE("OEMCrypto_SelectKey failed with %u", res); return toNdkScopedAStatus(mapAndNotifyOfOEMCryptoResult(in_sessionId, res)); } output.resize(in_input.size()); Status status = Status::OK; res = mCrypto->decrypt(cryptoSession.oecSessionId(), in_input.data(), in_input.size(), in_iv.data(), cryptoSession.cipherAlgorithm(), output.data()); *_aidl_return = output; if (res == OEMCrypto_SUCCESS) { status = Status::OK; } else { ALOGE("OEMCrypto_Generic_Decrypt failed with %u", res); status = mapAndNotifyOfOEMCryptoResult(in_sessionId, res); } return toNdkScopedAStatus(status); } ::ndk::ScopedAStatus WVDrmPlugin::sign(const vector& in_sessionId, const vector& in_keyId, const vector& in_message, vector* _aidl_return) { vector signature; *_aidl_return = signature; CdmSessionId cdmSessionId(in_sessionId.begin(), in_sessionId.end()); if (!mCryptoSessions.count(cdmSessionId)) { return toNdkScopedAStatus(Status::ERROR_DRM_SESSION_NOT_OPENED); } const CryptoSession& cryptoSession = mCryptoSessions[cdmSessionId]; if (cryptoSession.macAlgorithm() == kInvalidCryptoAlgorithm) { ALOGW("Returns UNKNOWN error for legacy status NO_INIT"); return toNdkScopedAStatus(Status::ERROR_DRM_UNKNOWN); } OEMCryptoResult res = mCrypto->selectKey(cryptoSession.oecSessionId(), in_keyId.data(), in_keyId.size()); if (res != OEMCrypto_SUCCESS) { ALOGE("OEMCrypto_SelectKey failed with %u", res); return toNdkScopedAStatus(mapAndNotifyOfOEMCryptoResult(in_sessionId, res)); } size_t signatureSize = 0; res = mCrypto->sign(cryptoSession.oecSessionId(), in_message.data(), in_message.size(), cryptoSession.macAlgorithm(), nullptr, &signatureSize); Status status = Status::OK; if (res != OEMCrypto_ERROR_SHORT_BUFFER) { ALOGE( "OEMCrypto_Generic_Sign failed with %u when requesting signature " "size", res); if (res != OEMCrypto_SUCCESS) { status = mapAndNotifyOfOEMCryptoResult(in_sessionId, res); } else { status = Status::ERROR_DRM_UNKNOWN; } return toNdkScopedAStatus(status); } signature.resize(signatureSize); res = mCrypto->sign(cryptoSession.oecSessionId(), in_message.data(), in_message.size(), cryptoSession.macAlgorithm(), signature.data(), &signatureSize); *_aidl_return = signature; if (res == OEMCrypto_SUCCESS) { status = Status::OK; } else { ALOGE("OEMCrypto_Generic_Sign failed with %u", res); status = mapAndNotifyOfOEMCryptoResult(in_sessionId, res); } return toNdkScopedAStatus(status); } ::ndk::ScopedAStatus WVDrmPlugin::verify(const vector& in_sessionId, const vector& in_keyId, const vector& in_message, const vector& in_signature, bool* _aidl_return) { bool match = false; *_aidl_return = match; CdmSessionId cdmSessionId(in_sessionId.begin(), in_sessionId.end()); if (!mCryptoSessions.count(cdmSessionId)) { return toNdkScopedAStatus(Status::ERROR_DRM_SESSION_NOT_OPENED); } const CryptoSession& cryptoSession = mCryptoSessions[cdmSessionId]; if (cryptoSession.macAlgorithm() == kInvalidCryptoAlgorithm) { ALOGW("Returns UNKNOWN error for legacy status NO_INIT"); return toNdkScopedAStatus(Status::ERROR_DRM_UNKNOWN); } OEMCryptoResult res = mCrypto->selectKey(cryptoSession.oecSessionId(), in_keyId.data(), in_keyId.size()); if (res != OEMCrypto_SUCCESS) { ALOGE("OEMCrypto_SelectKey failed with %u", res); return toNdkScopedAStatus(mapAndNotifyOfOEMCryptoResult(in_sessionId, res)); } res = mCrypto->verify(cryptoSession.oecSessionId(), in_message.data(), in_message.size(), cryptoSession.macAlgorithm(), in_signature.data(), in_signature.size()); Status status = Status::OK; if (res == OEMCrypto_SUCCESS) { match = true; status = Status::OK; } else if (res == OEMCrypto_ERROR_SIGNATURE_FAILURE) { match = false; status = Status::OK; } else { ALOGE("OEMCrypto_Generic_Verify failed with %u", res); match = false; status = mapAndNotifyOfOEMCryptoResult(in_sessionId, res); } *_aidl_return = match; return toNdkScopedAStatus(status); } ::ndk::ScopedAStatus WVDrmPlugin::signRSA(const vector& in_sessionId, const std::string& in_algorithm, const vector& in_message, const vector& in_wrappedkey, vector* _aidl_return) { *_aidl_return = vector(); if (in_sessionId.size() == 0 || in_algorithm.size() == 0 || in_message.size() == 0 || in_wrappedkey.size() == 0) { return toNdkScopedAStatus(Status::BAD_VALUE); } const auto& self = android::IPCThreadState::self(); const char* sid = self->getCallingSid(); if (!sid || (!strstr(sid, ":mediashell_app:") && !strstr(sid, ":mediadrmserver:") && !strstr(sid, ":setupwraith_app:"))) { ALOGE( "Only mediashell/mediadrmserver/setupwraith_app can call signRSA, " "but actually: %s", sid); return toNdkScopedAStatus(Status::ERROR_DRM_UNKNOWN); } const std::string algo(in_algorithm.c_str()); vector 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 std::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& in_sessionId, const vector& 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& 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& 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& 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& 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& 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 sessionId = StrToVector(cdmSessionId); const vector 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 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 sessionId = StrToVector(cdmSessionId); const vector 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 sessionId = StrToVector(cdmSessionId); int64_t newExpiryTimeMilliseconds = newExpiryTimeSeconds == wvcdm::NEVER_EXPIRES ? newExpiryTimeSeconds : newExpiryTimeSeconds * 1000; sendExpirationUpdate(sessionId, newExpiryTimeMilliseconds); } void WVDrmPlugin::OnSessionLostState(const CdmSessionId& cdmSessionId) { const vector sessionId = StrToVector(cdmSessionId); sendSessionLostState(sessionId); } Status WVDrmPlugin::queryProperty(const std::string& property, std::string& stringValue) const { return queryProperty(getRequestedSecurityLevel(), property, stringValue); } Status 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: %u", res); } return mapCdmResponseType(res); } Status WVDrmPlugin::queryProperty(const std::string& property, vector& vector_value) const { std::string string_value; Status status = queryProperty(property, string_value); if (status != Status::OK) return status; vector_value = StrToVector(string_value); return 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); } Status WVDrmPlugin::mapAndNotifyOfCdmResponseType( const vector& sessionId, CdmResponseType res) { notifyOfCdmResponseType(sessionId, res); return mapCdmResponseType(res); } void WVDrmPlugin::notifyOfCdmResponseType(const vector& sessionId, CdmResponseType res) { const vector 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& sessionId, OEMCryptoResult res) { const vector 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::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& 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(&initDataArray[sizeof(nboSize)]); std::string id(idField, kPsshTag.size()); return id == kPsshTag; } Status 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(); } Status WVDrmPlugin::CdmIdentifierBuilder::getCdmIdentifier( CdmIdentifier* identifier) { if (!mIsIdentifierSealed) { Status res = calculateSpoid(); if (res != Status::OK) return res; mIsIdentifierSealed = true; } *identifier = mCdmIdentifier; return Status::OK; } Status WVDrmPlugin::CdmIdentifierBuilder::getDeviceUniqueId(std::string* id) { if (mUseSpoid) { CdmIdentifier identifier; Status res = getCdmIdentifier(&identifier); if (res != Status::OK) return res; *id = identifier.spoid; return Status::OK; } else { return getOemcryptoDeviceId(id); } } Status 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. Status res = getDeviceUniqueId(id); if (res != Status::OK) return res; for (char& c : *id) { c = ~c; } return 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; } Status WVDrmPlugin::CdmIdentifierBuilder::calculateSpoid() { if (!mUseSpoid) return Status::OK; // Calculate SPOID for default security level if appropriate std::string deviceId; if (mParent.getRequestedSecurityLevel() == wvcdm::kLevelDefault) { Status res = getOemcryptoDeviceId(&deviceId); if (res != Status::OK) return res; return 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. Status res = getOemcryptoDeviceId(wvcdm::kLevel3, &deviceId); if (res != Status::OK) return res; std::string spoidL3; res = calculateSpoid(deviceId, &spoidL3); if (res != Status::OK) return res; 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 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 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 Status::OK; } std::string spoidLevelDefault; res = calculateSpoid(deviceIdLevelDefault, &spoidLevelDefault); if (res != Status::OK) { mCdmIdentifier.spoid = spoidL3; return Status::OK; } if (mParent.isProvisioned(wvcdm::kSecurityLevelL1, origin(), spoidLevelDefault, atsc_mode_enabled)) { mCdmIdentifier.spoid = spoidLevelDefault; return Status::OK; } // Not provisioned with CdmIdentifier containing SPOID with L1 or L3 // device ID. Return L3 SPOID. mCdmIdentifier.spoid = spoidL3; return 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(hash), SHA256_DIGEST_LENGTH); return Status::OK; } Status WVDrmPlugin::CdmIdentifierBuilder::getOemcryptoDeviceId( std::string* id) { return mParent.queryProperty(wvcdm::QUERY_KEY_DEVICE_ID, *id); } Status 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