-
Notifications
You must be signed in to change notification settings - Fork 516
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
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
1 parent
175606c
commit d1d75f4
Showing
12 changed files
with
499 additions
and
10 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
216 changes: 216 additions & 0 deletions
216
packager/media/formats/webm/encrypted_segmenter_unittest.cc
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
Oops, something went wrong.