Fix SAMPLE-AES EC3 encryption
The IV was incorrectly updated across samples. Created a new cryptor SampleAesEc3Cryptor specially for SAMPLE-AES EC3 encryption / decryption. The new cryptor uses constant-iv, and makes sure the IV is reset to the initial value at the beginning of each audio frame and chained across syncframes within the the audio frame. Also added E-AC3 end to end test. Fixes #279 Change-Id: I0aa60c17836daeef5ba433a05e5ff0906191d9ac
This commit is contained in:
parent
1455f43c02
commit
9ecee46658
|
@ -1051,6 +1051,22 @@ class PackagerFunctionalTest(PackagerAppTest):
|
||||||
os.path.join(self.tmp_dir, 'video.m3u8'),
|
os.path.join(self.tmp_dir, 'video.m3u8'),
|
||||||
'bear-640x360-v-mp4-cenc-golden.m3u8')
|
'bear-640x360-v-mp4-cenc-golden.m3u8')
|
||||||
|
|
||||||
|
def testPackageWithEc3AndHlsSingleSegmentMp4Encrypted(self):
|
||||||
|
self.assertPackageSuccess(
|
||||||
|
self._GetStreams(
|
||||||
|
['audio', 'video'], hls=True, test_files=['bear-640x360-ec3.mp4']),
|
||||||
|
self._GetFlags(encryption=True, output_hls=True))
|
||||||
|
self._DiffGold(self.output[0], 'bear-640x360-ec3-a-cenc-golden.mp4')
|
||||||
|
self._DiffGold(self.output[1], 'bear-640x360-ec3-v-cenc-golden.mp4')
|
||||||
|
self._DiffGold(self.hls_master_playlist_output,
|
||||||
|
'bear-640x360-ec3-av-mp4-master-cenc-golden.m3u8')
|
||||||
|
self._DiffGold(
|
||||||
|
os.path.join(self.tmp_dir, 'audio.m3u8'),
|
||||||
|
'bear-640x360-ec3-a-mp4-cenc-golden.m3u8')
|
||||||
|
self._DiffGold(
|
||||||
|
os.path.join(self.tmp_dir, 'video.m3u8'),
|
||||||
|
'bear-640x360-ec3-v-mp4-cenc-golden.m3u8')
|
||||||
|
|
||||||
def testPackageWithHlsSingleSegmentMp4EncryptedAndAdCues(self):
|
def testPackageWithHlsSingleSegmentMp4EncryptedAndAdCues(self):
|
||||||
self.assertPackageSuccess(
|
self.assertPackageSuccess(
|
||||||
self._GetStreams(['audio', 'video'], hls=True),
|
self._GetStreams(['audio', 'video'], hls=True),
|
||||||
|
|
Binary file not shown.
|
@ -0,0 +1,17 @@
|
||||||
|
#EXTM3U
|
||||||
|
#EXT-X-VERSION:6
|
||||||
|
## Generated with https://github.com/google/shaka-packager version <tag>-<hash>-<test>
|
||||||
|
#EXT-X-TARGETDURATION:2
|
||||||
|
#EXT-X-PLAYLIST-TYPE:VOD
|
||||||
|
#EXT-X-MAP:URI="output_audio.mp4",BYTERANGE="909@0"
|
||||||
|
#EXT-X-KEY:METHOD=SAMPLE-AES-CENC,URI="data:text/plain;base64,MTIzNDU2Nzg5MDEyMzQ1Ng==",KEYFORMAT="identity"
|
||||||
|
#EXTINF:1.010,
|
||||||
|
#EXT-X-BYTERANGE:24460@977
|
||||||
|
output_audio.mp4
|
||||||
|
#EXTINF:0.975,
|
||||||
|
#EXT-X-BYTERANGE:23899
|
||||||
|
output_audio.mp4
|
||||||
|
#EXTINF:0.766,
|
||||||
|
#EXT-X-BYTERANGE:18811
|
||||||
|
output_audio.mp4
|
||||||
|
#EXT-X-ENDLIST
|
|
@ -0,0 +1,5 @@
|
||||||
|
#EXTM3U
|
||||||
|
## Generated with https://github.com/google/shaka-packager version <tag>-<hash>-<test>
|
||||||
|
#EXT-X-MEDIA:TYPE=AUDIO,URI="audio.m3u8",GROUP-ID="audio",NAME="stream_0",AUTOSELECT=YES,CHANNELS="2"
|
||||||
|
#EXT-X-STREAM-INF:BANDWIDTH=1174135,CODECS="avc1.64001e,ec-3",RESOLUTION=640x360,AUDIO="audio"
|
||||||
|
video.m3u8
|
Binary file not shown.
|
@ -0,0 +1,17 @@
|
||||||
|
#EXTM3U
|
||||||
|
#EXT-X-VERSION:6
|
||||||
|
## Generated with https://github.com/google/shaka-packager version <tag>-<hash>-<test>
|
||||||
|
#EXT-X-TARGETDURATION:2
|
||||||
|
#EXT-X-PLAYLIST-TYPE:VOD
|
||||||
|
#EXT-X-MAP:URI="output_video.mp4",BYTERANGE="1091@0"
|
||||||
|
#EXT-X-KEY:METHOD=SAMPLE-AES-CENC,URI="data:text/plain;base64,MTIzNDU2Nzg5MDEyMzQ1Ng==",KEYFORMAT="identity"
|
||||||
|
#EXTINF:1.001,
|
||||||
|
#EXT-X-BYTERANGE:93805@1159
|
||||||
|
output_video.mp4
|
||||||
|
#EXTINF:1.001,
|
||||||
|
#EXT-X-BYTERANGE:122340
|
||||||
|
output_video.mp4
|
||||||
|
#EXTINF:0.734,
|
||||||
|
#EXT-X-BYTERANGE:80067
|
||||||
|
output_video.mp4
|
||||||
|
#EXT-X-ENDLIST
|
|
@ -68,7 +68,8 @@ class AesCryptor {
|
||||||
}
|
}
|
||||||
/// @}
|
/// @}
|
||||||
|
|
||||||
/// Set IV.
|
/// Set IV. SetIv() implementation guarantees that the iv passed to SetIv()
|
||||||
|
/// is set to iv() and then calls SetIvInternal().
|
||||||
/// @return true if successful, false if the input is invalid.
|
/// @return true if successful, false if the input is invalid.
|
||||||
bool SetIv(const std::vector<uint8_t>& iv);
|
bool SetIv(const std::vector<uint8_t>& iv);
|
||||||
|
|
||||||
|
@ -138,5 +139,3 @@ class AesCryptor {
|
||||||
} // namespace shaka
|
} // namespace shaka
|
||||||
|
|
||||||
#endif // PACKAGER_MEDIA_BASE_AES_CRYPTOR_H_
|
#endif // PACKAGER_MEDIA_BASE_AES_CRYPTOR_H_
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -15,6 +15,8 @@
|
||||||
'sources': [
|
'sources': [
|
||||||
'encryption_handler.cc',
|
'encryption_handler.cc',
|
||||||
'encryption_handler.h',
|
'encryption_handler.h',
|
||||||
|
'sample_aes_ec3_cryptor.cc',
|
||||||
|
'sample_aes_ec3_cryptor.h',
|
||||||
],
|
],
|
||||||
'dependencies': [
|
'dependencies': [
|
||||||
'../base/media_base.gyp:media_base',
|
'../base/media_base.gyp:media_base',
|
||||||
|
@ -26,6 +28,7 @@
|
||||||
'type': '<(gtest_target_type)',
|
'type': '<(gtest_target_type)',
|
||||||
'sources': [
|
'sources': [
|
||||||
'encryption_handler_unittest.cc',
|
'encryption_handler_unittest.cc',
|
||||||
|
'sample_aes_ec3_cryptor_unittest.cc',
|
||||||
],
|
],
|
||||||
'dependencies': [
|
'dependencies': [
|
||||||
'../../testing/gtest.gyp:gtest',
|
'../../testing/gtest.gyp:gtest',
|
||||||
|
|
|
@ -14,13 +14,14 @@
|
||||||
|
|
||||||
#include "packager/media/base/aes_encryptor.h"
|
#include "packager/media/base/aes_encryptor.h"
|
||||||
#include "packager/media/base/aes_pattern_cryptor.h"
|
#include "packager/media/base/aes_pattern_cryptor.h"
|
||||||
|
#include "packager/media/base/audio_stream_info.h"
|
||||||
#include "packager/media/base/key_source.h"
|
#include "packager/media/base/key_source.h"
|
||||||
#include "packager/media/base/media_sample.h"
|
#include "packager/media/base/media_sample.h"
|
||||||
#include "packager/media/base/audio_stream_info.h"
|
|
||||||
#include "packager/media/base/video_stream_info.h"
|
#include "packager/media/base/video_stream_info.h"
|
||||||
#include "packager/media/codecs/video_slice_header_parser.h"
|
#include "packager/media/codecs/video_slice_header_parser.h"
|
||||||
#include "packager/media/codecs/vp8_parser.h"
|
#include "packager/media/codecs/vp8_parser.h"
|
||||||
#include "packager/media/codecs/vp9_parser.h"
|
#include "packager/media/codecs/vp9_parser.h"
|
||||||
|
#include "packager/media/crypto/sample_aes_ec3_cryptor.h"
|
||||||
|
|
||||||
namespace shaka {
|
namespace shaka {
|
||||||
namespace media {
|
namespace media {
|
||||||
|
@ -280,14 +281,6 @@ Status EncryptionHandler::ProcessMediaSample(
|
||||||
}
|
}
|
||||||
DCHECK_EQ(decrypt_config->GetTotalSizeOfSubsamples(),
|
DCHECK_EQ(decrypt_config->GetTotalSizeOfSubsamples(),
|
||||||
clear_sample->data_size());
|
clear_sample->data_size());
|
||||||
} else if (codec_ == kCodecEAC3 &&
|
|
||||||
protection_scheme_ == kAppleSampleAesProtectionScheme) {
|
|
||||||
if (!SampleAesEncryptEac3Frame(clear_sample->data(),
|
|
||||||
clear_sample->data_size(),
|
|
||||||
&cipher_sample_data.get()[0])) {
|
|
||||||
return Status(error::ENCRYPTION_FAILURE,
|
|
||||||
"Failed to encrypt E-AC3 frame.");
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
memcpy(&cipher_sample_data.get()[0], clear_sample->data(),
|
memcpy(&cipher_sample_data.get()[0], clear_sample->data(),
|
||||||
std::min(clear_sample->data_size(), leading_clear_bytes_size_));
|
std::min(clear_sample->data_size(), leading_clear_bytes_size_));
|
||||||
|
@ -330,6 +323,7 @@ Status EncryptionHandler::SetupProtectionPattern(StreamType stream_type) {
|
||||||
case kCodecAAC:
|
case kCodecAAC:
|
||||||
FALLTHROUGH_INTENDED;
|
FALLTHROUGH_INTENDED;
|
||||||
case kCodecAC3:
|
case kCodecAC3:
|
||||||
|
FALLTHROUGH_INTENDED;
|
||||||
case kCodecEAC3:
|
case kCodecEAC3:
|
||||||
// Audio is whole sample encrypted. We could not use a
|
// Audio is whole sample encrypted. We could not use a
|
||||||
// crypto_byte_block_ of 1 here as if there is one crypto block
|
// crypto_byte_block_ of 1 here as if there is one crypto block
|
||||||
|
@ -337,7 +331,10 @@ Status EncryptionHandler::SetupProtectionPattern(StreamType stream_type) {
|
||||||
// encrypted for audio.
|
// encrypted for audio.
|
||||||
crypt_byte_block_ = 0u;
|
crypt_byte_block_ = 0u;
|
||||||
skip_byte_block_ = 0u;
|
skip_byte_block_ = 0u;
|
||||||
leading_clear_bytes_size_ = kAudioLeadingClearBytesSize;
|
// E-AC3 encryption is handled by SampleAesEc3Cryptor, which also
|
||||||
|
// manages leading clear bytes.
|
||||||
|
leading_clear_bytes_size_ =
|
||||||
|
codec_ == kCodecEAC3 ? 0 : kAudioLeadingClearBytesSize;
|
||||||
min_protected_data_size_ = leading_clear_bytes_size_ + 15u;
|
min_protected_data_size_ = leading_clear_bytes_size_ + 15u;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
|
@ -371,6 +368,7 @@ Status EncryptionHandler::SetupProtectionPattern(StreamType stream_type) {
|
||||||
// Not using pattern encryption.
|
// Not using pattern encryption.
|
||||||
crypt_byte_block_ = 0u;
|
crypt_byte_block_ = 0u;
|
||||||
skip_byte_block_ = 0u;
|
skip_byte_block_ = 0u;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
return Status::OK;
|
return Status::OK;
|
||||||
}
|
}
|
||||||
|
@ -400,17 +398,13 @@ bool EncryptionHandler::CreateEncryptor(const EncryptionKey& encryption_key) {
|
||||||
break;
|
break;
|
||||||
case kAppleSampleAesProtectionScheme:
|
case kAppleSampleAesProtectionScheme:
|
||||||
if (crypt_byte_block_ == 0 && skip_byte_block_ == 0) {
|
if (crypt_byte_block_ == 0 && skip_byte_block_ == 0) {
|
||||||
auto constant_iv_flag = AesCryptor::kUseConstantIv;
|
|
||||||
if (codec_ == kCodecEAC3) {
|
if (codec_ == kCodecEAC3) {
|
||||||
// MPEG-2 Stream Encryption Format for HTTP Live Streaming 2.3.1.3
|
encryptor.reset(new SampleAesEc3Cryptor(
|
||||||
// Enhanced AC-3: Within an Enhanced AC-3 audio frame, the AES-128
|
std::unique_ptr<AesCryptor>(new AesCbcEncryptor(kNoPadding))));
|
||||||
// cipher block chaining (CBC) initialization vector (IV) is not reset
|
} else {
|
||||||
// at syncframe boundaries. The IV is reset at the beginning of each
|
encryptor.reset(
|
||||||
// audio frame.
|
new AesCbcEncryptor(kNoPadding, AesCryptor::kUseConstantIv));
|
||||||
// We'll manage the reset of IV outside of AesCryptor.
|
|
||||||
constant_iv_flag = AesCryptor::kDontUseConstantIv;
|
|
||||||
}
|
}
|
||||||
encryptor.reset(new AesCbcEncryptor(kNoPadding, constant_iv_flag));
|
|
||||||
} else {
|
} else {
|
||||||
encryptor.reset(new AesPatternCryptor(
|
encryptor.reset(new AesPatternCryptor(
|
||||||
crypt_byte_block_, skip_byte_block_,
|
crypt_byte_block_, skip_byte_block_,
|
||||||
|
@ -568,35 +562,6 @@ bool EncryptionHandler::EncryptNalFrame(const uint8_t* source,
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool EncryptionHandler::SampleAesEncryptEac3Frame(const uint8_t* source,
|
|
||||||
size_t source_size,
|
|
||||||
uint8_t* dest) {
|
|
||||||
DCHECK(source);
|
|
||||||
DCHECK(dest);
|
|
||||||
|
|
||||||
std::vector<size_t> syncframe_sizes;
|
|
||||||
if (!ExtractEac3SyncframeSizes(source, source_size, &syncframe_sizes))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
// MPEG-2 Stream Encryption Format for HTTP Live Streaming 2.3.1.3 Enhanced
|
|
||||||
// AC-3: The IV is reset at the beginning of each audio frame.
|
|
||||||
encryptor_->SetIv(encryptor_->iv());
|
|
||||||
|
|
||||||
for (size_t syncframe_size : syncframe_sizes) {
|
|
||||||
memcpy(dest, source, std::min(syncframe_size, leading_clear_bytes_size_));
|
|
||||||
if (syncframe_size > leading_clear_bytes_size_) {
|
|
||||||
// The residual block is left unecrypted (copied without encryption). No
|
|
||||||
// need to do special handling here.
|
|
||||||
EncryptBytes(source + leading_clear_bytes_size_,
|
|
||||||
syncframe_size - leading_clear_bytes_size_,
|
|
||||||
dest + leading_clear_bytes_size_);
|
|
||||||
}
|
|
||||||
source += syncframe_size;
|
|
||||||
dest += syncframe_size;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void EncryptionHandler::EncryptBytes(const uint8_t* source,
|
void EncryptionHandler::EncryptBytes(const uint8_t* source,
|
||||||
size_t source_size,
|
size_t source_size,
|
||||||
uint8_t* dest) {
|
uint8_t* dest) {
|
||||||
|
@ -606,48 +571,6 @@ void EncryptionHandler::EncryptBytes(const uint8_t* source,
|
||||||
CHECK(encryptor_->Crypt(source, source_size, dest));
|
CHECK(encryptor_->Crypt(source, source_size, dest));
|
||||||
}
|
}
|
||||||
|
|
||||||
bool EncryptionHandler::ExtractEac3SyncframeSizes(
|
|
||||||
const uint8_t* source,
|
|
||||||
size_t source_size,
|
|
||||||
std::vector<size_t>* syncframe_sizes) {
|
|
||||||
DCHECK(source);
|
|
||||||
DCHECK(syncframe_sizes);
|
|
||||||
|
|
||||||
syncframe_sizes->clear();
|
|
||||||
BufferReader frame(source, source_size);
|
|
||||||
// ASTC Standard A/52:2012 Annex E: Enhanced AC-3.
|
|
||||||
while (frame.HasBytes(1)) {
|
|
||||||
uint16_t syncword;
|
|
||||||
if (!frame.Read2(&syncword)) {
|
|
||||||
LOG(ERROR) << "Not enough bytes for syncword.";
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (syncword != 0x0B77) {
|
|
||||||
LOG(ERROR) << "Invalid E-AC3 frame. Seeing 0x" << std::hex << syncword
|
|
||||||
<< std::dec
|
|
||||||
<< ". The sync frame does not start with "
|
|
||||||
"the valid syncword 0x0B77.";
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
uint16_t stream_type_and_syncframe_size;
|
|
||||||
if (!frame.Read2(&stream_type_and_syncframe_size)) {
|
|
||||||
LOG(ERROR) << "Not enough bytes for syncframe size.";
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
// frmsiz = least significant 11 bits. syncframe_size is (frmsiz + 1) * 2.
|
|
||||||
const size_t syncframe_size =
|
|
||||||
((stream_type_and_syncframe_size & 0x7FF) + 1) * 2;
|
|
||||||
if (!frame.SkipBytes(syncframe_size - sizeof(syncword) -
|
|
||||||
sizeof(stream_type_and_syncframe_size))) {
|
|
||||||
LOG(ERROR) << "Not enough bytes for syncframe. Expecting "
|
|
||||||
<< syncframe_size << " bytes.";
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
syncframe_sizes->push_back(syncframe_size);
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void EncryptionHandler::InjectVpxParserForTesting(
|
void EncryptionHandler::InjectVpxParserForTesting(
|
||||||
std::unique_ptr<VPxParser> vpx_parser) {
|
std::unique_ptr<VPxParser> vpx_parser) {
|
||||||
vpx_parser_ = std::move(vpx_parser);
|
vpx_parser_ = std::move(vpx_parser);
|
||||||
|
|
|
@ -0,0 +1,115 @@
|
||||||
|
// Copyright 2018 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/crypto/sample_aes_ec3_cryptor.h"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
|
#include "packager/base/logging.h"
|
||||||
|
#include "packager/media/base/buffer_reader.h"
|
||||||
|
|
||||||
|
namespace shaka {
|
||||||
|
namespace media {
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
bool ExtractEac3SyncframeSizes(const uint8_t* source,
|
||||||
|
size_t source_size,
|
||||||
|
std::vector<size_t>* syncframe_sizes) {
|
||||||
|
DCHECK(source);
|
||||||
|
DCHECK(syncframe_sizes);
|
||||||
|
|
||||||
|
syncframe_sizes->clear();
|
||||||
|
BufferReader frame(source, source_size);
|
||||||
|
// ASTC Standard A/52:2012 Annex E: Enhanced AC-3.
|
||||||
|
while (frame.HasBytes(1)) {
|
||||||
|
uint16_t syncword;
|
||||||
|
if (!frame.Read2(&syncword)) {
|
||||||
|
LOG(ERROR) << "Not enough bytes for syncword.";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (syncword != 0x0B77) {
|
||||||
|
LOG(ERROR) << "Invalid E-AC3 frame. Seeing 0x" << std::hex << syncword
|
||||||
|
<< std::dec
|
||||||
|
<< ". The sync frame does not start with "
|
||||||
|
"the valid syncword 0x0B77.";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
uint16_t stream_type_and_syncframe_size;
|
||||||
|
if (!frame.Read2(&stream_type_and_syncframe_size)) {
|
||||||
|
LOG(ERROR) << "Not enough bytes for syncframe size.";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// frmsiz = least significant 11 bits. syncframe_size is (frmsiz + 1) * 2.
|
||||||
|
const size_t syncframe_size =
|
||||||
|
((stream_type_and_syncframe_size & 0x7FF) + 1) * 2;
|
||||||
|
if (!frame.SkipBytes(syncframe_size - sizeof(syncword) -
|
||||||
|
sizeof(stream_type_and_syncframe_size))) {
|
||||||
|
LOG(ERROR) << "Not enough bytes for syncframe. Expecting "
|
||||||
|
<< syncframe_size << " bytes.";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
syncframe_sizes->push_back(syncframe_size);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
SampleAesEc3Cryptor::SampleAesEc3Cryptor(std::unique_ptr<AesCryptor> cryptor)
|
||||||
|
: AesCryptor(AesCryptor::kUseConstantIv), cryptor_(std::move(cryptor)) {
|
||||||
|
DCHECK(cryptor_);
|
||||||
|
DCHECK(!cryptor_->use_constant_iv());
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SampleAesEc3Cryptor::InitializeWithIv(const std::vector<uint8_t>& key,
|
||||||
|
const std::vector<uint8_t>& iv) {
|
||||||
|
return SetIv(iv) && cryptor_->InitializeWithIv(key, iv);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SampleAesEc3Cryptor::CryptInternal(const uint8_t* text,
|
||||||
|
size_t text_size,
|
||||||
|
uint8_t* crypt_text,
|
||||||
|
size_t* crypt_text_size) {
|
||||||
|
// |crypt_text_size| is the same as |text_size|.
|
||||||
|
if (*crypt_text_size < text_size) {
|
||||||
|
LOG(ERROR) << "Expecting output size of at least " << text_size
|
||||||
|
<< " bytes.";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
*crypt_text_size = text_size;
|
||||||
|
|
||||||
|
std::vector<size_t> syncframe_sizes;
|
||||||
|
if (!ExtractEac3SyncframeSizes(text, text_size, &syncframe_sizes))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// MPEG-2 Stream Encryption Format for HTTP Live Streaming 2.3.1.3 Enhanced
|
||||||
|
// AC-3: The first 16 bytes, starting with the syncframe() header, are not
|
||||||
|
// encrypted.
|
||||||
|
const size_t kLeadingClearBytesSize = 16u;
|
||||||
|
|
||||||
|
for (size_t syncframe_size : syncframe_sizes) {
|
||||||
|
memcpy(crypt_text, text, std::min(syncframe_size, kLeadingClearBytesSize));
|
||||||
|
if (syncframe_size > kLeadingClearBytesSize) {
|
||||||
|
// The residual block is left untouched (copied without
|
||||||
|
// encryption/decryption). No need to do special handling here.
|
||||||
|
if (!cryptor_->Crypt(text + kLeadingClearBytesSize,
|
||||||
|
syncframe_size - kLeadingClearBytesSize,
|
||||||
|
crypt_text + kLeadingClearBytesSize)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
text += syncframe_size;
|
||||||
|
crypt_text += syncframe_size;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SampleAesEc3Cryptor::SetIvInternal() {
|
||||||
|
CHECK(cryptor_->SetIv(iv()));
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace media
|
||||||
|
} // namespace shaka
|
|
@ -0,0 +1,47 @@
|
||||||
|
// Copyright 2018 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/base/aes_cryptor.h"
|
||||||
|
|
||||||
|
#ifndef PACKAGER_MEDIA_CRYPTO_SAMPLE_AES_EC3_CRYPTOR_H_
|
||||||
|
#define PACKAGER_MEDIA_CRYPTO_SAMPLE_AES_EC3_CRYPTOR_H_
|
||||||
|
|
||||||
|
namespace shaka {
|
||||||
|
namespace media {
|
||||||
|
|
||||||
|
/// Implements SAMPLE-AES E-AC3 encryption / decryption per specification at:
|
||||||
|
/// https://goo.gl/1sgcwY.
|
||||||
|
class SampleAesEc3Cryptor : public AesCryptor {
|
||||||
|
public:
|
||||||
|
/// @param cryptor points to an AesCryptor instance which performs the actual
|
||||||
|
/// encryption/decryption. Note that @a cryptor shall not use constant
|
||||||
|
/// iv.
|
||||||
|
explicit SampleAesEc3Cryptor(std::unique_ptr<AesCryptor> cryptor);
|
||||||
|
|
||||||
|
/// @name AesCryptor implementation overrides.
|
||||||
|
/// @{
|
||||||
|
bool InitializeWithIv(const std::vector<uint8_t>& key,
|
||||||
|
const std::vector<uint8_t>& iv) override;
|
||||||
|
/// @}
|
||||||
|
|
||||||
|
private:
|
||||||
|
SampleAesEc3Cryptor(const SampleAesEc3Cryptor&) = delete;
|
||||||
|
SampleAesEc3Cryptor& operator=(const SampleAesEc3Cryptor&) = delete;
|
||||||
|
|
||||||
|
// AesCryptor implementation overrides.
|
||||||
|
bool CryptInternal(const uint8_t* text,
|
||||||
|
size_t text_size,
|
||||||
|
uint8_t* crypt_text,
|
||||||
|
size_t* crypt_text_size) override;
|
||||||
|
void SetIvInternal() override;
|
||||||
|
|
||||||
|
std::unique_ptr<AesCryptor> cryptor_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace media
|
||||||
|
} // namespace shaka
|
||||||
|
|
||||||
|
#endif // PACKAGER_MEDIA_CRYPTO_SAMPLE_AES_EC3_CRYPTOR_H_
|
|
@ -0,0 +1,119 @@
|
||||||
|
// Copyright 2018 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/crypto/sample_aes_ec3_cryptor.h"
|
||||||
|
|
||||||
|
#include <gmock/gmock.h>
|
||||||
|
#include <gtest/gtest.h>
|
||||||
|
|
||||||
|
using ::testing::_;
|
||||||
|
using ::testing::Invoke;
|
||||||
|
using ::testing::Return;
|
||||||
|
|
||||||
|
namespace shaka {
|
||||||
|
namespace media {
|
||||||
|
|
||||||
|
class MockAesCryptor : public AesCryptor {
|
||||||
|
public:
|
||||||
|
MockAesCryptor() : AesCryptor(kDontUseConstantIv) {}
|
||||||
|
|
||||||
|
MOCK_METHOD2(InitializeWithIv,
|
||||||
|
bool(const std::vector<uint8_t>& key,
|
||||||
|
const std::vector<uint8_t>& iv));
|
||||||
|
MOCK_METHOD4(CryptInternal,
|
||||||
|
bool(const uint8_t* text,
|
||||||
|
size_t text_size,
|
||||||
|
uint8_t* crypt_text,
|
||||||
|
size_t* crypt_text_size));
|
||||||
|
MOCK_METHOD0(SetIvInternal, void());
|
||||||
|
};
|
||||||
|
|
||||||
|
class SampleAesEc3CryptorTest : public ::testing::Test {
|
||||||
|
public:
|
||||||
|
SampleAesEc3CryptorTest()
|
||||||
|
: mock_cryptor_(new MockAesCryptor),
|
||||||
|
ec3_cryptor_(std::unique_ptr<MockAesCryptor>(mock_cryptor_)) {}
|
||||||
|
|
||||||
|
void SetUp() {
|
||||||
|
std::vector<uint8_t> key(16, 'k');
|
||||||
|
std::vector<uint8_t> iv(8, 'i');
|
||||||
|
EXPECT_CALL(*mock_cryptor_, InitializeWithIv(key, iv))
|
||||||
|
.WillOnce(Return(true));
|
||||||
|
EXPECT_TRUE(ec3_cryptor_.InitializeWithIv(key, iv));
|
||||||
|
EXPECT_EQ(iv, ec3_cryptor_.iv());
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
MockAesCryptor* mock_cryptor_; // Owned by |ec3_cryptor_|.
|
||||||
|
SampleAesEc3Cryptor ec3_cryptor_;
|
||||||
|
};
|
||||||
|
|
||||||
|
TEST_F(SampleAesEc3CryptorTest, Crypt) {
|
||||||
|
const std::vector<uint8_t> text = {
|
||||||
|
// First syncframe with 20 bytes.
|
||||||
|
0x0B, 0x77, 0x00, 0x09, 0x05, 0x06, 0x07, 0x08, 0x09, 0x10, 0x11, 0x12,
|
||||||
|
0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x20,
|
||||||
|
// Second syncframe with 26 bytes.
|
||||||
|
0x0B, 0x77, 0x00, 0x0C, 0x15, 0x16, 0x17, 0x18, 0x19, 0x20, 0x21, 0x22,
|
||||||
|
0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x30, 0x31, 0x32, 0x33, 0x34,
|
||||||
|
0x35, 0x36,
|
||||||
|
// Third syncframe with 16 bytes.
|
||||||
|
0x0B, 0x77, 0x00, 0x07, 0x15, 0x26, 0x27, 0x28, 0x29, 0x30, 0x31, 0x32,
|
||||||
|
0x33, 0x34, 0x35, 0x36};
|
||||||
|
|
||||||
|
EXPECT_CALL(*mock_cryptor_, CryptInternal(_, _, _, _))
|
||||||
|
.WillRepeatedly(Invoke([](const uint8_t* text, size_t text_size,
|
||||||
|
uint8_t* crypt_text, size_t* crypt_text_size) {
|
||||||
|
*crypt_text_size = text_size;
|
||||||
|
for (size_t i = 0; i < text_size; ++i) {
|
||||||
|
*crypt_text++ = *text++ + 0x40;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}));
|
||||||
|
|
||||||
|
const std::vector<uint8_t> expected_crypt_text = {
|
||||||
|
// First syncframe with 20 bytes.
|
||||||
|
0x0B, 0x77, 0x00, 0x09, 0x05, 0x06, 0x07, 0x08, 0x09, 0x10, 0x11, 0x12,
|
||||||
|
0x13, 0x14, 0x15, 0x16, 0x57, 0x58, 0x59, 0x60,
|
||||||
|
// Second syncframe with 26 bytes.
|
||||||
|
0x0B, 0x77, 0x00, 0x0C, 0x15, 0x16, 0x17, 0x18, 0x19, 0x20, 0x21, 0x22,
|
||||||
|
0x23, 0x24, 0x25, 0x26, 0x67, 0x68, 0x69, 0x70, 0x71, 0x72, 0x73, 0x74,
|
||||||
|
0x75, 0x76,
|
||||||
|
// Third syncframe with 16 bytes.
|
||||||
|
0x0B, 0x77, 0x00, 0x07, 0x15, 0x26, 0x27, 0x28, 0x29, 0x30, 0x31, 0x32,
|
||||||
|
0x33, 0x34, 0x35, 0x36};
|
||||||
|
|
||||||
|
std::vector<uint8_t> crypt_text;
|
||||||
|
ASSERT_TRUE(ec3_cryptor_.Crypt(text, &crypt_text));
|
||||||
|
EXPECT_EQ(expected_crypt_text, crypt_text);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(SampleAesEc3CryptorTest, InvalidEc3Syncword) {
|
||||||
|
const std::vector<uint8_t> text = {0x0C, 0x77, 0x00, 0x09, 0x05, 0x06, 0x07,
|
||||||
|
0x08, 0x09, 0x10, 0x11, 0x12, 0x13, 0x14,
|
||||||
|
0x15, 0x16, 0x17, 0x18, 0x19, 0x20};
|
||||||
|
|
||||||
|
EXPECT_CALL(*mock_cryptor_, CryptInternal(_, _, _, _))
|
||||||
|
.WillRepeatedly(Return(true));
|
||||||
|
|
||||||
|
std::vector<uint8_t> crypt_text;
|
||||||
|
ASSERT_FALSE(ec3_cryptor_.Crypt(text, &crypt_text));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(SampleAesEc3CryptorTest, InvalidEc3SyncframeSize) {
|
||||||
|
const std::vector<uint8_t> text = {0x0B, 0x77, 0x00, 0x0A, 0x05, 0x06, 0x07,
|
||||||
|
0x08, 0x09, 0x10, 0x11, 0x12, 0x13, 0x14,
|
||||||
|
0x15, 0x16, 0x17, 0x18, 0x19, 0x20};
|
||||||
|
|
||||||
|
EXPECT_CALL(*mock_cryptor_, CryptInternal(_, _, _, _))
|
||||||
|
.WillRepeatedly(Return(true));
|
||||||
|
|
||||||
|
std::vector<uint8_t> crypt_text;
|
||||||
|
ASSERT_FALSE(ec3_cryptor_.Crypt(text, &crypt_text));
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace media
|
||||||
|
} // namespace shaka
|
|
@ -33,7 +33,8 @@ bear-640x360-hevc.ts - HEVC + AAC encode, multiplexed into an MPEG2-TS container
|
||||||
// ISO-BMFF streams.
|
// ISO-BMFF streams.
|
||||||
bear-1280x720.mp4 - AVC + AAC encode, mulitplexed into an ISOBMFF container.
|
bear-1280x720.mp4 - AVC + AAC encode, mulitplexed into an ISOBMFF container.
|
||||||
bear-640x360.mp4 - Same as above, but in a different resolution.
|
bear-640x360.mp4 - Same as above, but in a different resolution.
|
||||||
bear-640x360-hevc.mp4 - Same content, but encoded with HEVC.
|
bear-640x360-ec3.mp4 - Same content, but audio encoded with E-AC3.
|
||||||
|
bear-640x360-hevc.mp4 - Same content, but video encoded with HEVC.
|
||||||
bear-320x180.mp4 - Same as above, but in a different resolution.
|
bear-320x180.mp4 - Same as above, but in a different resolution.
|
||||||
bear-640x360-trailing-moov.mp4 - Same content, but with moov box in the end.
|
bear-640x360-trailing-moov.mp4 - Same content, but with moov box in the end.
|
||||||
bear-640x360-trailing-moov-additional-mdat.mp4 - Same content, but with moov box in the end and an additional unused mdat, which should be ignored.
|
bear-640x360-trailing-moov-additional-mdat.mp4 - Same content, but with moov box in the end and an additional unused mdat, which should be ignored.
|
||||||
|
|
Binary file not shown.
Loading…
Reference in New Issue