Skip to content

Commit

Permalink
Add encryption support to WebM.
Browse files Browse the repository at this point in the history
This does not support key-rotation and will give an error.  This is
because WebM does not have a way to indicate a change in key ID using
media segments.

b/22463551

Change-Id: I9b3dac818dc370302a5afc0d25d8a060b64d30cd
  • Loading branch information
TheModMaker committed Jan 15, 2016
1 parent 175606c commit d1d75f4
Show file tree
Hide file tree
Showing 12 changed files with 499 additions and 10 deletions.
1 change: 1 addition & 0 deletions packager/media/base/aes_encryptor.h
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ class AesCtrEncryptor {

/// @name Various forms of encrypt calls.
/// block_offset() will be updated according to input plaintext size.
/// The plaintext and ciphertext pointers can be the same address.
/// @{
bool Encrypt(const uint8_t* plaintext,
size_t plaintext_size,
Expand Down
4 changes: 4 additions & 0 deletions packager/media/base/media_sample.h
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,10 @@ class MediaSample : public base::RefCountedThreadSafe<MediaSample> {
data_.assign(data, data + data_size);
}

void resize_data(const size_t data_size) {
data_.resize(data_size);
}

void set_is_key_frame(bool value) {
is_key_frame_ = value;
}
Expand Down
216 changes: 216 additions & 0 deletions packager/media/formats/webm/encrypted_segmenter_unittest.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,216 @@
// Copyright (c) 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "packager/media/formats/webm/single_segment_segmenter.h"
#include "packager/media/formats/webm/two_pass_single_segment_segmenter.h"

#include <gtest/gtest.h>

#include "packager/base/memory/scoped_ptr.h"
#include "packager/media/formats/webm/segmenter_test_base.h"

namespace edash_packager {
namespace media {
namespace {

const uint64_t kDuration = 1000;
const std::string kKeyId = "68656c6c6f20776f726c64";
const std::string kIv = "0123456789012345";
const std::string kKey = "01234567890123456789012345678901";
const std::string kPsshData = "";

const uint8_t kBasicSupportData[] = {
// ID: EBML Header, Payload Size: 31
0x1a, 0x45, 0xdf, 0xa3, 0x9f,
// EBMLVersion: 1
0x42, 0x86, 0x81, 0x01,
// EBMLReadVersion: 1
0x42, 0xf7, 0x81, 0x01,
// EBMLMaxIDLength: 4
0x42, 0xf2, 0x81, 0x04,
// EBMLMaxSizeLength: 8
0x42, 0xf3, 0x81, 0x08,
// DocType: 'webm'
0x42, 0x82, 0x84, 0x77, 0x65, 0x62, 0x6d,
// DocTypeVersion: 2
0x42, 0x87, 0x81, 0x02,
// DocTypeReadVersion: 2
0x42, 0x85, 0x81, 0x02,
// ID: Segment, Payload Size: 400
0x18, 0x53, 0x80, 0x67, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x90,
// ID: SeekHead, Payload Size: 30
0x11, 0x4d, 0x9b, 0x74, 0x9e,
// ID: Seek, Payload Size: 12
0x4d, 0xbb, 0x8c,
// SeekID: binary(4) (Cluster)
0x53, 0xab, 0x84, 0x1f, 0x43, 0xb6, 0x75,
// SeekPosition: 322
0x53, 0xac, 0x82, 0x01, 0x42,
// ID: Seek, Payload Size: 12
0x4d, 0xbb, 0x8c,
// SeekID: binary(4) (Cues)
0x53, 0xab, 0x84, 0x1c, 0x53, 0xbb, 0x6b,
// SeekPosition: 429
0x53, 0xac, 0x82, 0x01, 0xad,
// ID: Void, Payload Size: 52
0xec, 0xb4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// ID: Info, Payload Size: 88
0x15, 0x49, 0xa9, 0x66, 0xd8,
// TimecodeScale: 1000000
0x2a, 0xd7, 0xb1, 0x83, 0x0f, 0x42, 0x40,
// Duration: float(5000)
0x44, 0x89, 0x84, 0x45, 0x9c, 0x40, 0x00,
// MuxingApp: 'libwebm-0.2.1.0'
0x4d, 0x80, 0x8f, 0x6c, 0x69, 0x62, 0x77, 0x65, 0x62, 0x6d, 0x2d, 0x30,
0x2e, 0x32, 0x2e, 0x31, 0x2e, 0x30,
// WritingApp: 'https://github.com/google/edash-packager version test'
0x57, 0x41, 0xb5,
0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x67, 0x69, 0x74, 0x68,
0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x67, 0x6f, 0x6f, 0x67, 0x6c,
0x65, 0x2f, 0x65, 0x64, 0x61, 0x73, 0x68, 0x2d, 0x70, 0x61, 0x63, 0x6b,
0x61, 0x67, 0x65, 0x72, 0x20, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e,
0x20, 0x74, 0x65, 0x73, 0x74,
// ID: Tracks, Payload Size: 87
0x16, 0x54, 0xae, 0x6b, 0xd7,
// ID: Track, Payload Size: 85
0xae, 0xd5,
// TrackNumber: 1
0xd7, 0x81, 0x01,
// TrackUID: 1
0x73, 0xc5, 0x81, 0x01,
// TrackType: 1
0x83, 0x81, 0x01,
// CodecID: 'V_VP8'
0x86, 0x85, 0x56, 0x5f, 0x56, 0x50, 0x38,
// Language: 'en'
0x22, 0xb5, 0x9c, 0x82, 0x65, 0x6e,
// ID: ContentEncodings, Payload Size: 43
0x6d, 0x80, 0xab,
// ID: ContentEncoding, Payload Size: 40
0x62, 0x40, 0xa8,
// ContentEncodingOrder: 0
0x50, 0x31, 0x81, 0x00,
// ContentEncodingScope: 1
0x50, 0x32, 0x81, 0x01,
// ContentEncodingType: 1
0x50, 0x33, 0x81, 0x01,
// ID: ContentEncryption, Payload Size: 25
0x50, 0x35, 0x99,
// ContentEncAlgo: 5
0x47, 0xe1, 0x81, 0x05,
// ContentEncKeyID: binary(11)
0x47, 0xe2, 0x8b,
0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x77, 0x6f, 0x72, 0x6c,
0x64,
// ID: ContentEncAESSettings, Payload Size: 4
0x47, 0xe7, 0x84,
// AESSettingsCipherMode: 1
0x47, 0xe8, 0x81, 0x01,
// ID: Video, Payload Size: 14
0xe0, 0x8e,
// PixelWidth: 100
0xb0, 0x81, 0x64,
// PixelHeight: 100
0xba, 0x81, 0x64,
// DisplayWidth: 100
0x54, 0xb0, 0x81, 0x64,
// DisplayHeight: 100
0x54, 0xba, 0x81, 0x64,
// ID: Cluster, Payload Size: 95
0x1f, 0x43, 0xb6, 0x75, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x5f,
// Timecode: 0
0xe7, 0x81, 0x00,
// ID: SimpleBlock, Payload Size: 10
0xa3, 0x8a, 0x81, 0x00, 0x00, 0x80,
// Signal Byte: Clear
0x00,
// Frame Data:
0xde, 0xad, 0xbe, 0xef, 0x00,
// ID: SimpleBlock, Payload Size: 18
0xa3, 0x92, 0x81, 0x03, 0xe8, 0x80,
// Signal Byte: Encrypted
0x01,
// IV:
0x01, 0x23, 0x45, 0x67, 0x89, 0x01, 0x23, 0x45,
// Frame Data:
0xcc, 0x03, 0xef, 0xc4, 0xf4,
// ID: SimpleBlock, Payload Size: 18
0xa3, 0x92, 0x81, 0x07, 0xd0, 0x80,
// Signal Byte: Encrypted
0x01,
// IV:
0x01, 0x23, 0x45, 0x67, 0x89, 0x01, 0x23, 0x46,
// Frame Data:
0xbf, 0x38, 0x72, 0x20, 0xac,
// ID: SimpleBlock, Payload Size: 18
0xa3, 0x92, 0x81, 0x0b, 0xb8, 0x80,
// Signal Byte: Encrypted
0x01,
// IV:
0x01, 0x23, 0x45, 0x67, 0x89, 0x01, 0x23, 0x47,
// Frame Data:
0x0d, 0x8e, 0xae, 0xbe, 0xd0,
// ID: SimpleBlock, Payload Size: 18
0xa3, 0x92, 0x81, 0x0f, 0xa0, 0x80,
// Signal Byte: Encrypted
0x01,
// IV:
0x01, 0x23, 0x45, 0x67, 0x89, 0x01, 0x23, 0x48,
// Frame Data:
0xa5, 0x97, 0xf8, 0x9e, 0x87,
// ID: Cues, Payload Size: 14
0x1c, 0x53, 0xbb, 0x6b, 0x8e,
// ID: CuePoint, Payload Size: 12
0xbb, 0x8c,
// CueTime: 0
0xb3, 0x81, 0x00,
// ID: CueTrackPositions, Payload Size: 7
0xb7, 0x87,
// CueTrack: 1
0xf7, 0x81, 0x01,
// CueClusterPosition: 274
0xf1, 0x82, 0x01, 0x12
};

} // namespace

class EncrypedSegmenterTest : public SegmentTestBase {
public:
EncrypedSegmenterTest() : info_(CreateVideoStreamInfo()) {}

protected:
void InitializeSegmenter(const MuxerOptions& options) {
key_source_ = KeySource::CreateFromHexStrings(kKeyId, kKey, kPsshData, kIv);
ASSERT_NO_FATAL_FAILURE(
CreateAndInitializeSegmenter<webm::SingleSegmentSegmenter>(
options, info_.get(), key_source_.get(), &segmenter_));
}

scoped_refptr<StreamInfo> info_;
scoped_ptr<webm::Segmenter> segmenter_;
scoped_ptr<KeySource> key_source_;
};

TEST_F(EncrypedSegmenterTest, BasicSupport) {
MuxerOptions options = CreateMuxerOptions();
ASSERT_NO_FATAL_FAILURE(InitializeSegmenter(options));

// Write the samples to the Segmenter.
for (int i = 0; i < 5; i++) {
scoped_refptr<MediaSample> sample =
CreateSample(kKeyFrame, kDuration, kNoSideData);
ASSERT_OK(segmenter_->AddSample(sample));
}
ASSERT_OK(segmenter_->Finalize());

ASSERT_FILE_EQ(OutputFileName().c_str(), kBasicSupportData);
}

} // namespace media
} // namespace edash_packager

134 changes: 134 additions & 0 deletions packager/media/formats/webm/encryptor.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
// Copyright 2015 Google Inc. All rights reserved.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file or at
// https://developers.google.com/open-source/licenses/bsd

#include "packager/media/formats/webm/encryptor.h"

#include "packager/media/base/aes_encryptor.h"
#include "packager/media/base/media_sample.h"

namespace edash_packager {
namespace media {
namespace webm {
namespace {

// Generate 64bit IV by default.
const size_t kDefaultIvSize = 8u;

Status CreateContentEncryption(mkvmuxer::Track* track, EncryptionKey* key) {
if (!track->AddContentEncoding()) {
return Status(error::INTERNAL_ERROR,
"Could not add ContentEncoding to track.");
}

mkvmuxer::ContentEncoding* const encoding =
track->GetContentEncodingByIndex(0);
if (!encoding) {
return Status(error::INTERNAL_ERROR,
"Could not add ContentEncoding to track.");
}

mkvmuxer::ContentEncAESSettings* const aes = encoding->enc_aes_settings();
if (!aes) {
return Status(error::INTERNAL_ERROR,
"Error getting ContentEncAESSettings.");
}
if (aes->cipher_mode() != mkvmuxer::ContentEncAESSettings::kCTR) {
return Status(error::INTERNAL_ERROR, "Cipher Mode is not CTR.");
}

if (!key->key_id.empty() &&
!encoding->SetEncryptionID(
reinterpret_cast<const uint8*>(key->key_id.data()),
key->key_id.size())) {
return Status(error::INTERNAL_ERROR, "Error setting encryption ID.");
}
return Status::OK;
}

} // namespace

Encryptor::Encryptor() {}

Encryptor::~Encryptor() {}

Status Encryptor::Initialize(MuxerListener* muxer_listener,
KeySource::TrackType track_type,
KeySource* key_source) {
DCHECK(key_source);
return CreateEncryptor(muxer_listener, track_type, key_source);
}

Status Encryptor::AddTrackInfo(mkvmuxer::Track* track) {
DCHECK(key_);
return CreateContentEncryption(track, key_.get());
}

Status Encryptor::EncryptFrame(scoped_refptr<MediaSample> sample,
bool encrypt_frame) {
DCHECK(encryptor_);

const size_t sample_size = sample->data_size();
if (encrypt_frame) {
// | 1 | iv | enc_data |
const size_t iv_size = encryptor_->iv().size();
sample->resize_data(sample_size + iv_size + 1);
uint8_t* sample_data = sample->writable_data();

// Encrypt the data in-place.
if (!encryptor_->Encrypt(sample_data, sample_size, sample_data)) {
return Status(error::MUXER_FAILURE, "Failed to encrypt the frame.");
}

// First move the sample data to after the IV; then write the IV and signal
// byte.
memmove(sample_data + iv_size + 1, sample_data, sample_size);
sample_data[0] = 0x01;
memcpy(sample_data + 1, encryptor_->iv().data(), iv_size);

encryptor_->UpdateIv();
} else {
// | 0 | data |
sample->resize_data(sample_size + 1);
uint8_t* sample_data = sample->writable_data();
memmove(sample_data + 1, sample_data, sample_size);
sample_data[0] = 0x00;
}

return Status::OK;
}

Status Encryptor::CreateEncryptor(MuxerListener* muxer_listener,
KeySource::TrackType track_type,
KeySource* key_source) {
scoped_ptr<EncryptionKey> encryption_key(new EncryptionKey());
Status status = key_source->GetKey(track_type, encryption_key.get());
if (!status.ok())
return status;

scoped_ptr<AesCtrEncryptor> encryptor(new AesCtrEncryptor());
const bool initialized = encryption_key->iv.empty()
? encryptor->InitializeWithRandomIv(
encryption_key->key, kDefaultIvSize)
: encryptor->InitializeWithIv(
encryption_key->key, encryption_key->iv);
if (!initialized)
return Status(error::INTERNAL_ERROR, "Failed to create the encryptor.");

if (muxer_listener) {
const bool kInitialEncryptionInfo = true;
muxer_listener->OnEncryptionInfoReady(
kInitialEncryptionInfo, key_source->UUID(), key_source->SystemName(),
encryption_key->key_id, encryption_key->pssh);
}

key_ = encryption_key.Pass();
encryptor_ = encryptor.Pass();
return Status::OK;
}

} // namespace webm
} // namespace media
} // namespace edash_packager
Loading

0 comments on commit d1d75f4

Please sign in to comment.