Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: experiment with decrypt and re-encrypt #21

Merged
merged 16 commits into from
Apr 17, 2024
Merged
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;
lfordyce marked this conversation as resolved.
Show resolved Hide resolved
live_config.track_type = LiveConfig::TrackType::VIDEO;
live_config.protection_scheme = LiveConfig::EncryptionScheme::AES_128;
live_config.segment_number = i;
lfordyce marked this conversation as resolved.
Show resolved Hide resolved
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));
lfordyce marked this conversation as resolved.
Show resolved Hide resolved
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.
Loading