Skip to content

Commit

Permalink
feat: experiment with decrypt and re-encrypt (#21)
Browse files Browse the repository at this point in the history
  • Loading branch information
lfordyce authored Apr 17, 2024
1 parent 21b8987 commit 8e45add
Show file tree
Hide file tree
Showing 28 changed files with 214 additions and 2 deletions.
6 changes: 5 additions & 1 deletion include/packager/live_packager.h
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ struct LiveConfig {
OutputFormat format;
TrackType track_type;

// TODO: should we allow for keys to be hex string?
/// Encryption parameters
std::vector<uint8_t> iv;
std::vector<uint8_t> key;
std::vector<uint8_t> key_id;
Expand All @@ -118,6 +118,10 @@ struct LiveConfig {
/// Used for timed text packaging to set the fragment decode time when the
/// output format is either VTT in MP4 or TTML in MP4.
int64_t timed_text_decode_time = -1;

/// Decryption parameters
std::vector<uint8_t> decryption_key;
std::vector<uint8_t> decryption_key_id;
};

class LivePackager {
Expand Down
5 changes: 5 additions & 0 deletions include/packager/live_packager_export.h
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,11 @@ typedef struct LivePackagerConfig {
/// Used for timed text packaging to set the fragment decode time when the
/// output format is either VTT in MP4 or TTML in MP4.
int64_t timed_text_decode_time;

/// Decryption parameters
bool enable_decryption;
uint8_t decryption_key[KEY_SIZE];
uint8_t decryption_key_id[KEY_ID_SIZE];
} LivePackagerConfig_t;

typedef struct LivePackager_buffer_s* LivePackagerBuffer_t;
Expand Down
15 changes: 15 additions & 0 deletions packager/live_packager.cc
Original file line number Diff line number Diff line change
Expand Up @@ -334,6 +334,13 @@ Status LivePackager::PackageInit(const Segment& init_segment,

// in order to enable init packaging as a separate execution.
packaging_params.init_segment_only = true;
if (!config_.decryption_key.empty() && !config_.decryption_key_id.empty()) {
DecryptionParams& decryption_params = packaging_params.decryption_params;
decryption_params.key_provider = KeyProvider::kRawKey;
RawKeyParams::KeyInfo& key_info = decryption_params.raw_key.key_map[""];
key_info.key = config_.decryption_key;
key_info.key_id = config_.decryption_key_id;
}

EncryptionParams& encryption_params = packaging_params.encryption_params;
// As a side effect of InitializeEncryption, encryption_params will be
Expand Down Expand Up @@ -392,6 +399,14 @@ Status LivePackager::Package(const Segment& init_segment,
packaging_params.cts_offset_adjustment =
config_.format == LiveConfig::OutputFormat::TS;

if (!config_.decryption_key.empty() && !config_.decryption_key_id.empty()) {
DecryptionParams& decryption_params = packaging_params.decryption_params;
decryption_params.key_provider = KeyProvider::kRawKey;
RawKeyParams::KeyInfo& key_info = decryption_params.raw_key.key_map[""];
key_info.key = config_.decryption_key;
key_info.key_id = config_.decryption_key_id;
}

EncryptionParams& encryption_params = packaging_params.encryption_params;
// As a side effect of InitializeEncryption, encryption_params will be
// modified.
Expand Down
9 changes: 9 additions & 0 deletions packager/live_packager_export.cc
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ LivePackager_t livepackager_new(LivePackagerConfig_t cfg) {
.segment_number = cfg.segment_number,
.m2ts_offset_ms = cfg.m2ts_offset_ms,
.timed_text_decode_time = cfg.timed_text_decode_time,
.decryption_key = {},
.decryption_key_id = {},
};

if (cfg.protection_scheme != ENCRYPTION_SCHEME_NONE) {
Expand All @@ -27,6 +29,13 @@ LivePackager_t livepackager_new(LivePackagerConfig_t cfg) {
converted.key_id = std::vector(cfg.key_id, cfg.key_id + KEY_ID_SIZE);
}

if (cfg.enable_decryption) {
converted.decryption_key =
std::vector(cfg.decryption_key, cfg.decryption_key + KEY_SIZE);
converted.decryption_key_id =
std::vector(cfg.decryption_key_id, cfg.decryption_key_id + KEY_ID_SIZE);
}

return new (std::nothrow)
LivePackager_instance_s{std::make_unique<shaka::LivePackager>(converted)};
}
Expand Down
181 changes: 180 additions & 1 deletion packager/live_packager_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
#include <packager/file.h>
#include <packager/live_packager.h>
#include <packager/media/base/aes_decryptor.h>
#include <packager/media/base/aes_pattern_cryptor.h>
#include <packager/media/base/byte_queue.h>
#include <packager/media/base/key_source.h>
#include <packager/media/base/media_sample.h>
Expand Down Expand Up @@ -48,7 +49,15 @@ const uint8_t kIv[]{
0x08, 0x09, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15,
};

constexpr int kNumSegments = 10;
const char kKeyIdHex[] = "00000000621f2afe7ab2c868d5fd2e2e";
const char kKeyHex[] = "1af987fa084ff3c0f4ad35a6bdab98e2";

std::vector<uint8_t> HexStringToVector(const std::string& hex_str) {
std::string raw_str = absl::HexStringToBytes(hex_str);
return std::vector<uint8_t>(raw_str.begin(), raw_str.end());
}

const int kNumSegments = 10;
constexpr unsigned kSegmentDurationMs = 5000;

std::filesystem::path GetTestDataFilePath(const std::string& name) {
Expand Down Expand Up @@ -706,6 +715,56 @@ TEST_F(LivePackagerBaseTest, VerifyAes128WithDecryption) {
}
}

// Test demonstrates decrypting fmp4 source with one set of keys and
// re-encrypting using a difference encryption schema (AES-128) and different
// set of keys. As validation the re-encryption segments are decrypted and
// compared against decrypted segments.
TEST_F(LivePackagerBaseTest, VerifyPrdDecryptReEncrypt) {
std::vector<uint8_t> init_segment_buffer =
ReadTestDataFile("encrypted/prd_data/init.mp4");
ASSERT_FALSE(init_segment_buffer.empty());

media::AesCbcDecryptor decryptor(media::kPkcs5Padding,
media::AesCryptor::kUseConstantIv);
ASSERT_TRUE(decryptor.InitializeWithIv(key_, iv_));

for (unsigned int i = 1; i <= 7; i++) {
std::string segment_num = absl::StrFormat("encrypted/prd_data/%05d.m4s", i);
std::vector<uint8_t> segment_buffer = ReadTestDataFile(segment_num);
ASSERT_FALSE(segment_buffer.empty());

SegmentData init_seg(init_segment_buffer.data(),
init_segment_buffer.size());
SegmentData media_seg(segment_buffer.data(), segment_buffer.size());

LiveConfig live_config;
live_config.format = LiveConfig::OutputFormat::TS;
live_config.track_type = LiveConfig::TrackType::VIDEO;
live_config.protection_scheme = LiveConfig::EncryptionScheme::AES_128;
live_config.segment_number = i;
live_config.decryption_key = HexStringToVector(kKeyHex);
live_config.decryption_key_id = HexStringToVector(kKeyIdHex);

SetupLivePackagerConfig(live_config);

SegmentBuffer out;
ASSERT_EQ(Status::OK, live_packager_->Package(init_seg, media_seg, out));
ASSERT_GT(out.Size(), 0);

std::string exp_decrypted_segment =
absl::StrFormat("encrypted/prd_data/decrypt/ts/%04d.ts", i);
std::vector<uint8_t> exp_decrypted_segment_buffer =
ReadTestDataFile(exp_decrypted_segment);
ASSERT_FALSE(exp_decrypted_segment_buffer.empty());

std::vector<uint8_t> decrypted;
std::vector<uint8_t> buffer(out.Data(), out.Data() + out.Size());

ASSERT_TRUE(decryptor.Crypt(buffer, &decrypted));
ASSERT_EQ(decrypted, exp_decrypted_segment_buffer);
}
}

TEST_F(LivePackagerBaseTest, EncryptionFailure) {
std::vector<uint8_t> init_segment_buffer = ReadTestDataFile("input/init.mp4");
ASSERT_FALSE(init_segment_buffer.empty());
Expand Down Expand Up @@ -1004,6 +1063,126 @@ INSTANTIATE_TEST_CASE_P(
LiveConfig::OutputFormat::TS, LiveConfig::TrackType::AUDIO,
"audio/en/%05d.m4s", false}));

struct LivePackagerReEncryptCase {
unsigned int num_segments;
std::string init_segment_name;
LiveConfig::EncryptionScheme encryption_scheme;
LiveConfig::OutputFormat output_format;
LiveConfig::TrackType track_type;
const char* media_segment_format;
};

class LivePackagerTestReEncrypt
: public LivePackagerBaseTest,
public ::testing::WithParamInterface<LivePackagerReEncryptCase> {
public:
void SetUp() override {
LivePackagerBaseTest::SetUp();
LiveConfig live_config;
live_config.format = GetParam().output_format;
live_config.track_type = GetParam().track_type;
live_config.protection_scheme = GetParam().encryption_scheme;
live_config.decryption_key = HexStringToVector(kKeyHex);
live_config.decryption_key_id = HexStringToVector(kKeyIdHex);
SetupLivePackagerConfig(live_config);
}

protected:
static std::vector<uint8_t> ReadExpectedData() {
std::vector<uint8_t> buf =
ReadTestDataFile("encrypted/prd_data/decrypt/fmp4/init.mp4");
for (unsigned int i = 0; i < GetParam().num_segments; i++) {
auto seg_buf = ReadTestDataFile(
absl::StrFormat("encrypted/prd_data/decrypt/fmp4/%05d.m4s", i + 1));
buf.insert(buf.end(), seg_buf.begin(), seg_buf.end());
}

return buf;
}

static std::unique_ptr<media::KeySource> MakeKeySource() {
RawKeyParams raw_key;
RawKeyParams::KeyInfo& key_info = raw_key.key_map[""];
key_info.key = {std::begin(kKey), std::end(kKey)};
key_info.key_id = {std::begin(kKeyId), std::end(kKeyId)};
key_info.iv = {std::begin(kIv), std::end(kIv)};

return media::RawKeySource::Create(raw_key);
}

std::unique_ptr<media::KeySource> key_source_ = MakeKeySource();
std::unique_ptr<MP4MediaParserTest> parser_noenc_ =
std::make_unique<MP4MediaParserTest>(nullptr);
std::unique_ptr<MP4MediaParserTest> parser_enc_ =
std::make_unique<MP4MediaParserTest>(key_source_.get());
};

TEST_P(LivePackagerTestReEncrypt, VerifyReEncryption) {
std::vector<uint8_t> init_segment_buffer =
ReadTestDataFile(GetParam().init_segment_name);
ASSERT_FALSE(init_segment_buffer.empty());

SegmentData init_seg(init_segment_buffer.data(), init_segment_buffer.size());

SegmentBuffer actual_buf;
ASSERT_EQ(Status::OK, live_packager_->PackageInit(init_seg, actual_buf));

for (unsigned int i = 0; i < GetParam().num_segments; i++) {
std::string input_fname;
ASSERT_TRUE(
FormatWithIndex(GetParam().media_segment_format, i + 1, input_fname));

std::vector<uint8_t> segment_buffer = ReadTestDataFile(input_fname);
ASSERT_FALSE(segment_buffer.empty());

SegmentBuffer out;
LiveConfig live_config;
live_config.segment_number = i;
live_config.format = GetParam().output_format;
live_config.track_type = GetParam().track_type;
live_config.protection_scheme = GetParam().encryption_scheme;
live_config.decryption_key = HexStringToVector(kKeyHex);
live_config.decryption_key_id = HexStringToVector(kKeyIdHex);

SetupLivePackagerConfig(live_config);

SegmentData media_seg(segment_buffer.data(), segment_buffer.size());
ASSERT_EQ(Status::OK, live_packager_->Package(init_seg, media_seg, out));
ASSERT_GT(out.Size(), 0);
actual_buf.AppendData(out.Data(), out.Size());
}

auto expected_buf = ReadExpectedData();
CHECK(parser_noenc_->Parse(expected_buf.data(), expected_buf.size()));
auto& expected_samples = parser_noenc_->GetSamples();

CHECK(parser_enc_->Parse(actual_buf.Data(), actual_buf.Size()));
auto& actual_samples = parser_enc_->GetSamples();

CHECK_EQ(expected_samples.size(), actual_samples.size());
CHECK(std::equal(
expected_samples.begin(), expected_samples.end(), actual_samples.begin(),
actual_samples.end(), [](const auto& s1, const auto& s2) {
return s1->data_size() == s2->data_size() &&
0 == memcmp(s1->data(), s2->data(), s1->data_size());
}));
}

INSTANTIATE_TEST_CASE_P(
LivePackagerReEncryptTypes,
LivePackagerTestReEncrypt,
::testing::Values(
// Verify decrypt FMP4 and re-encrypt to FMP4 with CENC encryption.
LivePackagerReEncryptCase{
7, "encrypted/prd_data/init.mp4",
LiveConfig::EncryptionScheme::CENC, LiveConfig::OutputFormat::FMP4,
LiveConfig::TrackType::VIDEO, "encrypted/prd_data/%05d.m4s"},
// Verify decrypt FMP4 and re-encrypt to FMP4 with CBCS encryption.
LivePackagerReEncryptCase{
7, "encrypted/prd_data/init.mp4",
LiveConfig::EncryptionScheme::CBCS, LiveConfig::OutputFormat::FMP4,
LiveConfig::TrackType::VIDEO, "encrypted/prd_data/%05d.m4s"}));

struct TimedTextTestCase {
const char* media_segment_format;
const char* expected_segment_format;
Expand Down
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.

0 comments on commit 8e45add

Please sign in to comment.