From 0441c0b8d436d8e1226fa4694ab5a25bf0027fbb Mon Sep 17 00:00:00 2001 From: Fang Yu Date: Fri, 2 Nov 2018 14:22:06 -0700 Subject: [PATCH] Add a few more checks for "key length" and "iv length". ------------- Pad key value when crypto_mode is DVB_CSA, so that the key length is always 16 bytes. ------------- Minor comment and example code update. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=219860612 --- example/wv_cas_ecm_example.cc | 9 +-- media_cas_packager_sdk/internal/ecm.cc | 53 +++++++++++---- media_cas_packager_sdk/internal/ecm.h | 12 ++-- media_cas_packager_sdk/public/wv_cas_ecm.cc | 26 ++++++++ .../public/wv_cas_ecm_test.cc | 66 ++++++++++++++----- 5 files changed, 128 insertions(+), 38 deletions(-) diff --git a/example/wv_cas_ecm_example.cc b/example/wv_cas_ecm_example.cc index 3473d6b..f9506cd 100644 --- a/example/wv_cas_ecm_example.cc +++ b/example/wv_cas_ecm_example.cc @@ -14,12 +14,13 @@ #include "media_cas_packager_sdk/public/wv_cas_ecm.h" #include "media_cas_packager_sdk/public/wv_cas_types.h" -const int kContentIvSize = 16; const bool kKeyRotationEnabled = true; const char kEvenKey[] = "even_content_key"; // 16 bytes +const char kCsaEvenKey[] = "12345678"; // 8 bytes const char kEvenContentIv8Bytes[] = "evencont"; // 8 bytes const char kEvenContentIv16Bytes[] = "evencontevencont"; // 16 bytes const char kOddKey[] = "odd_content_key."; // 16 bytes +const char kCsaOddKey[] = "87654321"; // 8 bytes const char kOddContentIv8Bytes[] = "oddcont."; // 8 bytes const char kOddContentIv16Bytes[] = "oddcont.oddcont."; // 16 bytes const char kEntitlementKeyId[] = "ent_key_id......"; // 16 bytes @@ -28,7 +29,7 @@ const char kEntitlementKey[] = "entitlement_key................."; // 32 bytes int main(int argc, char **argv) { widevine::cas::WvCasEcm wv_cas_ecm; widevine::cas::WvCasStatus status = - wv_cas_ecm.Initialize(kContentIvSize, kKeyRotationEnabled, + wv_cas_ecm.Initialize(/* content_iv_size= */ 8, kKeyRotationEnabled, widevine::cas::CryptoMode::kDvbCsa); if (status != widevine::cas::OK) { std::cerr << "Failed to initialize WV CAS ECM, error: " @@ -36,8 +37,8 @@ int main(int argc, char **argv) { << std::endl; } std::string ecm; - status = wv_cas_ecm.GenerateEcm(kEvenKey, kEvenContentIv16Bytes, kOddKey, - kOddContentIv16Bytes, kEntitlementKeyId, + status = wv_cas_ecm.GenerateEcm(kCsaEvenKey, kEvenContentIv8Bytes, kCsaOddKey, + kOddContentIv8Bytes, kEntitlementKeyId, kEntitlementKey, &ecm); if (status != widevine::cas::OK) { std::cerr << "Failed to generate WV CAS ECM, error: " diff --git a/media_cas_packager_sdk/internal/ecm.cc b/media_cas_packager_sdk/internal/ecm.cc index 90316dd..ac13752 100644 --- a/media_cas_packager_sdk/internal/ecm.cc +++ b/media_cas_packager_sdk/internal/ecm.cc @@ -60,6 +60,7 @@ static constexpr int kNumBitsUnusedField = 6; static constexpr size_t kKeyIdSizeBytes = 16; static constexpr size_t kKeyDataSizeBytes = 16; static constexpr size_t kWrappedKeyIvSizeBytes = 16; +static constexpr size_t kWrappingKeySizeBytes = 32; // entitlement key static constexpr size_t kWrappingKeyIvSizeBytes = 16; // BitField constants for the ECM payload @@ -302,21 +303,39 @@ util::Status CasEcm::WrapEntitledKeys( if (entitled_key->wrapped_key_iv.empty()) { CHECK(RandomBytes(kWrappedKeyIvSizeBytes, &entitled_key->wrapped_key_iv)); } - entitled_key->wrapped_key_value = + util::Status status = WrapKey(entitlement_key->key_value, entitled_key->wrapped_key_iv, - entitled_key->key_value); + entitled_key->key_value, &entitled_key->wrapped_key_value); + if (!status.ok()) { + return status; + } entitlement_key++; } return util::OkStatus(); } -std::string CasEcm::WrapKey(const std::string& wrapping_key, const std::string& iv, - const std::string& key_value) { - if (iv.size() != kWrappingKeyIvSizeBytes) { - LOG(WARNING) << "Incorrect iv size for WrapKey(): " << iv.size(); +util::Status CasEcm::WrapKey(const std::string& wrapping_key, + const std::string& wrapping_iv, const std::string& key_value, + std::string* wrapped_key) { + util::Status status = ValidateKeyValue(wrapping_key, kWrappingKeySizeBytes); + if (!status.ok()) { + return status; + } + status = ValidateIv(wrapping_iv, kWrappingKeyIvSizeBytes); + if (!status.ok()) { + return status; + } + status = ValidateKeyValue(key_value, kKeyDataSizeBytes); + if (!status.ok()) { + return status; } // Wrapped key IV is always 16 bytes. - return crypto_util::EncryptAesCbcNoPad(wrapping_key, iv, key_value); + *wrapped_key = + crypto_util::EncryptAesCbcNoPad(wrapping_key, wrapping_iv, key_value); + if (wrapped_key->empty()) { + return util::Status(util::error::INTERNAL, "Failed to wrap key"); + } + return util::OkStatus(); } util::Status CasEcm::ValidateKeys(const std::vector& keys) { @@ -326,6 +345,10 @@ util::Status CasEcm::ValidateKeys(const std::vector& keys) { if (!status.ok()) { return status; } + status = ValidateKeyValue(key->key_value, kKeyDataSizeBytes); + if (!status.ok()) { + return status; + } status = ValidateIv(key->content_iv, content_iv_size_); if (!status.ok()) { return status; @@ -342,7 +365,7 @@ util::Status CasEcm::ValidateWrappedKeys( if (!status.ok()) { return status; } - status = ValidateKeyValue(key->wrapped_key_value); + status = ValidateKeyValue(key->wrapped_key_value, kKeyDataSizeBytes); if (!status.ok()) { LOG(ERROR) << "Wrapped key is bad."; return status; @@ -366,9 +389,10 @@ util::Status CasEcm::ValidateKeyId(const std::string& key_id) { return util::OkStatus(); } -util::Status CasEcm::ValidateKeyValue(const std::string& key_value) { - if (key_value.size() != kKeyDataSizeBytes) { - util::Status( +util::Status CasEcm::ValidateKeyValue(const std::string& key_value, + size_t key_value_size) { + if (key_value.size() != key_value_size) { + return util::Status( util::error::INVALID_ARGUMENT, absl::StrCat("Key is wrong size (", key_value.size(), " bytes).")); } @@ -390,6 +414,9 @@ std::string CasEcm::SerializeEcm(const std::vector& keys) { generation()); std::bitset decrypt_mode( static_cast(crypto_mode())); + if (decrypt_mode.to_string() == "00") { + LOG(FATAL) << "Invalid decrypt mode \"00\""; + } std::bitset rotation_enabled( RotationFieldValue(paired_keys_required())); std::bitset wrapped_key_iv_size( @@ -490,6 +517,10 @@ util::Status CasEcm::ParseEntitlementResponse(const std::string& response_string EntitlementKeyInfo ekey; ekey.key_id = key.key_id(); ekey.key_value = key.key(); + util::Status status = ValidateKeyValue(key.key(), kWrappingKeySizeBytes); + if (!status.ok()) { + return status; + } // Using only keys with correct KeySlot if (!key.has_key_slot()) { diff --git a/media_cas_packager_sdk/internal/ecm.h b/media_cas_packager_sdk/internal/ecm.h index 2adf97b..fca0b4a 100644 --- a/media_cas_packager_sdk/internal/ecm.h +++ b/media_cas_packager_sdk/internal/ecm.h @@ -198,17 +198,19 @@ class CasEcm { const std::vector& keys, const std::string& track_type, std::string* serialized_ecm, uint32_t* generation); - // Wrap a |key_value| using |wrapping_key| (entitlement key) and |iv|. - // Returns the resulting wrapped key. - virtual std::string WrapKey(const std::string& wrapping_key, const std::string& iv, - const std::string& key_value); + // Wrap |key_value| using |wrapping_key| (entitlement key) and |wrapping_iv|. + // Returns the resulting wrapped key in |wrapped_key|. + // Return a status indicating whether there has been any error. + virtual util::Status WrapKey(const std::string& wrapping_key, + const std::string& wrapping_iv, + const std::string& key_value, std::string* wrapped_key); virtual util::Status ValidateKeys(const std::vector& keys); virtual util::Status ValidateWrappedKeys( const std::vector& keys); util::Status ValidateKeyId(const std::string& key_id); - util::Status ValidateKeyValue(const std::string& key_value); + util::Status ValidateKeyValue(const std::string& key_value, size_t key_value_size); util::Status ValidateIv(const std::string& iv, size_t size); // TODO(user): need unit tests for CreateEntitlementRequest. diff --git a/media_cas_packager_sdk/public/wv_cas_ecm.cc b/media_cas_packager_sdk/public/wv_cas_ecm.cc index 685530a..86d1404 100644 --- a/media_cas_packager_sdk/public/wv_cas_ecm.cc +++ b/media_cas_packager_sdk/public/wv_cas_ecm.cc @@ -27,6 +27,10 @@ namespace widevine { namespace cas { namespace { + +static constexpr size_t kContentKeySizeBytes = 16; +static constexpr size_t kCsaContentKeySizeBytes = 8; + EcmInitParameters CreateEcmInitParameters(int content_iv_size, bool key_rotation_enabled, CryptoMode crypto_mode) { @@ -74,6 +78,13 @@ WvCasStatus WvCasEcm::GenerateEcm(const std::string& even_key, const std::string& entitlement_key, std::string* ecm) const { DCHECK(ecm); + if (crypto_mode_ == CryptoMode::kDvbCsa && + even_key.length() == kCsaContentKeySizeBytes && + odd_key.length() == kCsaContentKeySizeBytes) { + return GenerateEcm(absl::StrCat(even_key, even_key), even_content_iv, + absl::StrCat(odd_key, odd_key), odd_content_iv, + entitlement_key_id, entitlement_key, ecm); + } if (!initialized_) { LOG(ERROR) << "WvCasEcm has not been properly initialized"; return UNAVAILABLE; @@ -83,6 +94,11 @@ WvCasStatus WvCasEcm::GenerateEcm(const std::string& even_key, "rotation is disabled"; return UNAVAILABLE; } + if (even_key.size() != kContentKeySizeBytes || + odd_key.size() != kContentKeySizeBytes) { + LOG(ERROR) << "Size of content key is incorrect"; + return INVALID_ARGUMENT; + } if (even_content_iv.size() != content_iv_size_ || odd_content_iv.size() != content_iv_size_) { LOG(ERROR) << "Size of content IV is incorrect"; @@ -153,6 +169,12 @@ WvCasStatus WvCasEcm::GenerateSingleKeyEcm(const std::string& even_key, const std::string& entitlement_key, std::string* ecm) const { DCHECK(ecm); + if (crypto_mode_ == CryptoMode::kDvbCsa && + even_key.length() == kCsaContentKeySizeBytes) { + return GenerateSingleKeyEcm(absl::StrCat(even_key, even_key), + even_content_iv, entitlement_key_id, + entitlement_key, ecm); + } if (!initialized_) { LOG(ERROR) << "WvCasEcm has not been properly initialized"; return UNAVAILABLE; @@ -162,6 +184,10 @@ WvCasStatus WvCasEcm::GenerateSingleKeyEcm(const std::string& even_key, << "Please call GenerateEcm() instead when key rotation is enabled"; return UNAVAILABLE; } + if (even_key.size() != kContentKeySizeBytes) { + LOG(ERROR) << "Size of content key is incorrect"; + return INVALID_ARGUMENT; + } if (even_content_iv.size() != content_iv_size_) { LOG(ERROR) << "Size of content IV is incorrect"; return INVALID_ARGUMENT; diff --git a/media_cas_packager_sdk/public/wv_cas_ecm_test.cc b/media_cas_packager_sdk/public/wv_cas_ecm_test.cc index a16b2fa..f7c5cc7 100644 --- a/media_cas_packager_sdk/public/wv_cas_ecm_test.cc +++ b/media_cas_packager_sdk/public/wv_cas_ecm_test.cc @@ -19,8 +19,10 @@ namespace widevine { namespace cas { const char kEvenKey[] = "even_content_key"; // 16 bytes +const char kCsaEvenKey[] = "12345678"; // 8 bytes const char kEvenContentIv8Bytes[] = "evencont"; // 8 bytes const char kOddKey[] = "odd_content_key."; // 16 bytes +const char kCsaOddKey[] = "87654321"; // 8 bytes const char kOddContentIv8Bytes[] = "oddcont."; // 8 bytes const char kEntitlementKeyId[] = "ent_key_id......"; // 16 bytes const char kEntitlementKey[] = "entitlement_key................."; // 32 bytes @@ -94,6 +96,34 @@ TEST_F(WvCasEcmTest, GenerateSingleKeyEcmInvalidContentIv) { /* entitlement_key= */ kEntitlementKey, &actual_ecm)); } +TEST_F(WvCasEcmTest, GenerateEcmInvalidContentKey) { + EXPECT_EQ(OK, wv_cas_ecm_.Initialize(/* content_iv_size= */ 8, + /* key_rotation_enabled= */ true, + CryptoMode::kAesCtr)); + std::string actual_ecm; + EXPECT_EQ(INVALID_ARGUMENT, + wv_cas_ecm_.GenerateEcm( + /* even_key= */ kEvenKey, + /* even_content_iv= */ kEvenContentIv8Bytes, + /* odd_key= */ "12345678", + /* odd_content_iv= */ kOddContentIv8Bytes, + /* entitlement_key_id= */ kEntitlementKeyId, + /* entitlement_key= */ kEntitlementKey, &actual_ecm)); +} + +TEST_F(WvCasEcmTest, GenerateSingleKeyEcmInvalidContentKey) { + EXPECT_EQ(OK, wv_cas_ecm_.Initialize(/* content_iv_size= */ 8, + /* key_rotation_enabled= */ false, + CryptoMode::kAesCtr)); + std::string actual_ecm; + EXPECT_EQ(INVALID_ARGUMENT, + wv_cas_ecm_.GenerateSingleKeyEcm( + /* even_key= */ "12345678", + /* even_content_iv= */ kEvenContentIv8Bytes, + /* entitlement_key_id= */ kEntitlementKeyId, + /* entitlement_key= */ kEntitlementKey, &actual_ecm)); +} + TEST_F(WvCasEcmTest, GenerateEcm8BytesContentIvCtrSuccess) { EXPECT_EQ(OK, wv_cas_ecm_.Initialize(/* content_iv_size= */ 8, /* key_rotation_enabled= */ true, @@ -226,48 +256,48 @@ TEST_F(WvCasEcmTest, GenerateSingleKeyEcm16BytesContentIvCbcSuccess) { absl::BytesToHexString(actual_ecm)); } -TEST_F(WvCasEcmTest, GenerateEcm16BytesContentIvCsaSuccess) { - EXPECT_EQ(OK, wv_cas_ecm_.Initialize(/* content_iv_size= */ 16, +TEST_F(WvCasEcmTest, GenerateEcm8BytesContentIvCsaSuccess) { + EXPECT_EQ(OK, wv_cas_ecm_.Initialize(/* content_iv_size= */ 8, /* key_rotation_enabled= */ true, CryptoMode::kDvbCsa)); std::string actual_ecm; EXPECT_EQ(OK, wv_cas_ecm_.GenerateEcm( - /* even_key= */ kEvenKey, + /* even_key= */ kCsaEvenKey, /* even_content_iv= */ - absl::StrCat(kEvenContentIv8Bytes, kEvenContentIv8Bytes), - /* odd_key= */ kOddKey, + kEvenContentIv8Bytes, + /* odd_key= */ kCsaOddKey, /* odd_content_iv= */ - absl::StrCat(kOddContentIv8Bytes, kOddContentIv8Bytes), + kOddContentIv8Bytes, /* entitlement_key_id= */ kEntitlementKeyId, /* entitlement_key= */ kEntitlementKey, &actual_ecm)); EXPECT_EQ( - "4ad40107c0656e745f6b65795f69642e2e2e2e2e2ea5693deeba52b4cb27e7021eefa2f8" - "c2b25f7d48e60627208f4ecca00703aa2467f28b214546a42320e3fa49f936369c657665" - "6e636f6e746576656e636f6e74656e745f6b65795f69642e2e2e2e2e2e0700509b67763b" - "3f1c356bc1e1dc8bac99a1e2f95c37d9183cbb96582f3a05fdbe29925c37c6c6a45eb552" - "b5ddf87f8a6f6464636f6e742e6f6464636f6e742e", + "4ad4010780656e745f6b65795f69642e2e2e2e2e2e1970666a56b136d5d63b009c1a514a" + "948b8c0a380f2c9965134faac9d92992627abdd06f4a268bd12f8989373aece8bd657665" + "6e636f6e74656e745f6b65795f69642e2e2e2e2e2eda65f122610af2c9c9fa7ad18d07f4" + "3faab4190ac47c0d974547e8615d7bc64beb665a2d7c36f687ad8ec518e83062076f6464" + "636f6e742e", absl::BytesToHexString(actual_ecm)); } -TEST_F(WvCasEcmTest, GenerateSingleKeyEcm16BytesContentIvCsaSuccess) { - EXPECT_EQ(OK, wv_cas_ecm_.Initialize(/* content_iv_size= */ 16, +TEST_F(WvCasEcmTest, GenerateSingleKeyEcm8BytesContentIvCsaSuccess) { + EXPECT_EQ(OK, wv_cas_ecm_.Initialize(/* content_iv_size= */ 8, /* key_rotation_enabled= */ false, CryptoMode::kDvbCsa)); std::string actual_ecm; EXPECT_EQ(OK, wv_cas_ecm_.GenerateSingleKeyEcm( - /* even_key= */ kEvenKey, + /* even_key= */ kCsaEvenKey, /* even_content_iv= */ - absl::StrCat(kEvenContentIv8Bytes, kEvenContentIv8Bytes), + kEvenContentIv8Bytes, /* entitlement_key_id= */ kEntitlementKeyId, /* entitlement_key= */ kEntitlementKey, &actual_ecm)); EXPECT_EQ( - "4ad40106c0656e745f6b65795f69642e2e2e2e2e2ea5693deeba52b4cb27e7021eefa2f8" - "c2b25f7d48e60627208f4ecca00703aa2467f28b214546a42320e3fa49f936369c657665" - "6e636f6e746576656e636f6e74", + "4ad4010680656e745f6b65795f69642e2e2e2e2e2e1970666a56b136d5d63b009c1a514a" + "948b8c0a380f2c9965134faac9d92992627abdd06f4a268bd12f8989373aece8bd657665" + "6e636f6e74", absl::BytesToHexString(actual_ecm)); }