diff options
| author | Edwin Wong <edwinwong@google.com> | 2018-10-11 12:00:30 -0700 |
|---|---|---|
| committer | Edwin Wong <edwinwong@google.com> | 2018-10-11 17:38:23 -0700 |
| commit | 92f92de097b9d0bbd6a510edaec7b21b04a9268e (patch) | |
| tree | afb579b858a14df13ac1f2a117960ce877792a65 /drm | |
| parent | a1e48aa38987cd0460bbfc57e2b0fc513df17283 (diff) | |
| download | frameworks_av-92f92de097b9d0bbd6a510edaec7b21b04a9268e.tar.gz frameworks_av-92f92de097b9d0bbd6a510edaec7b21b04a9268e.tar.bz2 frameworks_av-92f92de097b9d0bbd6a510edaec7b21b04a9268e.zip | |
Add KEY_TYPE_OFFLINE support.
Clearkey now supports KeyType Streaming and Offline.
Test: CTS test
android.media.cts.MediaDrmClearkeyTest#testClearKeyPlaybackOfflineCenc
android.media.cts.MediaDrmClearkeyTest
Test: VTS tests
adb shell /data/nativetest/VtsHalDrmV1_0TargetTest/VtsHalDrmV1_0TargetTest
adb shell /data/nativetest/VtsHalDrmV1_1TargetTest/VtsHalDrmV1_1TargetTest
bug: 38112718
Change-Id: I34c788cd9457c6756de8bca1b3b22b9f1ff0655a
Diffstat (limited to 'drm')
16 files changed, 735 insertions, 70 deletions
diff --git a/drm/mediadrm/plugins/clearkey/common/Utils.cpp b/drm/mediadrm/plugins/clearkey/common/Utils.cpp index 93c643b636..d48b0a8c91 100644 --- a/drm/mediadrm/plugins/clearkey/common/Utils.cpp +++ b/drm/mediadrm/plugins/clearkey/common/Utils.cpp @@ -27,4 +27,18 @@ bool operator<(const Vector<uint8_t> &lhs, const Vector<uint8_t> &rhs) { return memcmp((void *)lhs.array(), (void *)rhs.array(), rhs.size()) < 0; } +std::string ByteArrayToHexString(const uint8_t* in_buffer, size_t length) { + static const char kHexChars[] = "0123456789ABCDEF"; + + // Each input byte creates two output hex characters. + std::string out_buffer(length * 2, '\0'); + + for (size_t i = 0; i < length; ++i) { + char byte = in_buffer[i]; + out_buffer[(i * 2)] = kHexChars[(byte >> 4) & 0xf]; + out_buffer[(i * 2) + 1] = kHexChars[byte & 0xf]; + } + return out_buffer; +} + } // namespace android diff --git a/drm/mediadrm/plugins/clearkey/common/include/Utils.h b/drm/mediadrm/plugins/clearkey/common/include/Utils.h index 25431247e0..aa571c0874 100644 --- a/drm/mediadrm/plugins/clearkey/common/include/Utils.h +++ b/drm/mediadrm/plugins/clearkey/common/include/Utils.h @@ -17,14 +17,16 @@ #ifndef CLEARKEY_UTILS_H_ #define CLEARKEY_UTILS_H_ +#include <string> #include <utils/Vector.h> +namespace android { // Add a comparison operator for this Vector specialization so that it can be // used as a key in a KeyedVector. -namespace android { - bool operator<(const Vector<uint8_t> &lhs, const Vector<uint8_t> &rhs); +std::string ByteArrayToHexString(const uint8_t* in_buffer, size_t length); + } // namespace android #define UNUSED(x) (void)(x); diff --git a/drm/mediadrm/plugins/clearkey/hidl/Android.bp b/drm/mediadrm/plugins/clearkey/hidl/Android.bp index 341d4f645d..edf818e841 100644 --- a/drm/mediadrm/plugins/clearkey/hidl/Android.bp +++ b/drm/mediadrm/plugins/clearkey/hidl/Android.bp @@ -25,10 +25,12 @@ cc_binary { "CreatePluginFactories.cpp", "CryptoFactory.cpp", "CryptoPlugin.cpp", + "DeviceFiles.cpp", "DrmFactory.cpp", "DrmPlugin.cpp", "InitDataParser.cpp", "JsonWebKey.cpp", + "MemoryFileSystem.cpp", "Session.cpp", "SessionLibrary.cpp", "service.cpp", @@ -49,11 +51,13 @@ cc_binary { "libhidlmemory", "libhidltransport", "liblog", + "libprotobuf-cpp-lite", "libutils", ], static_libs: [ "libclearkeycommon", + "libclearkeydevicefiles-protos", "libjsmn", ], @@ -66,3 +70,13 @@ cc_binary { }, } +cc_library_static { + name: "libclearkeydevicefiles-protos", + vendor: true, + + proto: { + export_proto_headers: true, + type: "lite", + }, + srcs: ["protos/DeviceFiles.proto"], +} diff --git a/drm/mediadrm/plugins/clearkey/hidl/DeviceFiles.cpp b/drm/mediadrm/plugins/clearkey/hidl/DeviceFiles.cpp new file mode 100644 index 0000000000..8fafce21c2 --- /dev/null +++ b/drm/mediadrm/plugins/clearkey/hidl/DeviceFiles.cpp @@ -0,0 +1,240 @@ +// Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary +// source code may only be used and distributed under the Widevine Master +// License Agreement. + +#include <utils/Log.h> + +#include <string> +#include <sys/stat.h> + +#include "DeviceFiles.h" +#include "Utils.h" + +#include <openssl/sha.h> + +// Protobuf generated classes. +using android::hardware::drm::V1_1::clearkey::OfflineFile; +using android::hardware::drm::V1_1::clearkey::HashedFile; +using android::hardware::drm::V1_1::clearkey::License; +using android::hardware::drm::V1_1::clearkey::License_LicenseState_ACTIVE; +using android::hardware::drm::V1_1::clearkey::License_LicenseState_RELEASING; + +namespace { +const char kLicenseFileNameExt[] = ".lic"; + +bool Hash(const std::string& data, std::string* hash) { + if (!hash) return false; + + hash->resize(SHA256_DIGEST_LENGTH); + + const unsigned char* input = reinterpret_cast<const unsigned char*>(data.data()); + unsigned char* output = reinterpret_cast<unsigned char*>(&(*hash)[0]); + SHA256(input, data.size(), output); + return true; +} + +} // namespace + +namespace android { +namespace hardware { +namespace drm { +namespace V1_1 { +namespace clearkey { + +bool DeviceFiles::StoreLicense( + const std::string& keySetId, LicenseState state, + const std::string& licenseResponse) { + + OfflineFile file; + file.set_type(OfflineFile::LICENSE); + file.set_version(OfflineFile::VERSION_1); + + License* license = file.mutable_license(); + switch (state) { + case kLicenseStateActive: + license->set_state(License_LicenseState_ACTIVE); + license->set_license(licenseResponse); + break; + case kLicenseStateReleasing: + license->set_state(License_LicenseState_RELEASING); + break; + default: + ALOGW("StoreLicense: Unknown license state: %u", state); + return false; + } + + std::string serializedFile; + file.SerializeToString(&serializedFile); + + return StoreFileWithHash(keySetId + kLicenseFileNameExt, serializedFile); +} + +bool DeviceFiles::StoreFileWithHash(const std::string& fileName, + const std::string& serializedFile) { + std::string hash; + if (!Hash(serializedFile, &hash)) { + ALOGE("StoreFileWithHash: Failed to compute hash"); + return false; + } + + HashedFile hashFile; + hashFile.set_file(serializedFile); + hashFile.set_hash(hash); + + std::string serializedHashFile; + hashFile.SerializeToString(&serializedHashFile); + + return StoreFileRaw(fileName, serializedHashFile); +} + +bool DeviceFiles::StoreFileRaw(const std::string& fileName, const std::string& serializedHashFile) { + MemoryFileSystem::MemoryFile memFile; + memFile.setFileName(fileName); + memFile.setContent(serializedHashFile); + memFile.setFileSize(serializedHashFile.size()); + size_t len = mFileHandle.Write(fileName, memFile); + + if (len != static_cast<size_t>(serializedHashFile.size())) { + ALOGE("StoreFileRaw: Failed to write %s", fileName.c_str()); + ALOGD("StoreFileRaw: expected=%zd, actual=%zu", serializedHashFile.size(), len); + return false; + } + + ALOGD("StoreFileRaw: wrote %zu bytes to %s", serializedHashFile.size(), fileName.c_str()); + return true; +} + +bool DeviceFiles::RetrieveLicense( + const std::string& keySetId, LicenseState* state, std::string* offlineLicense) { + OfflineFile file; + + if (!RetrieveHashedFile(keySetId + kLicenseFileNameExt, &file)) { + return false; + } + + if (file.type() != OfflineFile::LICENSE) { + ALOGE("RetrieveLicense: Invalid file type"); + return false; + } + + if (file.version() != OfflineFile::VERSION_1) { + ALOGE("RetrieveLicense: Invalid file version"); + return false; + } + + if (!file.has_license()) { + ALOGE("RetrieveLicense: License not present"); + return false; + } + + License license = file.license(); + + switch (license.state()) { + case License_LicenseState_ACTIVE: + *state = kLicenseStateActive; + break; + case License_LicenseState_RELEASING: + *state = kLicenseStateReleasing; + break; + default: + ALOGW("RetrieveLicense: Unrecognized license state: %u", + kLicenseStateUnknown); + *state = kLicenseStateUnknown; + break; + } + + *offlineLicense = license.license(); + return true; +} + +bool DeviceFiles::DeleteAllLicenses() { + return mFileHandle.RemoveAllFiles(); +} + +bool DeviceFiles::LicenseExists(const std::string& keySetId) { + return mFileHandle.FileExists(keySetId + kLicenseFileNameExt); +} + +bool DeviceFiles::RetrieveHashedFile(const std::string& fileName, OfflineFile* deSerializedFile) { + if (!deSerializedFile) { + ALOGE("RetrieveHashedFile: invalid file parameter"); + return false; + } + + if (!FileExists(fileName)) { + ALOGE("RetrieveHashedFile: %s does not exist", fileName.c_str()); + return false; + } + + ssize_t bytes = GetFileSize(fileName); + if (bytes <= 0) { + ALOGE("RetrieveHashedFile: invalid file size: %s", fileName.c_str()); + // Remove the corrupted file so the caller will not get the same error + // when trying to access the file repeatedly, causing the system to stall. + RemoveFile(fileName); + return false; + } + + std::string serializedHashFile; + serializedHashFile.resize(bytes); + bytes = mFileHandle.Read(fileName, &serializedHashFile); + + if (bytes != static_cast<ssize_t>(serializedHashFile.size())) { + ALOGE("RetrieveHashedFile: Failed to read from %s", fileName.c_str()); + ALOGV("RetrieveHashedFile: expected: %zd, actual: %zd", serializedHashFile.size(), bytes); + // Remove the corrupted file so the caller will not get the same error + // when trying to access the file repeatedly, causing the system to stall. + RemoveFile(fileName); + return false; + } + + ALOGV("RetrieveHashedFile: read %zd from %s", bytes, fileName.c_str()); + + HashedFile hashFile; + if (!hashFile.ParseFromString(serializedHashFile)) { + ALOGE("RetrieveHashedFile: Unable to parse hash file"); + // Remove corrupt file. + RemoveFile(fileName); + return false; + } + + std::string hash; + if (!Hash(hashFile.file(), &hash)) { + ALOGE("RetrieveHashedFile: Hash computation failed"); + return false; + } + + if (hash != hashFile.hash()) { + ALOGE("RetrieveHashedFile: Hash mismatch"); + // Remove corrupt file. + RemoveFile(fileName); + return false; + } + + if (!deSerializedFile->ParseFromString(hashFile.file())) { + ALOGE("RetrieveHashedFile: Unable to parse file"); + // Remove corrupt file. + RemoveFile(fileName); + return false; + } + + return true; +} + +bool DeviceFiles::FileExists(const std::string& fileName) const { + return mFileHandle.FileExists(fileName); +} + +bool DeviceFiles::RemoveFile(const std::string& fileName) { + return mFileHandle.RemoveFile(fileName); +} + +ssize_t DeviceFiles::GetFileSize(const std::string& fileName) const { + return mFileHandle.GetFileSize(fileName); +} + +} // namespace clearkey +} // namespace V1_1 +} // namespace drm +} // namespace hardware +} // namespace android diff --git a/drm/mediadrm/plugins/clearkey/hidl/DrmPlugin.cpp b/drm/mediadrm/plugins/clearkey/hidl/DrmPlugin.cpp index d04d97648d..59dfb89aef 100644 --- a/drm/mediadrm/plugins/clearkey/hidl/DrmPlugin.cpp +++ b/drm/mediadrm/plugins/clearkey/hidl/DrmPlugin.cpp @@ -25,11 +25,15 @@ #include "ClearKeyDrmProperties.h" #include "Session.h" #include "TypeConvert.h" +#include "Utils.h" namespace { +const std::string kKeySetIdPrefix("ckid"); +const int kKeySetIdLength = 16; const int kSecureStopIdStart = 100; +const std::string kOfflineLicense("\"type\":\"persistent-license\""); const std::string kStreaming("Streaming"); -const std::string kOffline("Offline"); +const std::string kTemporaryLicense("\"type\":\"temporary\""); const std::string kTrue("True"); const std::string kQueryKeyLicenseType("LicenseType"); @@ -66,6 +70,7 @@ DrmPlugin::DrmPlugin(SessionLibrary* sessionLibrary) mPlayPolicy.clear(); initProperties(); mSecureStops.clear(); + std::srand(std::time(nullptr)); } void DrmPlugin::initProperties() { @@ -147,25 +152,53 @@ Status DrmPlugin::getKeyRequestCommon(const hidl_vec<uint8_t>& scope, std::string *defaultUrl) { UNUSED(optionalParameters); + // GetKeyRequestOfflineKeyTypeNotSupported() in vts 1.0 and 1.1 expects + // KeyType::OFFLINE to return ERROR_DRM_CANNOT_HANDLE in clearkey plugin. + // Those tests pass in an empty initData, we use the empty initData to + // signal the specific use case. + if (keyType == KeyType::OFFLINE && 0 == initData.size()) { + return Status::ERROR_DRM_CANNOT_HANDLE; + } + *defaultUrl = ""; *keyRequestType = KeyRequestType::UNKNOWN; *request = std::vector<uint8_t>(); - if (scope.size() == 0) { + if (scope.size() == 0 || + (keyType != KeyType::STREAMING && + keyType != KeyType::OFFLINE && + keyType != KeyType::RELEASE)) { return Status::BAD_VALUE; } - if (keyType != KeyType::STREAMING) { - return Status::ERROR_DRM_CANNOT_HANDLE; + const std::vector<uint8_t> scopeId = toVector(scope); + sp<Session> session; + if (keyType == KeyType::STREAMING || keyType == KeyType::OFFLINE) { + std::vector<uint8_t> sessionId(scopeId.begin(), scopeId.end()); + session = mSessionLibrary->findSession(sessionId); + if (!session.get()) { + return Status::ERROR_DRM_SESSION_NOT_OPENED; + } + *keyRequestType = KeyRequestType::INITIAL; } - sp<Session> session = mSessionLibrary->findSession(toVector(scope)); - if (!session.get()) { - return Status::ERROR_DRM_SESSION_NOT_OPENED; + Status status = session->getKeyRequest(initData, mimeType, keyType, request); + + if (keyType == KeyType::RELEASE) { + std::vector<uint8_t> keySetId(scopeId.begin(), scopeId.end()); + std::string requestString(request->begin(), request->end()); + if (requestString.find(kOfflineLicense) != std::string::npos) { + std::string emptyResponse; + std::string keySetIdString(keySetId.begin(), keySetId.end()); + if (!mFileHandle.StoreLicense(keySetIdString, + DeviceFiles::kLicenseStateReleasing, + emptyResponse)) { + ALOGE("Problem releasing offline license"); + return Status::ERROR_DRM_UNKNOWN; + } + } + *keyRequestType = KeyRequestType::RELEASE; } - - Status status = session->getKeyRequest(initData, mimeType, request); - *keyRequestType = KeyRequestType::INITIAL; return status; } @@ -227,6 +260,30 @@ void DrmPlugin::setPlayPolicy() { mPlayPolicy.push_back(policy); } +bool DrmPlugin::makeKeySetId(std::string* keySetId) { + if (!keySetId) { + ALOGE("keySetId destination not provided"); + return false; + } + std::vector<uint8_t> ksid(kKeySetIdPrefix.begin(), kKeySetIdPrefix.end()); + ksid.resize(kKeySetIdLength); + std::vector<uint8_t> randomData((kKeySetIdLength - kKeySetIdPrefix.size()) / 2, 0); + + while (keySetId->empty()) { + for (auto itr = randomData.begin(); itr != randomData.end(); ++itr) { + *itr = std::rand() % 0xff; + } + *keySetId = kKeySetIdPrefix + ByteArrayToHexString( + reinterpret_cast<const uint8_t*>(randomData.data()), randomData.size()); + if (mFileHandle.LicenseExists(*keySetId)) { + // collision, regenerate + ALOGV("Retry generating KeySetId"); + keySetId->clear(); + } + } + return true; +} + Return<void> DrmPlugin::provideKeyResponse( const hidl_vec<uint8_t>& scope, const hidl_vec<uint8_t>& response, @@ -237,44 +294,107 @@ Return<void> DrmPlugin::provideKeyResponse( return Void(); } - sp<Session> session = mSessionLibrary->findSession(toVector(scope)); - if (!session.get()) { - _hidl_cb(Status::ERROR_DRM_SESSION_NOT_OPENED, hidl_vec<uint8_t>()); - return Void(); - } - - setPlayPolicy(); - std::vector<uint8_t> keySetId; - keySetId.clear(); + std::string responseString( + reinterpret_cast<const char*>(response.data()), response.size()); + const std::vector<uint8_t> scopeId = toVector(scope); + std::vector<uint8_t> sessionId; + std::string keySetId; - Status status = session->provideKeyResponse(response); - if (status == Status::OK) { - // Test calling AMediaDrm listeners. - sendEvent(EventType::VENDOR_DEFINED, toVector(scope), toVector(scope)); - - sendExpirationUpdate(toVector(scope), 100); + Status status = Status::OK; + bool isOfflineLicense = responseString.find(kOfflineLicense) != std::string::npos; + bool isRelease = (memcmp(scopeId.data(), kKeySetIdPrefix.data(), kKeySetIdPrefix.size()) == 0); + if (isRelease) { + keySetId.assign(scopeId.begin(), scopeId.end()); + } else { + sessionId.assign(scopeId.begin(), scopeId.end()); + sp<Session> session = mSessionLibrary->findSession(sessionId); + if (!session.get()) { + _hidl_cb(Status::ERROR_DRM_SESSION_NOT_OPENED, hidl_vec<uint8_t>()); + return Void(); + } - std::vector<KeyStatus> keysStatus; - KeyStatus keyStatus; + setPlayPolicy(); + // non offline license returns empty keySetId + keySetId.clear(); + + status = session->provideKeyResponse(response); + if (status == Status::OK) { + if (isOfflineLicense) { + if (!makeKeySetId(&keySetId)) { + _hidl_cb(Status::ERROR_DRM_UNKNOWN, hidl_vec<uint8_t>()); + return Void(); + } + bool ok = mFileHandle.StoreLicense( + keySetId, + DeviceFiles::kLicenseStateActive, + std::string(response.begin(), response.end())); + if (!ok) { + ALOGE("Failed to store offline license"); + } + } + + // Test calling AMediaDrm listeners. + sendEvent(EventType::VENDOR_DEFINED, sessionId, sessionId); + + sendExpirationUpdate(sessionId, 100); + + std::vector<KeyStatus> keysStatus; + KeyStatus keyStatus; + + std::vector<uint8_t> keyId1 = { 0xA, 0xB, 0xC }; + keyStatus.keyId = keyId1; + keyStatus.type = V1_0::KeyStatusType::USABLE; + keysStatus.push_back(keyStatus); + + std::vector<uint8_t> keyId2 = { 0xD, 0xE, 0xF }; + keyStatus.keyId = keyId2; + keyStatus.type = V1_0::KeyStatusType::EXPIRED; + keysStatus.push_back(keyStatus); + + sendKeysChange(sessionId, keysStatus, true); + + installSecureStop(sessionId); + } else { + ALOGE("Failed to add key, error=%d", status); + } + } // keyType::STREAMING || keyType::OFFLINE - std::vector<uint8_t> keyId1 = { 0xA, 0xB, 0xC }; - keyStatus.keyId = keyId1; - keyStatus.type = V1_0::KeyStatusType::USABLE; - keysStatus.push_back(keyStatus); + std::vector<uint8_t> keySetIdVec(keySetId.begin(), keySetId.end()); + _hidl_cb(status, toHidlVec(keySetIdVec)); + return Void(); +} - std::vector<uint8_t> keyId2 = { 0xD, 0xE, 0xF }; - keyStatus.keyId = keyId2; - keyStatus.type = V1_0::KeyStatusType::EXPIRED; - keysStatus.push_back(keyStatus); +Return<Status> DrmPlugin::restoreKeys( + const hidl_vec<uint8_t>& sessionId, const hidl_vec<uint8_t>& keySetId) { + if (sessionId.size() == 0 || keySetId.size() == 0) { + return Status::BAD_VALUE; + } - sendKeysChange(toVector(scope), keysStatus, true); - } + DeviceFiles::LicenseState licenseState; + std::string keySetIdString(keySetId.begin(), keySetId.end()); + std::string offlineLicense; + Status status = Status::OK; + if (!mFileHandle.RetrieveLicense(keySetIdString, &licenseState, &offlineLicense)) { + ALOGE("Failed to restore offline license"); + return Status::ERROR_DRM_NO_LICENSE; + } - installSecureStop(scope); + if (DeviceFiles::kLicenseStateUnknown == licenseState || + DeviceFiles::kLicenseStateReleasing == licenseState) { + ALOGE("Invalid license state=%d", licenseState); + return Status::ERROR_DRM_NO_LICENSE; + } - // Returns status and empty keySetId - _hidl_cb(status, toHidlVec(keySetId)); - return Void(); + sp<Session> session = mSessionLibrary->findSession(toVector(sessionId)); + if (!session.get()) { + return Status::ERROR_DRM_SESSION_NOT_OPENED; + } + status = session->provideKeyResponse(std::vector<uint8_t>(offlineLicense.begin(), + offlineLicense.end())); + if (status != Status::OK) { + ALOGE("Failed to restore keys"); + } + return status; } Return<void> DrmPlugin::getPropertyString( diff --git a/drm/mediadrm/plugins/clearkey/hidl/InitDataParser.cpp b/drm/mediadrm/plugins/clearkey/hidl/InitDataParser.cpp index e2bb6517ea..dd1689fb1a 100644 --- a/drm/mediadrm/plugins/clearkey/hidl/InitDataParser.cpp +++ b/drm/mediadrm/plugins/clearkey/hidl/InitDataParser.cpp @@ -45,21 +45,22 @@ std::vector<uint8_t> StrToVector(const std::string& str) { } Status InitDataParser::parse(const std::vector<uint8_t>& initData, - const std::string& type, + const std::string& mimeType, + V1_0::KeyType keyType, std::vector<uint8_t>* licenseRequest) { // Build a list of the key IDs std::vector<const uint8_t*> keyIds; - if (type == kIsoBmffVideoMimeType || - type == kIsoBmffAudioMimeType || - type == kCencInitDataFormat) { + if (mimeType == kIsoBmffVideoMimeType || + mimeType == kIsoBmffAudioMimeType || + mimeType == kCencInitDataFormat) { Status res = parsePssh(initData, &keyIds); if (res != Status::OK) { return res; } - } else if (type == kWebmVideoMimeType || - type == kWebmAudioMimeType || - type == kWebmInitDataFormat) { + } else if (mimeType == kWebmVideoMimeType || + mimeType == kWebmAudioMimeType || + mimeType == kWebmInitDataFormat) { // WebM "init data" is just a single key ID if (initData.size() != kKeyIdSize) { return Status::ERROR_DRM_CANNOT_HANDLE; @@ -69,8 +70,12 @@ Status InitDataParser::parse(const std::vector<uint8_t>& initData, return Status::ERROR_DRM_CANNOT_HANDLE; } + if (keyType == V1_0::KeyType::RELEASE) { + // restore key + } + // Build the request - std::string requestJson = generateRequest(keyIds); + std::string requestJson = generateRequest(keyType, keyIds); std::vector<uint8_t> requestJsonVec = StrToVector(requestJson); licenseRequest->clear(); @@ -131,9 +136,11 @@ Status InitDataParser::parsePssh(const std::vector<uint8_t>& initData, return Status::OK; } -std::string InitDataParser::generateRequest(const std::vector<const uint8_t*>& keyIds) { +std::string InitDataParser::generateRequest(V1_0::KeyType keyType, + const std::vector<const uint8_t*>& keyIds) { const std::string kRequestPrefix("{\"kids\":["); - const std::string kRequestSuffix("],\"type\":\"temporary\"}"); + const std::string kTemporarySession("],\"type\":\"temporary\"}"); + const std::string kPersistentSession("],\"type\":\"persistent-license\"}"); std::string request(kRequestPrefix); std::string encodedId; @@ -147,7 +154,11 @@ std::string InitDataParser::generateRequest(const std::vector<const uint8_t*>& k request.append(encodedId); request.push_back('\"'); } - request.append(kRequestSuffix); + if (keyType == V1_0::KeyType::STREAMING) { + request.append(kTemporarySession); + } else if (keyType == V1_0::KeyType::OFFLINE) { + request.append(kPersistentSession); + } // Android's Base64 encoder produces padding. EME forbids padding. const char kBase64Padding = '='; diff --git a/drm/mediadrm/plugins/clearkey/hidl/JsonWebKey.cpp b/drm/mediadrm/plugins/clearkey/hidl/JsonWebKey.cpp index cccb41ecc4..1c32b1be93 100644 --- a/drm/mediadrm/plugins/clearkey/hidl/JsonWebKey.cpp +++ b/drm/mediadrm/plugins/clearkey/hidl/JsonWebKey.cpp @@ -22,12 +22,15 @@ #include "Base64.h" namespace { +const std::string kBase64Padding("="); const std::string kKeysTag("keys"); const std::string kKeyTypeTag("kty"); -const std::string kSymmetricKeyValue("oct"); const std::string kKeyTag("k"); const std::string kKeyIdTag("kid"); -const std::string kBase64Padding("="); +const std::string kMediaSessionType("type"); +const std::string kPersistentLicenseSession("persistent-license"); +const std::string kSymmetricKeyValue("oct"); +const std::string kTemporaryLicenseSession("temporary"); } namespace android { diff --git a/drm/mediadrm/plugins/clearkey/hidl/MemoryFileSystem.cpp b/drm/mediadrm/plugins/clearkey/hidl/MemoryFileSystem.cpp new file mode 100644 index 0000000000..47f188da18 --- /dev/null +++ b/drm/mediadrm/plugins/clearkey/hidl/MemoryFileSystem.cpp @@ -0,0 +1,81 @@ +// Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary +// source code may only be used and distributed under the Widevine Master +// License Agreement. + +#include <utils/Log.h> + +#include <string> + +#include "MemoryFileSystem.h" +#include "Utils.h" + +namespace android { +namespace hardware { +namespace drm { +namespace V1_1 { +namespace clearkey { + +std::string MemoryFileSystem::GetFileName(const std::string& path) { + size_t index = path.find_last_of("/"); + if (index != std::string::npos) { + return path.substr(index+1); + } else { + return path; + } +} + +bool MemoryFileSystem::FileExists(const std::string& fileName) const { + auto result = mMemoryFileSystem.find(fileName); + return result != mMemoryFileSystem.end(); +} + +ssize_t MemoryFileSystem::GetFileSize(const std::string& fileName) const { + auto result = mMemoryFileSystem.find(fileName); + if (result != mMemoryFileSystem.end()) { + return static_cast<ssize_t>(result->second.getFileSize()); + } else { + ALOGE("Failed to get size for %s", fileName.c_str()); + return -1; + } +} + +size_t MemoryFileSystem::Read(const std::string& path, std::string* buffer) { + std::string key = GetFileName(path); + auto result = mMemoryFileSystem.find(key); + if (result != mMemoryFileSystem.end()) { + std::string serializedHashFile = result->second.getContent(); + buffer->assign(serializedHashFile); + return buffer->size(); + } else { + ALOGE("Failed to read from %s", path.c_str()); + return -1; + } +} + +size_t MemoryFileSystem::Write(const std::string& path, const MemoryFile& memoryFile) { + std::string key = GetFileName(path); + mMemoryFileSystem.insert(std::pair<std::string, MemoryFile>(key, memoryFile)); + return memoryFile.getFileSize(); +} + +bool MemoryFileSystem::RemoveFile(const std::string& fileName) { + auto result = mMemoryFileSystem.find(fileName); + if (result != mMemoryFileSystem.end()) { + mMemoryFileSystem.erase(result); + return true; + } else { + ALOGE("Cannot find license to remove: %s", fileName.c_str()); + return false; + } +} + +bool MemoryFileSystem::RemoveAllFiles() { + mMemoryFileSystem.clear(); + return mMemoryFileSystem.empty(); +} + +} // namespace clearkey +} // namespace V1_1 +} // namespace drm +} // namespace hardware +} // namespace android diff --git a/drm/mediadrm/plugins/clearkey/hidl/Session.cpp b/drm/mediadrm/plugins/clearkey/hidl/Session.cpp index 07c9269c68..41a837427f 100644 --- a/drm/mediadrm/plugins/clearkey/hidl/Session.cpp +++ b/drm/mediadrm/plugins/clearkey/hidl/Session.cpp @@ -42,9 +42,10 @@ using android::Mutex; Status Session::getKeyRequest( const std::vector<uint8_t>& initData, const std::string& mimeType, + V1_0::KeyType keyType, std::vector<uint8_t>* keyRequest) const { InitDataParser parser; - return parser.parse(initData, mimeType, keyRequest); + return parser.parse(initData, mimeType, keyType, keyRequest); } Status Session::provideKeyResponse(const std::vector<uint8_t>& response) { diff --git a/drm/mediadrm/plugins/clearkey/hidl/include/ClearKeyDrmProperties.h b/drm/mediadrm/plugins/clearkey/hidl/include/ClearKeyDrmProperties.h index cad9222dd4..3d6a4a9cd0 100644 --- a/drm/mediadrm/plugins/clearkey/hidl/include/ClearKeyDrmProperties.h +++ b/drm/mediadrm/plugins/clearkey/hidl/include/ClearKeyDrmProperties.h @@ -28,7 +28,7 @@ namespace clearkey { static const std::string kVendorKey("vendor"); static const std::string kVendorValue("Google"); static const std::string kVersionKey("version"); -static const std::string kVersionValue("1.1"); +static const std::string kVersionValue("1.2"); static const std::string kPluginDescriptionKey("description"); static const std::string kPluginDescriptionValue("ClearKey CDM"); static const std::string kAlgorithmsKey("algorithms"); diff --git a/drm/mediadrm/plugins/clearkey/hidl/include/DeviceFiles.h b/drm/mediadrm/plugins/clearkey/hidl/include/DeviceFiles.h new file mode 100644 index 0000000000..a201e884ac --- /dev/null +++ b/drm/mediadrm/plugins/clearkey/hidl/include/DeviceFiles.h @@ -0,0 +1,67 @@ +// Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary +// source code may only be used and distributed under the Widevine Master +// License Agreement. +// +#ifndef CLEARKEY_DEVICE_FILES_H_ +#define CLEARKEY_DEVICE_FILES_H_ + +#include <errno.h> +#include <stdio.h> +#include <unistd.h> + +#include <set> +#include <string> +#include <vector> + +#include "protos/DeviceFiles.pb.h" +#include "ClearKeyTypes.h" +#include "MemoryFileSystem.h" + +namespace android { +namespace hardware { +namespace drm { +namespace V1_1 { +namespace clearkey { + +class DeviceFiles { + public: + typedef enum { + kLicenseStateActive, + kLicenseStateReleasing, + kLicenseStateUnknown, + } LicenseState; + + DeviceFiles() {}; + virtual ~DeviceFiles() {}; + + virtual bool StoreLicense(const std::string& keySetId, LicenseState state, + const std::string& keyResponse); + + virtual bool RetrieveLicense( + const std::string& key_set_id, LicenseState* state, std::string* offlineLicense); + + virtual bool LicenseExists(const std::string& keySetId); + + virtual bool DeleteAllLicenses(); + + private: + bool FileExists(const std::string& path) const; + ssize_t GetFileSize(const std::string& fileName) const; + bool RemoveFile(const std::string& fileName); + + bool RetrieveHashedFile(const std::string& fileName, OfflineFile* deSerializedFile); + bool StoreFileRaw(const std::string& fileName, const std::string& serializedFile); + bool StoreFileWithHash(const std::string& fileName, const std::string& serializedFile); + + MemoryFileSystem mFileHandle; + + CLEARKEY_DISALLOW_COPY_AND_ASSIGN(DeviceFiles); +}; + +} // namespace clearkey +} // namespace V1_1 +} // namespace drm +} // namespace hardware +} // namespace android + +#endif // CLEARKEY_DEVICE_FILES_H_ diff --git a/drm/mediadrm/plugins/clearkey/hidl/include/DrmPlugin.h b/drm/mediadrm/plugins/clearkey/hidl/include/DrmPlugin.h index fb0695aaef..12d8608a7f 100644 --- a/drm/mediadrm/plugins/clearkey/hidl/include/DrmPlugin.h +++ b/drm/mediadrm/plugins/clearkey/hidl/include/DrmPlugin.h @@ -22,6 +22,7 @@ #include <stdio.h> #include <map> +#include "DeviceFiles.h" #include "SessionLibrary.h" #include "Utils.h" @@ -53,7 +54,7 @@ using ::android::sp; struct DrmPlugin : public IDrmPlugin { explicit DrmPlugin(SessionLibrary* sessionLibrary); - virtual ~DrmPlugin() {} + virtual ~DrmPlugin() { mFileHandle.DeleteAllLicenses(); } Return<void> openSession(openSession_cb _hidl_cb) override; Return<void> openSession_1_1(SecurityLevel securityLevel, @@ -91,13 +92,7 @@ struct DrmPlugin : public IDrmPlugin { Return<Status> restoreKeys( const hidl_vec<uint8_t>& sessionId, - const hidl_vec<uint8_t>& keySetId) { - - if (sessionId.size() == 0 || keySetId.size() == 0) { - return Status::BAD_VALUE; - } - return Status::ERROR_DRM_CANNOT_HANDLE; - } + const hidl_vec<uint8_t>& keySetId) override; Return<void> queryKeyStatus( const hidl_vec<uint8_t>& sessionId, @@ -300,6 +295,7 @@ struct DrmPlugin : public IDrmPlugin { private: void initProperties(); void installSecureStop(const hidl_vec<uint8_t>& sessionId); + bool makeKeySetId(std::string* keySetId); void setPlayPolicy(); Return<Status> setSecurityLevel(const hidl_vec<uint8_t>& sessionId, @@ -331,6 +327,8 @@ private: int64_t mCloseSessionNotOpenedCount; uint32_t mNextSecureStopId; + DeviceFiles mFileHandle; + CLEARKEY_DISALLOW_COPY_AND_ASSIGN_AND_NEW(DrmPlugin); }; diff --git a/drm/mediadrm/plugins/clearkey/hidl/include/InitDataParser.h b/drm/mediadrm/plugins/clearkey/hidl/include/InitDataParser.h index 3189c4aeb2..7d7abf23d7 100644 --- a/drm/mediadrm/plugins/clearkey/hidl/include/InitDataParser.h +++ b/drm/mediadrm/plugins/clearkey/hidl/include/InitDataParser.h @@ -34,7 +34,8 @@ public: InitDataParser() {} Status parse(const std::vector<uint8_t>& initData, - const std::string& type, + const std::string& mimeType, + V1_0::KeyType keyType, std::vector<uint8_t>* licenseRequest); private: @@ -43,7 +44,7 @@ private: Status parsePssh(const std::vector<uint8_t>& initData, std::vector<const uint8_t*>* keyIds); - std::string generateRequest( + std::string generateRequest(V1_0::KeyType keyType, const std::vector<const uint8_t*>& keyIds); }; diff --git a/drm/mediadrm/plugins/clearkey/hidl/include/MemoryFileSystem.h b/drm/mediadrm/plugins/clearkey/hidl/include/MemoryFileSystem.h new file mode 100644 index 0000000000..db368d732e --- /dev/null +++ b/drm/mediadrm/plugins/clearkey/hidl/include/MemoryFileSystem.h @@ -0,0 +1,65 @@ +// Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary +// source code may only be used and distributed under the Widevine Master +// License Agreement. +// +#ifndef CLEARKEY_MEMORY_FILE_SYSTEM_H_ +#define CLEARKEY_MEMORY_FILE_SYSTEM_H_ + +#include <map> +#include <string> + +#include "ClearKeyTypes.h" + +namespace android { +namespace hardware { +namespace drm { +namespace V1_1 { +namespace clearkey { + +// Using android file system requires clearkey plugin to update +// its sepolicy. However, we are unable to update sepolicy for +// older vendor partitions. To provide backward compatibility, +// clearkey plugin implements a very simple file system in memory. +// This memory file system does not support directory structure. +class MemoryFileSystem { + public: + struct MemoryFile { + std::string fileName; // excludes path + std::string content; + size_t fileSize; + + std::string getContent() const { return content; } + size_t getFileSize() const { return fileSize; } + void setContent(const std::string& file) { content = file; } + void setFileName(const std::string& name) { fileName = name; } + void setFileSize(size_t size) { fileSize = size; } + }; + + MemoryFileSystem() {}; + virtual ~MemoryFileSystem() {}; + + bool FileExists(const std::string& fileName) const; + ssize_t GetFileSize(const std::string& fileName) const; + size_t Read(const std::string& pathName, std::string* buffer); + bool RemoveAllFiles(); + bool RemoveFile(const std::string& fileName); + size_t Write(const std::string& pathName, const MemoryFile& memoryFile); + + private: + // License file name is made up of a unique keySetId, therefore, + // the filename can be used as the key to locate licenses in the + // memory file system. + std::map<std::string, MemoryFile> mMemoryFileSystem; + + std::string GetFileName(const std::string& path); + + CLEARKEY_DISALLOW_COPY_AND_ASSIGN(MemoryFileSystem); +}; + +} // namespace clearkey +} // namespace V1_1 +} // namespace drm +} // namespace hardware +} // namespace android + +#endif // CLEARKEY_MEMORY_FILE_SYSTEM_H_ diff --git a/drm/mediadrm/plugins/clearkey/hidl/include/Session.h b/drm/mediadrm/plugins/clearkey/hidl/include/Session.h index cddfca5eba..1064dc713b 100644 --- a/drm/mediadrm/plugins/clearkey/hidl/include/Session.h +++ b/drm/mediadrm/plugins/clearkey/hidl/include/Session.h @@ -42,8 +42,9 @@ public: const std::vector<uint8_t>& sessionId() const { return mSessionId; } Status getKeyRequest( - const std::vector<uint8_t>& mimeType, - const std::string& initDataType, + const std::vector<uint8_t>& initDataType, + const std::string& mimeType, + V1_0::KeyType keyType, std::vector<uint8_t>* keyRequest) const; Status provideKeyResponse( diff --git a/drm/mediadrm/plugins/clearkey/hidl/protos/DeviceFiles.proto b/drm/mediadrm/plugins/clearkey/hidl/protos/DeviceFiles.proto new file mode 100644 index 0000000000..145ac5b000 --- /dev/null +++ b/drm/mediadrm/plugins/clearkey/hidl/protos/DeviceFiles.proto @@ -0,0 +1,47 @@ +// ---------------------------------------------------------------------------- +// device_files.proto +// ---------------------------------------------------------------------------- +// Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary +// source code may only be used and distributed under the Widevine Master +// License Agreement. +// +// Description: +// Format of various files stored at the device. +// +syntax = "proto2"; + +package android.hardware.drm.V1_1.clearkey; + +// need this if we are using libprotobuf-cpp-2.3.0-lite +option optimize_for = LITE_RUNTIME; + +message License { + enum LicenseState { + ACTIVE = 1; + RELEASING = 2; + } + + optional LicenseState state = 1; + optional bytes license = 2; +} + +message OfflineFile { + enum FileType { + LICENSE = 1; + } + + enum FileVersion { + VERSION_1 = 1; + } + + optional FileType type = 1; + optional FileVersion version = 2 [default = VERSION_1]; + optional License license = 3; + +} + +message HashedFile { + optional bytes file = 1; + // A raw (not hex-encoded) SHA256, taken over the bytes of 'file'. + optional bytes hash = 2; +} |
