// // Copyright 2013 Google Inc. All Rights Reserved. // //#define LOG_NDEBUG 0 #define LOG_TAG "WVCdm" #include #include "WVDrmPlugin.h" #include #include #include #include #include #include "mapErrors-inl.h" #include "media/stagefright/MediaErrors.h" #include "utils/Errors.h" #include "wv_cdm_constants.h" namespace wvdrm { using namespace android; using namespace std; using namespace wvcdm; static const char* const kResetSecurityLevel = ""; static const char* const kEnable = "enable"; static const char* const kDisable = "disable"; WVDrmPlugin::WVDrmPlugin(WvContentDecryptionModule* cdm, WVGenericCryptoInterface* crypto) : mCDM(cdm), mCrypto(crypto) {} WVDrmPlugin::~WVDrmPlugin() { typedef map::iterator mapIterator; for (mapIterator iter = mCryptoSessions.begin(); iter != mCryptoSessions.end(); ++iter) { bool bRes = mCDM->DetachEventListener(iter->first, this); if (!bRes) { ALOGE("Received failure when trying to detach WVDrmPlugin as an event" "listener."); } CdmResponseType res = mCDM->CloseSession(iter->first); if (!isCdmResponseTypeSuccess(res)) { ALOGE("Failed to close session while destroying WVDrmPlugin"); } } mCryptoSessions.clear(); } status_t WVDrmPlugin::openSession(Vector& sessionId) { CdmSessionId cdmSessionId; CdmResponseType res = mCDM->OpenSession("com.widevine", &mPropertySet, &cdmSessionId); if (!isCdmResponseTypeSuccess(res)) { return mapAndNotifyOfCdmResponseType(sessionId, res); } bool success = false; // Register for events bool listenerAttached = mCDM->AttachEventListener(cdmSessionId, this); if (listenerAttached) { // Construct a CryptoSession CdmQueryMap info; res = mCDM->QueryKeyControlInfo(cdmSessionId, &info); if (isCdmResponseTypeSuccess(res) && info.count(QUERY_KEY_OEMCRYPTO_SESSION_ID)) { OEMCrypto_SESSION oecSessionId; istringstream(info[QUERY_KEY_OEMCRYPTO_SESSION_ID]) >> oecSessionId; mCryptoSessions[cdmSessionId] = CryptoSession(oecSessionId); success = true; } else { ALOGE("Unable to query key control info."); } } else { ALOGE("Received failure when trying to attach WVDrmPlugin as an event" "listener."); } if (success) { // Marshal Session ID sessionId.clear(); sessionId.appendArray(reinterpret_cast(cdmSessionId.data()), cdmSessionId.size()); return android::OK; } else { if (listenerAttached) { mCDM->DetachEventListener(cdmSessionId, this); } 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. return kErrorCDMGeneric; } } } status_t WVDrmPlugin::closeSession(const Vector& sessionId) { CdmSessionId cdmSessionId(sessionId.begin(), sessionId.end()); CdmResponseType res = mCDM->CloseSession(cdmSessionId); if (isCdmResponseTypeSuccess(res)) { mCryptoSessions.erase(cdmSessionId); } return mapAndNotifyOfCdmResponseType(sessionId, res); } status_t WVDrmPlugin::getKeyRequest( const Vector& scope, const Vector& initData, const String8& initDataType, KeyType keyType, const KeyedVector& optionalParameters, Vector& request, String8& defaultUrl) { CdmLicenseType cdmLicenseType; CdmSessionId cdmSessionId; CdmKeySetId cdmKeySetId; if (keyType == kKeyType_Offline) { cdmLicenseType = kLicenseTypeOffline; cdmSessionId.assign(scope.begin(), scope.end()); } else if (keyType == kKeyType_Streaming) { cdmLicenseType = kLicenseTypeStreaming; cdmSessionId.assign(scope.begin(), scope.end()); } else if (keyType == kKeyType_Release) { cdmLicenseType = kLicenseTypeRelease; cdmKeySetId.assign(scope.begin(), scope.end()); } else { return android::ERROR_DRM_CANNOT_HANDLE; } string cdmMimeType = initDataType.string(); // Provide backwards-compatibility for apps that pass non-EME-compatible MIME // types. if (cdmMimeType != wvcdm::ISO_BMFF_VIDEO_MIME_TYPE && cdmMimeType != wvcdm::ISO_BMFF_AUDIO_MIME_TYPE && cdmMimeType != wvcdm::WEBM_VIDEO_MIME_TYPE && cdmMimeType != wvcdm::WEBM_AUDIO_MIME_TYPE) { cdmMimeType = wvcdm::ISO_BMFF_VIDEO_MIME_TYPE; } CdmInitData processedInitData; if (cdmMimeType == wvcdm::ISO_BMFF_VIDEO_MIME_TYPE || cdmMimeType == wvcdm::ISO_BMFF_AUDIO_MIME_TYPE) { // For ISO-BMFF, we need to wrap the init data in a new PSSH header. static const char 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(psshPrefix, sizeof(psshPrefix) / sizeof(char)); processedInitData.append(reinterpret_cast(initData.array()), 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(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(initData.array()), initData.size()); } CdmAppParameterMap cdmParameters; for (size_t i = 0; i < optionalParameters.size(); ++i) { const String8& key = optionalParameters.keyAt(i); const String8& value = optionalParameters.valueAt(i); string cdmKey(key.string(), key.size()); string cdmValue(value.string(), value.size()); cdmParameters[cdmKey] = cdmValue; } CdmKeyMessage keyRequest; string cdmDefaultUrl; CdmResponseType res = mCDM->GenerateKeyRequest(cdmSessionId, cdmKeySetId, cdmMimeType, processedInitData, cdmLicenseType, cdmParameters, &keyRequest, &cdmDefaultUrl); if (isCdmResponseTypeSuccess(res)) { defaultUrl.clear(); defaultUrl.setTo(cdmDefaultUrl.data(), cdmDefaultUrl.size()); request.clear(); request.appendArray(reinterpret_cast(keyRequest.data()), keyRequest.size()); } if (keyType == kKeyType_Release) { // When releasing keys, we do not have a session ID. return mapCdmResponseType(res); } else { // For all other requests, we have a session ID. return mapAndNotifyOfCdmResponseType(scope, res); } } status_t WVDrmPlugin::provideKeyResponse( const Vector& scope, const Vector& response, Vector& keySetId) { CdmSessionId cdmSessionId; CdmKeyResponse cdmResponse(response.begin(), response.end()); CdmKeySetId cdmKeySetId; bool isRequest = (memcmp(scope.array(), SESSION_ID_PREFIX, sizeof(SESSION_ID_PREFIX) - 1) == 0); bool isRelease = (memcmp(scope.array(), KEY_SET_ID_PREFIX, sizeof(KEY_SET_ID_PREFIX) - 1) == 0); if (isRequest) { cdmSessionId.assign(scope.begin(), scope.end()); } else if (isRelease) { cdmKeySetId.assign(scope.begin(), scope.end()); } else { return android::ERROR_DRM_CANNOT_HANDLE; } CdmResponseType res = mCDM->AddKey(cdmSessionId, cdmResponse, &cdmKeySetId); if (isRequest && isCdmResponseTypeSuccess(res)) { keySetId.clear(); keySetId.appendArray(reinterpret_cast(cdmKeySetId.data()), cdmKeySetId.size()); } if (isRelease) { // When releasing keys, we do not have a session ID. return mapCdmResponseType(res); } else { // For all other requests, we have a session ID. status_t status = mapAndNotifyOfCdmResponseType(scope, res); // For "NEED_KEY," we still want to send the notifcation, 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 = android::OK; } return status; } } status_t WVDrmPlugin::removeKeys(const Vector& sessionId) { CdmSessionId cdmSessionId(sessionId.begin(), sessionId.end()); CdmResponseType res = mCDM->CancelKeyRequest(cdmSessionId); return mapAndNotifyOfCdmResponseType(sessionId, res); } status_t WVDrmPlugin::restoreKeys(const Vector& sessionId, const Vector& keySetId) { CdmSessionId cdmSessionId(sessionId.begin(), sessionId.end()); CdmKeySetId cdmKeySetId(keySetId.begin(), keySetId.end()); CdmResponseType res = mCDM->RestoreKey(cdmSessionId, cdmKeySetId); return mapAndNotifyOfCdmResponseType(sessionId, res); } status_t WVDrmPlugin::queryKeyStatus( const Vector& sessionId, KeyedVector& infoMap) const { CdmSessionId cdmSessionId(sessionId.begin(), sessionId.end()); CdmQueryMap cdmLicenseInfo; CdmResponseType res = mCDM->QueryKeyStatus(cdmSessionId, &cdmLicenseInfo); if (isCdmResponseTypeSuccess(res)) { infoMap.clear(); for (CdmQueryMap::const_iterator iter = cdmLicenseInfo.begin(); iter != cdmLicenseInfo.end(); ++iter) { const string& cdmKey = iter->first; const string& cdmValue = iter->second; String8 key(cdmKey.data(), cdmKey.size()); String8 value(cdmValue.data(), cdmValue.size()); infoMap.add(key, value); } } return mapCdmResponseType(res); } status_t WVDrmPlugin::getProvisionRequest(const String8& cert_type, const String8& cert_authority, Vector& request, String8& defaultUrl) { CdmProvisioningRequest cdmProvisionRequest; string cdmDefaultUrl; CdmCertificateType cdmCertType = kCertificateWidevine; if (cert_type == "X.509") { cdmCertType = kCertificateX509; } string cdmCertAuthority = cert_authority.string(); CdmResponseType res = mCDM->GetProvisioningRequest(cdmCertType, cdmCertAuthority, &cdmProvisionRequest, &cdmDefaultUrl); if (isCdmResponseTypeSuccess(res)) { request.clear(); request.appendArray(reinterpret_cast( cdmProvisionRequest.data()), cdmProvisionRequest.size()); defaultUrl.clear(); defaultUrl.setTo(cdmDefaultUrl.data(), cdmDefaultUrl.size()); } return mapCdmResponseType(res); } status_t WVDrmPlugin::provideProvisionResponse( const Vector& response, Vector& certificate, Vector& wrapped_key) { CdmProvisioningResponse cdmResponse(response.begin(), response.end()); string cdmCertificate; string cdmWrappedKey; CdmResponseType res = mCDM->HandleProvisioningResponse(cdmResponse, &cdmCertificate, &cdmWrappedKey); if (isCdmResponseTypeSuccess(res)) { certificate.clear(); certificate.appendArray( reinterpret_cast(cdmCertificate.data()), cdmCertificate.size()); wrapped_key.clear(); wrapped_key.appendArray( reinterpret_cast(cdmWrappedKey.data()), cdmWrappedKey.size()); } return mapCdmResponseType(res); } status_t WVDrmPlugin::getSecureStops(List >& secureStops) { CdmSecureStops cdmSecureStops; CdmResponseType res = mCDM->GetSecureStops(&cdmSecureStops); if (isCdmResponseTypeSuccess(res)) { secureStops.clear(); for (CdmSecureStops::const_iterator iter = cdmSecureStops.begin(); iter != cdmSecureStops.end(); ++iter) { const string& cdmStop = *iter; Vector stop; stop.appendArray(reinterpret_cast(cdmStop.data()), cdmStop.size()); secureStops.push_back(stop); } } return mapCdmResponseType(res); } status_t WVDrmPlugin::releaseSecureStops(const Vector& ssRelease) { CdmSecureStopReleaseMessage cdmMessage(ssRelease.begin(), ssRelease.end()); CdmResponseType res = mCDM->ReleaseSecureStops(cdmMessage); return mapCdmResponseType(res); } status_t WVDrmPlugin::getPropertyString(const String8& name, String8& value) const { if (name == "vendor") { value = "Google"; } else if (name == "version") { value = "1.0"; } else if (name == "description") { value = "Widevine CDM"; } else if (name == "algorithms") { value = "AES/CBC/NoPadding,HmacSHA256"; } else if (name == "securityLevel") { string requestedLevel = mPropertySet.security_level(); if (requestedLevel.length() > 0) { value = requestedLevel.c_str(); } else { CdmQueryMap status; CdmResponseType res = mCDM->QueryStatus(&status); if (!isCdmResponseTypeSuccess(res)) { ALOGE("Error querying CDM status: %u", res); return mapCdmResponseType(res); } else if (!status.count(QUERY_KEY_SECURITY_LEVEL)) { ALOGE("CDM did not report a security level"); return kErrorCDMGeneric; } value = status[QUERY_KEY_SECURITY_LEVEL].c_str(); } } else if (name == "systemId") { CdmQueryMap status; CdmResponseType res = mCDM->QueryStatus(&status); if (res != wvcdm::NO_ERROR) { ALOGE("Error querying CDM status: %u", res); return mapCdmResponseType(res); } else if (!status.count(QUERY_KEY_SYSTEM_ID)) { ALOGE("CDM did not report a system ID"); return kErrorCDMGeneric; } value = status[QUERY_KEY_SYSTEM_ID].c_str(); } 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 { ALOGE("App requested unknown string property %s", name.string()); return android::ERROR_DRM_CANNOT_HANDLE; } return android::OK; } status_t WVDrmPlugin::getPropertyByteArray(const String8& name, Vector& value) const { if (name == "deviceUniqueId") { CdmQueryMap status; CdmResponseType res = mCDM->QueryStatus(&status); if (!isCdmResponseTypeSuccess(res)) { ALOGE("Error querying CDM status: %u", res); return mapCdmResponseType(res); } else if (!status.count(QUERY_KEY_DEVICE_ID)) { ALOGE("CDM did not report a device unique ID"); return kErrorCDMGeneric; } const string& uniqueId = status[QUERY_KEY_DEVICE_ID]; value.clear(); value.appendArray(reinterpret_cast(uniqueId.data()), uniqueId.size()); } else if (name == "provisioningUniqueId") { CdmQueryMap status; CdmResponseType res = mCDM->QueryStatus(&status); if (!isCdmResponseTypeSuccess(res)) { ALOGE("Error querying CDM status: %u", res); return mapCdmResponseType(res); } else if (!status.count(QUERY_KEY_PROVISIONING_ID)) { ALOGE("CDM did not report a provisioning unique ID"); return kErrorCDMGeneric; } const string& uniqueId = status[QUERY_KEY_PROVISIONING_ID]; value.clear(); value.appendArray(reinterpret_cast(uniqueId.data()), uniqueId.size()); } else if (name == "serviceCertificate") { vector cert = mPropertySet.service_certificate(); value.clear(); value.appendArray(&cert[0], cert.size()); } else { ALOGE("App requested unknown byte array property %s", name.string()); return android::ERROR_DRM_CANNOT_HANDLE; } return android::OK; } status_t WVDrmPlugin::setPropertyString(const String8& name, const String8& value) { if (name == "securityLevel") { if (mCryptoSessions.size() == 0) { if (value == QUERY_VALUE_SECURITY_LEVEL_L3.c_str()) { mPropertySet.set_security_level(QUERY_VALUE_SECURITY_LEVEL_L3); } else if (value == kResetSecurityLevel) { mPropertySet.set_security_level(""); } else { ALOGE("App requested invalid security level %s", value.string()); return android::BAD_VALUE; } } else { ALOGE("App tried to change security level while sessions are open."); return kErrorSessionIsOpen; } } 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.string()); return android::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.string()); return android::BAD_VALUE; } } else { ALOGE("App tried to change key sharing while sessions are open."); return kErrorSessionIsOpen; } } else { ALOGE("App set unknown string property %s", name.string()); return android::ERROR_DRM_CANNOT_HANDLE; } return android::OK; } status_t WVDrmPlugin::setPropertyByteArray(const String8& name, const Vector& value) { if (name == "serviceCertificate") { vector cert(value.begin(), value.end()); mPropertySet.set_service_certificate(cert); } else { ALOGE("App set unknown byte array property %s", name.string()); return android::ERROR_DRM_CANNOT_HANDLE; } return android::OK; } status_t WVDrmPlugin::setCipherAlgorithm(const Vector& sessionId, const String8& algorithm) { CdmSessionId cdmSessionId(sessionId.begin(), sessionId.end()); if (!mCryptoSessions.count(cdmSessionId)) { return android::ERROR_DRM_SESSION_NOT_OPENED; } CryptoSession& cryptoSession = mCryptoSessions[cdmSessionId]; if (algorithm == "AES/CBC/NoPadding") { cryptoSession.setCipherAlgorithm(OEMCrypto_AES_CBC_128_NO_PADDING); } else { return android::ERROR_DRM_CANNOT_HANDLE; } return android::OK; } status_t WVDrmPlugin::setMacAlgorithm(const Vector& sessionId, const String8& algorithm) { CdmSessionId cdmSessionId(sessionId.begin(), sessionId.end()); if (!mCryptoSessions.count(cdmSessionId)) { return android::ERROR_DRM_SESSION_NOT_OPENED; } CryptoSession& cryptoSession = mCryptoSessions[cdmSessionId]; if (algorithm == "HmacSHA256") { cryptoSession.setMacAlgorithm(OEMCrypto_HMAC_SHA256); } else { return android::ERROR_DRM_CANNOT_HANDLE; } return android::OK; } status_t WVDrmPlugin::encrypt(const Vector& sessionId, const Vector& keyId, const Vector& input, const Vector& iv, Vector& output) { CdmSessionId cdmSessionId(sessionId.begin(), sessionId.end()); if (!mCryptoSessions.count(cdmSessionId)) { return android::ERROR_DRM_SESSION_NOT_OPENED; } const CryptoSession& cryptoSession = mCryptoSessions[cdmSessionId]; if (cryptoSession.cipherAlgorithm() == kInvalidCrytpoAlgorithm) { return android::NO_INIT; } OEMCryptoResult res = mCrypto->selectKey(cryptoSession.oecSessionId(), keyId.array(), keyId.size()); if (res != OEMCrypto_SUCCESS) { ALOGE("OEMCrypto_SelectKey failed with %u", res); return mapAndNotifyOfOEMCryptoResult(sessionId, res); } output.resize(input.size()); res = mCrypto->encrypt(cryptoSession.oecSessionId(), input.array(), input.size(), iv.array(), cryptoSession.cipherAlgorithm(), output.editArray()); if (res == OEMCrypto_SUCCESS) { return android::OK; } else { ALOGE("OEMCrypto_Generic_Encrypt failed with %u", res); return mapAndNotifyOfOEMCryptoResult(sessionId, res); } } status_t WVDrmPlugin::decrypt(const Vector& sessionId, const Vector& keyId, const Vector& input, const Vector& iv, Vector& output) { CdmSessionId cdmSessionId(sessionId.begin(), sessionId.end()); if (!mCryptoSessions.count(cdmSessionId)) { return android::ERROR_DRM_SESSION_NOT_OPENED; } const CryptoSession& cryptoSession = mCryptoSessions[cdmSessionId]; if (cryptoSession.cipherAlgorithm() == kInvalidCrytpoAlgorithm) { return android::NO_INIT; } OEMCryptoResult res = mCrypto->selectKey(cryptoSession.oecSessionId(), keyId.array(), keyId.size()); if (res != OEMCrypto_SUCCESS) { ALOGE("OEMCrypto_SelectKey failed with %u", res); return mapAndNotifyOfOEMCryptoResult(sessionId, res); } output.resize(input.size()); res = mCrypto->decrypt(cryptoSession.oecSessionId(), input.array(), input.size(), iv.array(), cryptoSession.cipherAlgorithm(), output.editArray()); if (res == OEMCrypto_SUCCESS) { return android::OK; } else { ALOGE("OEMCrypto_Generic_Decrypt failed with %u", res); return mapAndNotifyOfOEMCryptoResult(sessionId, res); } } status_t WVDrmPlugin::sign(const Vector& sessionId, const Vector& keyId, const Vector& message, Vector& signature) { CdmSessionId cdmSessionId(sessionId.begin(), sessionId.end()); if (!mCryptoSessions.count(cdmSessionId)) { return android::ERROR_DRM_SESSION_NOT_OPENED; } const CryptoSession& cryptoSession = mCryptoSessions[cdmSessionId]; if (cryptoSession.macAlgorithm() == kInvalidCrytpoAlgorithm) { return android::NO_INIT; } OEMCryptoResult res = mCrypto->selectKey(cryptoSession.oecSessionId(), keyId.array(), keyId.size()); if (res != OEMCrypto_SUCCESS) { ALOGE("OEMCrypto_SelectKey failed with %u", res); return mapAndNotifyOfOEMCryptoResult(sessionId, res); } size_t signatureSize = 0; res = mCrypto->sign(cryptoSession.oecSessionId(), message.array(), message.size(), cryptoSession.macAlgorithm(), NULL, &signatureSize); if (res != OEMCrypto_ERROR_SHORT_BUFFER) { ALOGE("OEMCrypto_Generic_Sign failed with %u when requesting signature " "size", res); if (res != OEMCrypto_SUCCESS) { return mapAndNotifyOfOEMCryptoResult(sessionId, res); } else { return android::ERROR_DRM_UNKNOWN; } } signature.resize(signatureSize); res = mCrypto->sign(cryptoSession.oecSessionId(), message.array(), message.size(), cryptoSession.macAlgorithm(), signature.editArray(), &signatureSize); if (res == OEMCrypto_SUCCESS) { return android::OK; } else { ALOGE("OEMCrypto_Generic_Sign failed with %u", res); return mapAndNotifyOfOEMCryptoResult(sessionId, res); } } status_t WVDrmPlugin::verify(const Vector& sessionId, const Vector& keyId, const Vector& message, const Vector& signature, bool& match) { CdmSessionId cdmSessionId(sessionId.begin(), sessionId.end()); if (!mCryptoSessions.count(cdmSessionId)) { return android::ERROR_DRM_SESSION_NOT_OPENED; } const CryptoSession& cryptoSession = mCryptoSessions[cdmSessionId]; if (cryptoSession.macAlgorithm() == kInvalidCrytpoAlgorithm) { return android::NO_INIT; } OEMCryptoResult res = mCrypto->selectKey(cryptoSession.oecSessionId(), keyId.array(), keyId.size()); if (res != OEMCrypto_SUCCESS) { ALOGE("OEMCrypto_SelectKey failed with %u", res); return mapAndNotifyOfOEMCryptoResult(sessionId, res); } res = mCrypto->verify(cryptoSession.oecSessionId(), message.array(), message.size(), cryptoSession.macAlgorithm(), signature.array(), signature.size()); if (res == OEMCrypto_SUCCESS) { match = true; return android::OK; } else if (res == OEMCrypto_ERROR_SIGNATURE_FAILURE) { match = false; return android::OK; } else { ALOGE("OEMCrypto_Generic_Verify failed with %u", res); return mapAndNotifyOfOEMCryptoResult(sessionId, res); } } status_t WVDrmPlugin::signRSA(const Vector& sessionId, const String8& algorithm, const Vector& message, const Vector& wrappedKey, Vector& signature) { CdmSessionId cdmSessionId(sessionId.begin(), sessionId.end()); if (!mCryptoSessions.count(cdmSessionId)) { return android::ERROR_DRM_SESSION_NOT_OPENED; } const CryptoSession& cryptoSession = mCryptoSessions[cdmSessionId]; RSA_Padding_Scheme padding_scheme; if (algorithm == "RSASSA-PSS-SHA1") { padding_scheme = kSign_RSASSA_PSS; } else if (algorithm == "PKCS1-BlockType1") { padding_scheme = kSign_PKCS1_Block1; } else { ALOGE("Unknown RSA Algorithm %s", algorithm.string()); return android::ERROR_DRM_CANNOT_HANDLE; } OEMCryptoResult res = mCrypto->loadDeviceRSAKey(cryptoSession.oecSessionId(), wrappedKey.array(), wrappedKey.size()); if (res != OEMCrypto_SUCCESS) { ALOGE("OEMCrypto_LoadDeviceRSAKey failed with %u", res); return mapOEMCryptoResult(res); } size_t signatureSize = 0; res = mCrypto->generateRSASignature(cryptoSession.oecSessionId(), message.array(), message.size(), NULL, &signatureSize, padding_scheme); if (res != OEMCrypto_ERROR_SHORT_BUFFER) { ALOGE("OEMCrypto_GenerateRSASignature failed with %u when requesting " "signature size", res); if (res != OEMCrypto_SUCCESS) { return mapOEMCryptoResult(res); } else { return android::ERROR_DRM_UNKNOWN; } } signature.resize(signatureSize); res = mCrypto->generateRSASignature(cryptoSession.oecSessionId(), message.array(), message.size(), signature.editArray(), &signatureSize, padding_scheme); if (res != OEMCrypto_SUCCESS) { ALOGE("OEMCrypto_GenerateRSASignature failed with %u", res); return mapOEMCryptoResult(res); } return android::OK; } void WVDrmPlugin::onEvent(const CdmSessionId& cdmSessionId, CdmEventType cdmEventType) { Vector sessionId; EventType eventType = kDrmPluginEventVendorDefined; switch (cdmEventType) { case LICENSE_EXPIRED_EVENT: eventType = kDrmPluginEventKeyExpired; break; case LICENSE_RENEWAL_NEEDED_EVENT: eventType = kDrmPluginEventKeyNeeded; break; } sessionId.appendArray(reinterpret_cast(cdmSessionId.data()), cdmSessionId.size()); // Call base-class method with translated event. sendEvent(eventType, 0, &sessionId, NULL); } status_t WVDrmPlugin::mapAndNotifyOfCdmResponseType( const Vector& sessionId, CdmResponseType res) { if (res == wvcdm::NEED_PROVISIONING) { sendEvent(kDrmPluginEventProvisionRequired, 0, &sessionId, NULL); } else if (res == wvcdm::NEED_KEY) { sendEvent(kDrmPluginEventKeyNeeded, 0, &sessionId, NULL); } return mapCdmResponseType(res); } status_t WVDrmPlugin::mapAndNotifyOfOEMCryptoResult( const Vector& sessionId, OEMCryptoResult res) { if (res == OEMCrypto_ERROR_NO_DEVICE_KEY) { sendEvent(kDrmPluginEventProvisionRequired, 0, &sessionId, NULL); } return mapOEMCryptoResult(res); } status_t WVDrmPlugin::mapOEMCryptoResult(OEMCryptoResult res) { switch (res) { case OEMCrypto_SUCCESS: return android::OK; case OEMCrypto_ERROR_SIGNATURE_FAILURE: return android::ERROR_DRM_TAMPER_DETECTED; case OEMCrypto_ERROR_SHORT_BUFFER: return kErrorIncorrectBufferSize; case OEMCrypto_ERROR_NO_DEVICE_KEY: return android::ERROR_DRM_NOT_PROVISIONED; case OEMCrypto_ERROR_INVALID_SESSION: return android::ERROR_DRM_SESSION_NOT_OPENED; case OEMCrypto_ERROR_TOO_MANY_SESSIONS: return kErrorTooManySessions; case OEMCrypto_ERROR_INVALID_RSA_KEY: return kErrorInvalidKey; case OEMCrypto_ERROR_INSUFFICIENT_RESOURCES: return android::ERROR_DRM_RESOURCE_BUSY; case OEMCrypto_ERROR_NOT_IMPLEMENTED: return android::ERROR_DRM_CANNOT_HANDLE; case OEMCrypto_ERROR_UNKNOWN_FAILURE: case OEMCrypto_ERROR_OPEN_SESSION_FAILED: return android::ERROR_DRM_UNKNOWN; default: return android::UNKNOWN_ERROR; } } } // namespace wvdrm