/* * 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. */ // Data objects encapsulating the clear key Ecm (Entitlement Control // Message) and related container messages. Deserialization and decryption // are handled externally to reduce build-time dependencies. // // Simplified typical client-side use: // Asset asset; // from the AssetRegistry. // uint8[] ecm_buffer; // received over network, contains an EcmContainer. // EcmContainer ecm_container; // util::Status status = ecm_container.Parse(ecm_buffer); // status = ecm_container.descriptor(1).ecm().Decrypt( // ecm_container.descriptor(1).ecm().buffer(), asset_key); // string content_key; // if (ecm_container.descriptor(1).ecm().has_content_key()) { // content_key = ecm_container.descriptor(1).ecm().content_key(); // } // // use |content_key| to decrypt content. // // Simplified typical server-side use: // EcmContainer container; // string encoded_ecm; // // Use the ecm_generator API to encode and encrypt an ECM from data fields. // util::Status status = ecm_generator::EncodeECM(..., &encoded_ecm); // // Use |encoded_ecm| to initialized the Ecm from this library. // Ecm ecm; // util::Status status = ecm.Parse(encoded_ecm); // EcmDescriptor descriptor(crypto_period_id, ecm); // status = container.Add(descriptor); // string serialized_container; // status = container.Marshall(&serialized_container); // // now |serialized_container| can be sent to the STB. // // Due to past overloading of the term "ECM" this library introduces some // new terminology. // // Ecm: the 32-byte message sent from the head end to a packager that contains // the asset_id, system_id, and content_key (clear). // // EcmDescriptor: contains an Ecm and an id (the crypto period id in the case // of the BroadcastEncryptor). It contains no encrypted fields. // // EcmContainer: sent by the server in the video stream using the ECM pid. // This contains 1 or 2 EcmDescriptors and a count. It contains no // encrypted fields. // // The first EcmContainer sent by the server has only one EcmDescriptor. After // the first crypto period change, an EcmContainer contains 2 EcmDescriptors. // One has an odd id and one has an even id. The decrypted content keys from the // Ecms in the EcmDescriptors are used by the Mpeg2 parser as odd and even // scrambling keys. As the crypto period changes, the oldest EcmDescriptor is // dropped from the EcmContainer and the new EcmDescriptor is added. // // These classes use a simplified protobuf model. For non-repeating fields, // - has_foo() indicates whether the field is populated. // - the accessor foo() returns either a value or a const reference. // - a mutator sets the value. Primitive types and strings use // set_foo(value) while for objects mutable_foo() returns a pointer. // // To prevent null references, objects (like the Asset contained in an Emm) // are allocated as members and can be accessed via foo() even if they have // not been populated. The caller must call has_foo() to make sure that the // object is valid. Calling mutable_foo() to obtain a pointer causes has_foo() // to return true. // // Repeated fields (like the EcmDescriptors contained in an EcmContainer) are // handled differently. // - foo_size() returns the number of instances. // - the accessor foo(index) returns either a value or a const reference to // the instance at index. It is illegal to call with |index| >= the value // returned by foo_size(). |index| is checked with CHECK. // - a mutator to change the value of the instance. Primitive types and // strings use set_foo(index, value) while for objects mutable_foo(index) // returns a pointer. It is illegal to call with |index| >= the value // returned by foo_size(). |index| is checked with CHECK. // // Accessing a repeated field with an invalid index causes CHECK to fail. // Be sure to call EcmContainer::decriptor_size() before calling descriptor() // or mutable_descriptor()! // #ifndef CLEAR_KEY_ECM_H_ #define CLEAR_KEY_ECM_H_ #include #include #include "protos/license_protos.pb.h" #include #include #include using namespace std; namespace android { namespace clearkeycas { // Entitlement Control Message. It contains clear fields. The asset_id // and system_id as well as the content_key are clear. // // This class is not thread-safe. class Ecm { public: // Wire size of ECM. static constexpr size_t kSizeBytes = 16 + 16; // clear fields + clear key // Creates an empty ECM which must be initialized via Parse(). Ecm(); ~Ecm(); // Parses clear fields of Ecm serialized in |buffer_as_binary| and saves // a copy of |buffer_as_binary| for a future DecryptEcm call. // Returns: // - BAD_VALUE if |buffer_as_binary| is too small. // - CLEARKEY_STATUS_INVALIDASSETID via ecm_generator::DecodeEcmClearFields if // asset_id is 0. // - CLEARKEY_STATUS_INVALIDSYSTEMID via ecm_generator::DecodeEcmClearFields if // system_id is 0. // Postconditions: // - |asset_id_| and |system_id_| are populated with non-zero values. // - |buffer_| contains a copy of the serialized Ecm. status_t Parse(const sp& buffer_as_binary); // Parses and decrypts Ecm serialized in |buffer_as_binary| using // |asset_from_emm|.asset_key().encryption_key(). It is not necessary to call // Parse() first. // Returns BAD_VALUE if |buffer_as_binary| is too small. // Returns CLEARKEY_STATUS_INVALIDASSETID via // ecm_generator::DecodeEcmClearFields if asset_id is 0. // Returns CLEARKEY_STATUS_INVALIDSYSTEMID via // ecm_generator::DecodeEcmClearFields if system_id is 0. // Returns CLEARKEY_STATUS_INVALID_PARAMETER if // - asset_id in |asset_from_emm| does not match asset_id in serialized Ecm. // Preconditions: |asset_from_emm| must contain asset_id and asset_key fields. // Postconditions: asset_id() and system_id() are populated with non-zero // values, content_key() is populated with the clear content key. status_t Decrypt(const sp& buffer_as_binary, const Asset& asset_from_emm); // |buffer_| is a serialized copy of the Ecm used for later decryption or // for marshalling. inline bool has_buffer() const { return buffer_ != NULL; } const sp buffer() const { return buffer_; } inline void set_buffer(const sp& buffer) { buffer_ = ABuffer::CreateAsCopy(buffer->data(), buffer->size()); } // |content_key| is the clear, encryption/decryption key generated by the server. inline bool has_content_key() const { return content_key_ != NULL; } inline void set_content_key(const sp& value) { content_key_ = ABuffer::CreateAsCopy(value->data(), value->size()); } inline const sp content_key() const { return content_key_; } // |asset_id| from the server. inline bool has_asset_id() const { return asset_id_set_; } inline uint64_t asset_id() const { return asset_id_; } inline void set_asset_id(uint64_t value) { asset_id_ = value; asset_id_set_ = true; } // |system_id| from the server. inline bool has_system_id() const { return system_id_set_; } inline uint32_t system_id() const { return system_id_; } inline void set_system_id(uint32_t value) { system_id_ = value; system_id_set_ = true; } private: uint64_t asset_id_; bool asset_id_set_; sp buffer_; sp content_key_; uint32_t system_id_; bool system_id_set_; }; // Contains an Ecm and and Id. // This class is not thread-safe. class EcmDescriptor { public: // Wire size of Id field. static constexpr size_t kIdSizeBytes = sizeof(uint16_t); // Wire size of EcmDescriptor. static constexpr size_t kSizeBytes = Ecm::kSizeBytes + kIdSizeBytes; // Client-side ctor. Populate from a buffer with Parse(). EcmDescriptor(); // Server-side ctor. // Args: // - |id| is the crypto period ID. // - |ecm| is an ECM which must have been intialized with Ecm::Parse(). EcmDescriptor(uint16_t id, const Ecm& ecm); ~EcmDescriptor(); // Parses EcmDescriptor and its contained Ecm which are serialized in the // binary string |buffer_as_binary|. // Returns // - BAD_VALUE if |buffer_as_binary| is too short to contain a // serialized EcmDescriptor. // - Errors returned by Ecm::Parse. // Postconditions: // - id() is populated. Note that 0 is a legal value. // - the clear fields of the contained Ecm have been populated. status_t Parse(const sp& buffer_as_binary); // |id| of the contained Ecm. Typically the crypto period id. inline bool has_id() const { return id_set_; } inline void set_id(uint16_t value) { id_ = value; id_set_ = true; } inline uint16_t id() const { return id_; } // The contained |ecm|. inline bool has_ecm() const { return ecm_set_; } inline Ecm* mutable_ecm() { ecm_set_ = true; return &ecm_; } inline const Ecm& ecm() const { return ecm_; } private: Ecm ecm_; bool ecm_set_; uint16_t id_; bool id_set_; }; // Contains a count and 1 or 2 EcmDescriptors. This is included in the video // stream by the sender in the ECM pid. // This class is not thread-safe. class EcmContainer { public: // Wire size of the count field. static constexpr size_t kCountSizeBytes = sizeof(uint16_t); // Minimum wire size assuming one EcmDescriptor. static constexpr size_t kMinimumSizeBytes = EcmDescriptor::kSizeBytes + kCountSizeBytes; static constexpr size_t kMinDescriptorCount = 1; static constexpr size_t kMaxDescriptorCount = 2; // Creates an empty EcmContainer which must be populated via Parse() // (client-side) or Add() (server-side). EcmContainer(); ~EcmContainer(); // Adds an EcmDescriptor for server-side applications. // If |count_| is 2, |descriptor| replaces the oldest EcmDescriptor. // // Returns: // - INTERNAL if the EcmContainer is in a bad state (count != 0, 1, or 2). // Postconditions: // - count() is within bounds (1 or 2). status_t Add(const EcmDescriptor& descriptor); // Parses EcmContainer and its contained EcmDescriptors which are serialized // in |buffer_as_binary|. // Returns // - BAD_VALUE if |buffer_as_binary| is too short to contain a // serialized EcmDescriptor. // - ERROR_OUT_OF_RANGE if the count contained in the serialized EcmContainer // is not 1 or 2. // - Errors returned by EcmDescriptor::Parse. // Postconditions: // - count() is within bounds (1 or 2) and. // - contained EcmDescriptor(s) parsed and populated. status_t Parse(const sp& buffer_as_binary); inline bool has_count() const { return count_set_; } // Sets the |count| of contained EcmDecriptors. Illegal values are silently // ignored. inline void set_count(size_t count) { if (!CountLegal(count)) return; count_ = count; count_set_ = true; } // Number of contained EcmDecriptors. Only 1 and 2 are legal values. inline size_t count() const { return count_; } // Returns the number of allowable descriptors. This is redundant but is // provided for protobuf compatibility. inline size_t descriptor_size() const { return count_; } // Returns a pointer to the EcmDescriptor at |index| for valid index values, // otherwise calls CHECK and aborts. Always call descriptor_size() first! inline EcmDescriptor* mutable_descriptor(size_t index) { //CHECK(IndexValid(index)); return &descriptor_[index]; } // Returns a reference to the EcmDescriptor at |index| for valid index // values, otherwise calls CHECK and aborts. Call descriptor_size() first! inline const EcmDescriptor& descriptor(size_t index) const { //CHECK(IndexValid(index)); return descriptor_[index]; } private: // Count value must be 1 or 2. inline bool CountLegal(size_t count) const { return count <= kMaxDescriptorCount && count >= kMinDescriptorCount; } // Index must be 0 or 1. inline bool IndexLegal(size_t index) const { return index < kMaxDescriptorCount; } // |index| is valid for this object: it is legal and < count_. inline bool IndexValid(size_t index) const { if (!IndexLegal(index)) return false; return index < count_; } size_t count_; bool count_set_; EcmDescriptor descriptor_[kMaxDescriptorCount]; DISALLOW_EVIL_CONSTRUCTORS(EcmContainer); }; } // namespace clearkeycas } // namespace android #endif // CLEAR_KEY_ECM_H_