/* * 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 "ClearKeyFetcher" #include #include #include #include "ClearKeyFetcher.h" #include "ecm.h" #include "LicenseFetcher.h" #include #include namespace android { namespace clearkeycas { ClearKeyFetcher::ClearKeyFetcher( std::unique_ptr license_fetcher) : initialized_(false), license_fetcher_(std::move(license_fetcher)) { CHECK(license_fetcher_); } ClearKeyFetcher::~ClearKeyFetcher() {} // This is a no-op but other KeyFetcher subclasses require initialization // so this is necessary to preserve the contract. status_t ClearKeyFetcher::Init() { initialized_ = true; return OK; } status_t ClearKeyFetcher::ObtainKey(const sp& buffer, uint64_t* asset_id, std::vector* keys) { CHECK(asset_id); CHECK(keys); CHECK(initialized_); *asset_id = 0; keys->clear(); EcmContainer container; status_t status = container.Parse(buffer); if (status != OK) { return status; } ALOGV("descriptor_size=%zu", container.descriptor_size()); // Validate that the BroadcastEncryptor is sending a properly formed // EcmContainer. If it contains two Ecms, the ids should have different // parity (one odd, one even). This does not necessarily affect decryption // but indicates a problem with Ecm generation. if (container.descriptor_size() == 2) { // XOR the least significant bits to verify different parity. bool same_parity = (((container.descriptor(0).id() & 0x01) ^ (container.descriptor(1).id() & 0x01)) == 0); if (same_parity) { ALOGW("asset_id=%" PRIu64 ": malformed Ecm, " "content keys have same parity, id0=%d, id1=%d", container.descriptor(0).ecm().asset_id(), container.descriptor(0).id(), container.descriptor(1).id()); } } *asset_id = container.descriptor(0).ecm().asset_id(); // Detect asset_id change. This could be caused by a configuration change // in the BroadcastEncryptor. This is unusual so log it in case it is an // operational mistake. This invalidates the current asset_key causing a // new license to be fetched. // TODO(rkint): test against BroadcastEncryptor to verify what BE sends on // asset_id change. If it sends an EcmContainer with 2 Ecms with different // asset_ids (old and new) then it might be best to prefetch the Emm. if ((asset_.id() != 0) && (*asset_id != asset_.id())) { ALOGW("Asset_id change from %" PRIu64 " to %" PRIu64, asset_.id(), *asset_id); asset_.Clear(); } // Fetch license to get asset_id if (!asset_.has_id()) { status = license_fetcher_->FetchLicense(*asset_id, &asset_); if (status != OK) { *asset_id = 0; return status; } ALOGV("FetchLicense succeeded, has_id=%d", asset_.has_id()); } keys->resize(container.descriptor_size()); for (size_t i = 0; i < container.descriptor_size(); ++i) { status = container.mutable_descriptor(i)->mutable_ecm()->Decrypt( container.descriptor(i).ecm().buffer(), asset_); if (status != OK) { *asset_id = 0; keys->clear(); return status; } // TODO: if 2 Ecms have same parity, key from Ecm with higher id // should be keys[1]. KeyInfo key; key.key_id = container.descriptor(i).id(); key.key_bytes = container.descriptor(i).ecm().content_key(); keys->at(key.key_id & 1) = key; } return OK; } } // namespace clearkeycas } // namespace android