Fairplay key system support
Support generation of fairplay key system tag: "com.apple.streamingkeydelivery" when --pssh includes fairplay key system id: // Unofficial fairplay system id extracted from // https://forums.developer.apple.com/thread/6185. const uint8_t kFairplaySystemId[] = {0x29, 0x70, 0x1F, 0xE4, 0x3C, 0xC7, 0x4A, 0x34, 0x8C, 0x5B, 0xAE, 0x90, 0xC7, 0x43, 0x9A, 0x47}; Closes #258
This commit is contained in:
parent
085e038b89
commit
34c5e011a5
1
AUTHORS
1
AUTHORS
|
@ -17,6 +17,7 @@ Anders Hasselqvist <anders.hasselqvist@gmail.com>
|
||||||
Chun-da Chen <capitalm.c@gmail.com>
|
Chun-da Chen <capitalm.c@gmail.com>
|
||||||
Google Inc. <*@google.com>
|
Google Inc. <*@google.com>
|
||||||
Leandro Moreira <leandro.ribeiro.moreira@gmail.com>
|
Leandro Moreira <leandro.ribeiro.moreira@gmail.com>
|
||||||
|
More Screens Ltd. <*@morescreens.net>
|
||||||
Philo Inc. <*@philo.com>
|
Philo Inc. <*@philo.com>
|
||||||
Richard Eklycke <richard@eklycke.se>
|
Richard Eklycke <richard@eklycke.se>
|
||||||
Sergio Ammirata <sergio@ammirata.net>
|
Sergio Ammirata <sergio@ammirata.net>
|
||||||
|
|
|
@ -25,6 +25,7 @@
|
||||||
Anders Hasselqvist <anders.hasselqvist@gmail.com>
|
Anders Hasselqvist <anders.hasselqvist@gmail.com>
|
||||||
Bei Li <beil@google.com>
|
Bei Li <beil@google.com>
|
||||||
Chun-da Chen <capitalm.c@gmail.com>
|
Chun-da Chen <capitalm.c@gmail.com>
|
||||||
|
David Cavar <pal3thorn@gmail.com>
|
||||||
Gabe Kopley <gabe@philo.com>
|
Gabe Kopley <gabe@philo.com>
|
||||||
Haoming Chen <hmchen@google.com>
|
Haoming Chen <hmchen@google.com>
|
||||||
Jacob Trimble <modmaker@google.com>
|
Jacob Trimble <modmaker@google.com>
|
||||||
|
|
|
@ -27,6 +27,7 @@ namespace hls {
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
const char kUriBase64Prefix[] = "data:text/plain;base64,";
|
const char kUriBase64Prefix[] = "data:text/plain;base64,";
|
||||||
|
const char kUriFairplayPrefix[] = "skd://";
|
||||||
const char kWidevineDashIfIopUUID[] =
|
const char kWidevineDashIfIopUUID[] =
|
||||||
"urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed";
|
"urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed";
|
||||||
|
|
||||||
|
@ -41,6 +42,22 @@ bool IsCommonSystemId(const std::vector<uint8_t>& system_id) {
|
||||||
std::equal(system_id.begin(), system_id.end(), media::kCommonSystemId);
|
std::equal(system_id.begin(), system_id.end(), media::kCommonSystemId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool IsFairplaySystemId(const std::vector<uint8_t>& system_id) {
|
||||||
|
return system_id.size() == arraysize(media::kFairplaySystemId) &&
|
||||||
|
std::equal(system_id.begin(), system_id.end(), media::kFairplaySystemId);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string Base64EncodeData(const std::string& prefix,
|
||||||
|
const std::string& data) {
|
||||||
|
std::string data_base64;
|
||||||
|
base::Base64Encode(data, &data_base64);
|
||||||
|
return prefix + data_base64;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string VectorToString(const std::vector<uint8_t>& v) {
|
||||||
|
return std::string(v.begin(), v.end());
|
||||||
|
}
|
||||||
|
|
||||||
// TODO(rkuroiwa): Dedup these with the functions in MpdBuilder.
|
// TODO(rkuroiwa): Dedup these with the functions in MpdBuilder.
|
||||||
std::string MakePathRelative(const std::string& original_path,
|
std::string MakePathRelative(const std::string& original_path,
|
||||||
const std::string& output_dir) {
|
const std::string& output_dir) {
|
||||||
|
@ -157,11 +174,10 @@ void NotifyEncryptionToMediaPlaylist(
|
||||||
if (!key_id.empty()) {
|
if (!key_id.empty()) {
|
||||||
key_id_string = "0x" + base::HexEncode(key_id.data(), key_id.size());
|
key_id_string = "0x" + base::HexEncode(key_id.data(), key_id.size());
|
||||||
}
|
}
|
||||||
std::string key_uri_data_base64;
|
|
||||||
base::Base64Encode(uri, &key_uri_data_base64);
|
|
||||||
media_playlist->AddEncryptionInfo(
|
media_playlist->AddEncryptionInfo(
|
||||||
encryption_method,
|
encryption_method,
|
||||||
kUriBase64Prefix + key_uri_data_base64, key_id_string, iv_string,
|
uri, key_id_string, iv_string,
|
||||||
key_format, key_format_version);
|
key_format, key_format_version);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -179,8 +195,9 @@ bool HandleWidevineKeyFormats(
|
||||||
&key_uri_data)) {
|
&key_uri_data)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
// This format does not have a key id field.
|
std::string key_uri_data_base64 =
|
||||||
NotifyEncryptionToMediaPlaylist(encryption_method, key_uri_data,
|
Base64EncodeData(kUriBase64Prefix, key_uri_data);
|
||||||
|
NotifyEncryptionToMediaPlaylist(encryption_method, key_uri_data_base64,
|
||||||
std::vector<uint8_t>(), iv, "com.widevine",
|
std::vector<uint8_t>(), iv, "com.widevine",
|
||||||
"1", media_playlist);
|
"1", media_playlist);
|
||||||
}
|
}
|
||||||
|
@ -188,8 +205,11 @@ bool HandleWidevineKeyFormats(
|
||||||
std::string pssh_as_string(
|
std::string pssh_as_string(
|
||||||
reinterpret_cast<const char*>(protection_system_specific_data.data()),
|
reinterpret_cast<const char*>(protection_system_specific_data.data()),
|
||||||
protection_system_specific_data.size());
|
protection_system_specific_data.size());
|
||||||
NotifyEncryptionToMediaPlaylist(encryption_method, pssh_as_string, key_id, iv,
|
std::string key_uri_data_base64 =
|
||||||
kWidevineDashIfIopUUID, "1", media_playlist);
|
Base64EncodeData(kUriBase64Prefix, pssh_as_string);
|
||||||
|
NotifyEncryptionToMediaPlaylist(encryption_method, key_uri_data_base64,
|
||||||
|
key_id, iv, kWidevineDashIfIopUUID, "1",
|
||||||
|
media_playlist);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -356,13 +376,31 @@ bool SimpleHlsNotifier::NotifyEncryptionUpdate(
|
||||||
if (IsCommonSystemId(system_id)) {
|
if (IsCommonSystemId(system_id)) {
|
||||||
// Use key_id as the key_uri. The player needs to have custom logic to
|
// Use key_id as the key_uri. The player needs to have custom logic to
|
||||||
// convert it to the actual key url.
|
// convert it to the actual key url.
|
||||||
std::string key_uri_data;
|
std::string key_uri_data = VectorToString(key_id);
|
||||||
key_uri_data.assign(key_id.begin(), key_id.end());
|
std::string key_uri_data_base64 =
|
||||||
|
Base64EncodeData(kUriBase64Prefix, key_uri_data);
|
||||||
NotifyEncryptionToMediaPlaylist(encryption_method,
|
NotifyEncryptionToMediaPlaylist(encryption_method,
|
||||||
key_uri_data, std::vector<uint8_t>(), iv,
|
key_uri_data_base64, std::vector<uint8_t>(),
|
||||||
"identity", "", media_playlist.get());
|
iv, "identity", "", media_playlist.get());
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (IsFairplaySystemId(system_id)) {
|
||||||
|
// Use key_id as the key_uri. The player needs to have custom logic to
|
||||||
|
// convert it to the actual key url.
|
||||||
|
std::string key_uri_data = VectorToString(key_id);
|
||||||
|
std::string key_uri_data_base64 =
|
||||||
|
Base64EncodeData(kUriFairplayPrefix, key_uri_data);
|
||||||
|
|
||||||
|
// Fairplay defines IV to be carried with the key, not the playlist.
|
||||||
|
NotifyEncryptionToMediaPlaylist(encryption_method,
|
||||||
|
key_uri_data_base64, std::vector<uint8_t>(),
|
||||||
|
std::vector<uint8_t>(),
|
||||||
|
"com.apple.streamingkeydelivery", "1",
|
||||||
|
media_playlist.get());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
LOG(ERROR) << "Unknown system ID: "
|
LOG(ERROR) << "Unknown system ID: "
|
||||||
<< base::HexEncode(system_id.data(), system_id.size());
|
<< base::HexEncode(system_id.data(), system_id.size());
|
||||||
return false;
|
return false;
|
||||||
|
|
|
@ -31,6 +31,7 @@ using ::testing::_;
|
||||||
namespace {
|
namespace {
|
||||||
const char kMasterPlaylistName[] = "master.m3u8";
|
const char kMasterPlaylistName[] = "master.m3u8";
|
||||||
const HlsPlaylistType kVodPlaylist = HlsPlaylistType::kVod;
|
const HlsPlaylistType kVodPlaylist = HlsPlaylistType::kVod;
|
||||||
|
const HlsPlaylistType kLivePlaylist = HlsPlaylistType::kLive;
|
||||||
|
|
||||||
class MockMasterPlaylist : public MasterPlaylist {
|
class MockMasterPlaylist : public MasterPlaylist {
|
||||||
public:
|
public:
|
||||||
|
@ -94,7 +95,10 @@ class SimpleHlsNotifierTest : public ::testing::Test {
|
||||||
media::kWidevineSystemId + arraysize(media::kWidevineSystemId)),
|
media::kWidevineSystemId + arraysize(media::kWidevineSystemId)),
|
||||||
common_system_id_(
|
common_system_id_(
|
||||||
media::kCommonSystemId,
|
media::kCommonSystemId,
|
||||||
media::kCommonSystemId + arraysize(media::kCommonSystemId)) {}
|
media::kCommonSystemId + arraysize(media::kCommonSystemId)),
|
||||||
|
fairplay_system_id_(
|
||||||
|
media::kFairplaySystemId,
|
||||||
|
media::kFairplaySystemId + arraysize(media::kFairplaySystemId)) {}
|
||||||
|
|
||||||
void InjectMediaPlaylistFactory(std::unique_ptr<MediaPlaylistFactory> factory,
|
void InjectMediaPlaylistFactory(std::unique_ptr<MediaPlaylistFactory> factory,
|
||||||
SimpleHlsNotifier* notifier) {
|
SimpleHlsNotifier* notifier) {
|
||||||
|
@ -139,6 +143,7 @@ class SimpleHlsNotifierTest : public ::testing::Test {
|
||||||
|
|
||||||
const std::vector<uint8_t> widevine_system_id_;
|
const std::vector<uint8_t> widevine_system_id_;
|
||||||
const std::vector<uint8_t> common_system_id_;
|
const std::vector<uint8_t> common_system_id_;
|
||||||
|
const std::vector<uint8_t> fairplay_system_id_;
|
||||||
};
|
};
|
||||||
|
|
||||||
TEST_F(SimpleHlsNotifierTest, Init) {
|
TEST_F(SimpleHlsNotifierTest, Init) {
|
||||||
|
@ -645,6 +650,35 @@ TEST_F(SimpleHlsNotifierTest, EncryptionScheme) {
|
||||||
stream_id, key_id, common_system_id_, iv, dummy_pssh_data));
|
stream_id, key_id, common_system_id_, iv, dummy_pssh_data));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Verify that the Fairplay systemID is correctly handled when constructing
|
||||||
|
// encryption info.
|
||||||
|
TEST_F(SimpleHlsNotifierTest, NotifyEncryptionUpdateFairplay) {
|
||||||
|
// Pointer released by SimpleHlsNotifier.
|
||||||
|
MockMediaPlaylist* mock_media_playlist =
|
||||||
|
new MockMediaPlaylist(kLivePlaylist, "playlist.m3u8", "", "");
|
||||||
|
SimpleHlsNotifier notifier(kLivePlaylist, kTestTimeShiftBufferDepth,
|
||||||
|
kTestPrefix, kAnyOutputDir, kMasterPlaylistName);
|
||||||
|
const uint32_t stream_id =
|
||||||
|
SetupStream(kSampleAesProtectionScheme, mock_media_playlist, ¬ifier);
|
||||||
|
const std::vector<uint8_t> key_id(16, 0x12);
|
||||||
|
const std::vector<uint8_t> dummy_pssh_data(10, 'p');
|
||||||
|
|
||||||
|
std::string expected_key_uri_base64;
|
||||||
|
base::Base64Encode(std::string(key_id.begin(), key_id.end()),
|
||||||
|
&expected_key_uri_base64);
|
||||||
|
EXPECT_CALL(
|
||||||
|
*mock_media_playlist,
|
||||||
|
AddEncryptionInfo(
|
||||||
|
MediaPlaylist::EncryptionMethod::kSampleAes,
|
||||||
|
StrEq("skd://" + expected_key_uri_base64),
|
||||||
|
StrEq(""),
|
||||||
|
StrEq(""),
|
||||||
|
StrEq("com.apple.streamingkeydelivery"), StrEq("1")));
|
||||||
|
EXPECT_TRUE(notifier.NotifyEncryptionUpdate(
|
||||||
|
stream_id, key_id, fairplay_system_id_, std::vector<uint8_t>(),
|
||||||
|
dummy_pssh_data));
|
||||||
|
}
|
||||||
|
|
||||||
// If using 'cenc' with Widevine, don't output the json form.
|
// If using 'cenc' with Widevine, don't output the json form.
|
||||||
TEST_F(SimpleHlsNotifierTest, WidevineCencEncryptionScheme) {
|
TEST_F(SimpleHlsNotifierTest, WidevineCencEncryptionScheme) {
|
||||||
// Pointer released by SimpleHlsNotifier.
|
// Pointer released by SimpleHlsNotifier.
|
||||||
|
|
|
@ -23,6 +23,12 @@ const uint8_t kCommonSystemId[] = {0x10, 0x77, 0xef, 0xec, 0xc0, 0xb2,
|
||||||
0x4d, 0x02, 0xac, 0xe3, 0x3c, 0x1e,
|
0x4d, 0x02, 0xac, 0xe3, 0x3c, 0x1e,
|
||||||
0x52, 0xe2, 0xfb, 0x4b};
|
0x52, 0xe2, 0xfb, 0x4b};
|
||||||
|
|
||||||
|
// Unofficial fairplay system id extracted from
|
||||||
|
// https://forums.developer.apple.com/thread/6185.
|
||||||
|
const uint8_t kFairplaySystemId[] = {0x29, 0x70, 0x1F, 0xE4, 0x3C, 0xC7,
|
||||||
|
0x4A, 0x34, 0x8C, 0x5B, 0xAE, 0x90,
|
||||||
|
0xC7, 0x43, 0x9A, 0x47};
|
||||||
|
|
||||||
/// A key source that uses fixed keys for encryption.
|
/// A key source that uses fixed keys for encryption.
|
||||||
class FixedKeySource : public KeySource {
|
class FixedKeySource : public KeySource {
|
||||||
public:
|
public:
|
||||||
|
|
Loading…
Reference in New Issue