/* * Copyright (C) 2017 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ //#define LOG_NDEBUG 0 #define LOG_TAG "ClearKeyCasPlugin" #include "ClearKeyFetcher.h" #include "ecm.h" #include "ClearKeyLicenseFetcher.h" #include "ClearKeyCasPlugin.h" #include "ClearKeySessionLibrary.h" #include #include #include #include #include android::CasFactory* createCasFactory() { return new android::clearkeycas::ClearKeyCasFactory(); } android::DescramblerFactory *createDescramblerFactory() { return new android::clearkeycas::ClearKeyDescramblerFactory(); } namespace android { namespace clearkeycas { static const int32_t sClearKeySystemId = 0xF6D8; bool ClearKeyCasFactory::isSystemIdSupported(int32_t CA_system_id) const { return CA_system_id == sClearKeySystemId; } status_t ClearKeyCasFactory::queryPlugins( std::vector *descriptors) const { descriptors->clear(); descriptors->push_back({sClearKeySystemId, String8("Clear Key CAS")}); return OK; } status_t ClearKeyCasFactory::createPlugin( int32_t CA_system_id, void *appData, CasPluginCallback callback, CasPlugin **plugin) { if (!isSystemIdSupported(CA_system_id)) { return BAD_VALUE; } *plugin = new ClearKeyCasPlugin(appData, callback); return OK; } status_t ClearKeyCasFactory::createPlugin( int32_t CA_system_id, void *appData, CasPluginCallbackExt callback, CasPlugin **plugin) { if (!isSystemIdSupported(CA_system_id)) { return BAD_VALUE; } *plugin = new ClearKeyCasPlugin(appData, callback); return OK; } //////////////////////////////////////////////////////////////////////////////// bool ClearKeyDescramblerFactory::isSystemIdSupported( int32_t CA_system_id) const { return CA_system_id == sClearKeySystemId; } status_t ClearKeyDescramblerFactory::createPlugin( int32_t CA_system_id, DescramblerPlugin** plugin) { if (!isSystemIdSupported(CA_system_id)) { return BAD_VALUE; } *plugin = new ClearKeyDescramblerPlugin(); return OK; } /////////////////////////////////////////////////////////////////////////////// ClearKeyCasPlugin::ClearKeyCasPlugin( void *appData, CasPluginCallback callback) : mCallback(callback), mCallbackExt(NULL), mStatusCallback(NULL), mAppData(appData) { ALOGV("CTOR"); } ClearKeyCasPlugin::ClearKeyCasPlugin( void *appData, CasPluginCallbackExt callback) : mCallback(NULL), mCallbackExt(callback), mAppData(appData) { ALOGV("CTOR"); } ClearKeyCasPlugin::~ClearKeyCasPlugin() { ALOGV("DTOR"); ClearKeySessionLibrary::get()->destroyPlugin(this); } status_t ClearKeyCasPlugin::setStatusCallback( CasPluginStatusCallback callback) { ALOGV("setStatusCallback"); mStatusCallback = callback; return OK; } status_t ClearKeyCasPlugin::setPrivateData(const CasData &/*data*/) { ALOGV("setPrivateData"); return OK; } static String8 sessionIdToString(const std::vector &array) { String8 result; for (size_t i = 0; i < array.size(); i++) { result.appendFormat("%02x ", array[i]); } if (result.isEmpty()) { result.append("(null)"); } return result; } status_t ClearKeyCasPlugin::openSession(CasSessionId* sessionId) { ALOGV("openSession"); return ClearKeySessionLibrary::get()->addSession(this, sessionId); } status_t ClearKeyCasPlugin::openSession(uint32_t intent, uint32_t mode, CasSessionId* sessionId) { ALOGV("openSession with intent=%d, mode=%d", intent, mode); // Echo the received information to the callback. // Clear key plugin doesn't use any event, echo'ing for testing only. if (mStatusCallback != NULL) { mStatusCallback((void*)mAppData, intent, mode); } // Clear key plugin doesn't use intent and mode. return ClearKeySessionLibrary::get()->addSession(this, sessionId); } status_t ClearKeyCasPlugin::closeSession(const CasSessionId &sessionId) { ALOGV("closeSession: sessionId=%s", sessionIdToString(sessionId).string()); std::shared_ptr session = ClearKeySessionLibrary::get()->findSession(sessionId); if (session.get() == nullptr) { return ERROR_CAS_SESSION_NOT_OPENED; } ClearKeySessionLibrary::get()->destroySession(sessionId); return OK; } status_t ClearKeyCasPlugin::setSessionPrivateData( const CasSessionId &sessionId, const CasData & /*data*/) { ALOGV("setSessionPrivateData: sessionId=%s", sessionIdToString(sessionId).string()); std::shared_ptr session = ClearKeySessionLibrary::get()->findSession(sessionId); if (session.get() == nullptr) { return ERROR_CAS_SESSION_NOT_OPENED; } return OK; } status_t ClearKeyCasPlugin::processEcm( const CasSessionId &sessionId, const CasEcm& ecm) { ALOGV("processEcm: sessionId=%s", sessionIdToString(sessionId).string()); std::shared_ptr session = ClearKeySessionLibrary::get()->findSession(sessionId); if (session.get() == nullptr) { return ERROR_CAS_SESSION_NOT_OPENED; } Mutex::Autolock lock(mKeyFetcherLock); return session->updateECM(mKeyFetcher.get(), (void*)ecm.data(), ecm.size()); } status_t ClearKeyCasPlugin::processEmm(const CasEmm& /*emm*/) { ALOGV("processEmm"); Mutex::Autolock lock(mKeyFetcherLock); return OK; } status_t ClearKeyCasPlugin::sendEvent( int32_t event, int32_t arg, const CasData &eventData) { ALOGV("sendEvent: event=%d, arg=%d", event, arg); // Echo the received event to the callback. // Clear key plugin doesn't use any event, echo'ing for testing only. if (mCallback != NULL) { mCallback((void*)mAppData, event, arg, (uint8_t*)eventData.data(), eventData.size()); } else if (mCallbackExt != NULL) { mCallbackExt((void*)mAppData, event, arg, (uint8_t*)eventData.data(), eventData.size(), NULL); } return OK; } status_t ClearKeyCasPlugin::sendSessionEvent( const CasSessionId &sessionId, int32_t event, int arg, const CasData &eventData) { ALOGV("sendSessionEvent: sessionId=%s, event=%d, arg=%d", sessionIdToString(sessionId).string(), event, arg); // Echo the received event to the callback. // Clear key plugin doesn't use any event, echo'ing for testing only. if (mCallbackExt != NULL) { mCallbackExt((void*)mAppData, event, arg, (uint8_t*)eventData.data(), eventData.size(), &sessionId); } return OK; } status_t ClearKeyCasPlugin::provision(const String8 &str) { ALOGV("provision: provisionString=%s", str.string()); Mutex::Autolock lock(mKeyFetcherLock); std::unique_ptr license_fetcher; license_fetcher.reset(new ClearKeyLicenseFetcher()); status_t err = license_fetcher->Init(str.string()); if (err != OK) { ALOGE("provision: failed to init ClearKeyLicenseFetcher (err=%d)", err); return err; } std::unique_ptr key_fetcher; key_fetcher.reset(new ClearKeyFetcher(std::move(license_fetcher))); err = key_fetcher->Init(); if (err != OK) { ALOGE("provision: failed to init ClearKeyFetcher (err=%d)", err); return err; } ALOGV("provision: using ClearKeyFetcher"); mKeyFetcher = std::move(key_fetcher); return OK; } status_t ClearKeyCasPlugin::refreshEntitlements( int32_t refreshType, const CasData &/*refreshData*/) { ALOGV("refreshEntitlements: refreshType=%d", refreshType); Mutex::Autolock lock(mKeyFetcherLock); return OK; } /////////////////////////////////////////////////////////////////////// // AES-128 CBC-CTS decrypt optimized for Transport Packets. |key| is the AES // key (odd key or even key), |length| is the data size, and |buffer| is the // ciphertext to be decrypted in place. status_t TpBlockCtsDecrypt(const AES_KEY& key, size_t length, char* buffer) { CHECK(buffer); // Invariant: Packet must be at least 16 bytes. CHECK(length >= AES_BLOCK_SIZE); // OpenSSL uses unsigned char. unsigned char* data = reinterpret_cast(buffer); // Start with zero-filled initialization vector. unsigned char iv[AES_BLOCK_SIZE]; memset(iv, 0, AES_BLOCK_SIZE); // Size of partial last block handled via CTS. int cts_byte_count = length % AES_BLOCK_SIZE; // If there no is no partial last block, then process using normal CBC. if (cts_byte_count == 0) { AES_cbc_encrypt(data, data, length, &key, iv, 0); return OK; } // Cipher text stealing (CTS) - Schneier Figure 9.5 p 196. // In CTS mode, the last two blocks have been swapped. Block[n-1] is really // the original block[n] combined with the low-order bytes of the original // block[n-1], while block[n] is the high-order bytes of the original // block[n-1] padded with zeros. // Block[0] - block[n-2] are handled with normal CBC. int cbc_byte_count = length - cts_byte_count - AES_BLOCK_SIZE; if (cbc_byte_count > 0) { AES_cbc_encrypt(data, data, cbc_byte_count, &key, iv, 0); // |data| points to block[n-1]. data += cbc_byte_count; } // Save block[n] to use as IV when decrypting block[n-1]. unsigned char block_n[AES_BLOCK_SIZE]; memset(block_n, 0, AES_BLOCK_SIZE); memcpy(block_n, data + AES_BLOCK_SIZE, cts_byte_count); // Decrypt block[n-1] using block[n] as IV, consistent with the original // block order. AES_cbc_encrypt(data, data, AES_BLOCK_SIZE, &key, block_n, 0); // Return the stolen ciphertext: swap the high-order bytes of block[n] // and block[n-1]. for (int i = 0; i < cts_byte_count; i++) { unsigned char temp = *(data + i); *(data + i) = *(data + AES_BLOCK_SIZE + i); *(data + AES_BLOCK_SIZE + i) = temp; } // Decrypt block[n-1] using previous IV. AES_cbc_encrypt(data, data, AES_BLOCK_SIZE, &key, iv, 0); return OK; } // PES header and ECM stream header layout // // processECM() receives the data_byte portion from the transport packet. // Below is the layout of the first 16 bytes of the ECM PES packet. Here // we don't parse them, we skip them and go to the ECM container directly. // The layout is included here only for reference. // // 0-2: 0x00 00 01 = start code prefix. // 3: 0xf0 = stream type (90 = ECM). // 4-5: 0x00 00 = PES length (filled in later, this is the length of the // PES header (16) plus the length of the ECM container). // 6-7: 0x00 00 = ECM major version. // 8-9: 0x00 01 = ECM minor version. // 10-11: 0x00 00 = Crypto period ID (filled in later). // 12-13: 0x00 00 = ECM container length (filled in later, either 84 or // 166). // 14-15: 0x00 00 = offset = 0. const static size_t kEcmHeaderLength = 16; const static size_t kUserKeyLength = 16; status_t ClearKeyCasSession::updateECM( KeyFetcher *keyFetcher, void *ecm, size_t size) { if (keyFetcher == nullptr) { return ERROR_CAS_NOT_PROVISIONED; } if (size < kEcmHeaderLength) { ALOGE("updateECM: invalid ecm size %zu", size); return BAD_VALUE; } Mutex::Autolock _lock(mKeyLock); if (mEcmBuffer != NULL && mEcmBuffer->capacity() == size && !memcmp(mEcmBuffer->base(), ecm, size)) { return OK; } mEcmBuffer = ABuffer::CreateAsCopy(ecm, size); mEcmBuffer->setRange(kEcmHeaderLength, size - kEcmHeaderLength); uint64_t asset_id; std::vector keys; status_t err = keyFetcher->ObtainKey(mEcmBuffer, &asset_id, &keys); if (err != OK) { ALOGE("updateECM: failed to obtain key (err=%d)", err); return err; } ALOGV("updateECM: %zu key(s) found", keys.size()); for (size_t keyIndex = 0; keyIndex < keys.size(); keyIndex++) { String8 str; const sp& keyBytes = keys[keyIndex].key_bytes; CHECK(keyBytes->size() == kUserKeyLength); int result = AES_set_decrypt_key( reinterpret_cast(keyBytes->data()), AES_BLOCK_SIZE * 8, &mKeyInfo[keyIndex].contentKey); mKeyInfo[keyIndex].valid = (result == 0); if (!mKeyInfo[keyIndex].valid) { ALOGE("updateECM: failed to set key %zu, key_id=%d", keyIndex, keys[keyIndex].key_id); } } return OK; } // Decryption of a set of sub-samples ssize_t ClearKeyCasSession::decrypt( bool secure, DescramblerPlugin::ScramblingControl scramblingControl, size_t numSubSamples, const DescramblerPlugin::SubSample *subSamples, const void *srcPtr, void *dstPtr, AString * /* errorDetailMsg */) { if (secure) { return ERROR_CAS_CANNOT_HANDLE; } scramblingControl = (DescramblerPlugin::ScramblingControl) (scramblingControl & DescramblerPlugin::kScrambling_Mask_Key); AES_KEY contentKey; if (scramblingControl != DescramblerPlugin::kScrambling_Unscrambled) { // Hold lock to get the key only to avoid contention for decryption Mutex::Autolock _lock(mKeyLock); int32_t keyIndex = (scramblingControl & 1); if (!mKeyInfo[keyIndex].valid) { ALOGE("decrypt: key %d is invalid", keyIndex); return ERROR_CAS_DECRYPT; } contentKey = mKeyInfo[keyIndex].contentKey; } uint8_t *src = (uint8_t*)srcPtr; uint8_t *dst = (uint8_t*)dstPtr; for (size_t i = 0; i < numSubSamples; i++) { size_t numBytesinSubSample = subSamples[i].mNumBytesOfClearData + subSamples[i].mNumBytesOfEncryptedData; if (src != dst) { memcpy(dst, src, numBytesinSubSample); } status_t err = OK; // Don't decrypt if len < AES_BLOCK_SIZE. // The last chunk shorter than AES_BLOCK_SIZE is not encrypted. if (scramblingControl != DescramblerPlugin::kScrambling_Unscrambled && subSamples[i].mNumBytesOfEncryptedData >= AES_BLOCK_SIZE) { err = decryptPayload( contentKey, numBytesinSubSample, subSamples[i].mNumBytesOfClearData, (char *)dst); } dst += numBytesinSubSample; src += numBytesinSubSample; } return dst - (uint8_t *)dstPtr; } // Decryption of a TS payload status_t ClearKeyCasSession::decryptPayload( const AES_KEY& key, size_t length, size_t offset, char* buffer) const { CHECK(buffer); // Invariant: only call decryptPayload with TS packets with at least 16 // bytes of payload (AES_BLOCK_SIZE). CHECK(length >= offset + AES_BLOCK_SIZE); return TpBlockCtsDecrypt(key, length - offset, buffer + offset); } /////////////////////////////////////////////////////////////////////////// #undef LOG_TAG #define LOG_TAG "ClearKeyDescramblerPlugin" bool ClearKeyDescramblerPlugin::requiresSecureDecoderComponent( const char *mime) const { ALOGV("requiresSecureDecoderComponent: mime=%s", mime); return false; } status_t ClearKeyDescramblerPlugin::setMediaCasSession( const CasSessionId &sessionId) { ALOGV("setMediaCasSession: sessionId=%s", sessionIdToString(sessionId).string()); std::shared_ptr session = ClearKeySessionLibrary::get()->findSession(sessionId); if (session.get() == nullptr) { ALOGE("ClearKeyDescramblerPlugin: session not found"); return ERROR_CAS_SESSION_NOT_OPENED; } std::atomic_store(&mCASSession, session); return OK; } ssize_t ClearKeyDescramblerPlugin::descramble( bool secure, ScramblingControl scramblingControl, size_t numSubSamples, const SubSample *subSamples, const void *srcPtr, int32_t srcOffset, void *dstPtr, int32_t dstOffset, AString *errorDetailMsg) { ALOGV("descramble: secure=%d, sctrl=%d, subSamples=%s, " "srcPtr=%p, dstPtr=%p, srcOffset=%d, dstOffset=%d", (int)secure, (int)scramblingControl, subSamplesToString(subSamples, numSubSamples).string(), srcPtr, dstPtr, srcOffset, dstOffset); std::shared_ptr session = std::atomic_load(&mCASSession); if (session.get() == nullptr) { ALOGE("Uninitialized CAS session!"); return ERROR_CAS_DECRYPT_UNIT_NOT_INITIALIZED; } return session->decrypt( secure, scramblingControl, numSubSamples, subSamples, (uint8_t*)srcPtr + srcOffset, dstPtr == NULL ? NULL : ((uint8_t*)dstPtr + dstOffset), errorDetailMsg); } // Conversion utilities String8 ClearKeyDescramblerPlugin::arrayToString( uint8_t const *array, size_t len) const { String8 result("{ "); for (size_t i = 0; i < len; i++) { result.appendFormat("0x%02x ", array[i]); } result += "}"; return result; } String8 ClearKeyDescramblerPlugin::subSamplesToString( SubSample const *subSamples, size_t numSubSamples) const { String8 result; for (size_t i = 0; i < numSubSamples; i++) { result.appendFormat("[%zu] {clear:%u, encrypted:%u} ", i, subSamples[i].mNumBytesOfClearData, subSamples[i].mNumBytesOfEncryptedData); } return result; } } // namespace clearkeycas } // namespace android