7#include <packager/media/base/cpix_key_source.h>
18#include <absl/log/check.h>
19#include <absl/log/log.h>
20#include <absl/strings/escaping.h>
21#include <absl/strings/match.h>
22#include <mbedtls/md.h>
24#include <packager/file.h>
25#include <packager/macros/compiler.h>
26#include <packager/macros/status.h>
27#include <packager/media/base/aes_decryptor.h>
28#include <packager/media/base/aes_encryptor.h>
29#include <packager/media/base/aes_key_wrap.h>
30#include <packager/media/base/cpix_parser.h>
31#include <packager/media/base/http_key_fetcher.h>
32#include <packager/media/base/protection_system_specific_info.h>
33#include <packager/media/base/rsa_key.h>
34#include <packager/utils/bytes_to_string_view.h>
37const char kEmptyDrmLabel[] =
"";
44const char kXmlContentType[] =
"application/xml";
46std::string KeyIdToString(
const std::vector<uint8_t>& key_id) {
50bool IsHttpUrl(
const std::string& source) {
51 return absl::StartsWith(source,
"http://") ||
52 absl::StartsWith(source,
"https://");
56class HttpCpixFetcher :
public CpixFetcher {
58 Status Fetch(
const std::string& url,
59 const std::string& request_body,
60 const std::vector<std::string>& headers,
61 std::string* response)
override {
62 HttpKeyFetcher fetcher;
63 fetcher.set_content_type(kXmlContentType);
64 fetcher.set_extra_headers(headers);
65 return request_body.empty() ? fetcher.Get(url, response)
66 : fetcher.Post(url, request_body, response);
70std::string PixelRangeToString(int64_t min_pixels, int64_t max_pixels) {
71 return "[" + std::to_string(min_pixels) +
", " +
72 (max_pixels == std::numeric_limits<int64_t>::max()
74 : std::to_string(max_pixels)) +
81Status VideoFilterToLabels(
const CpixVideoFilter& video_filter,
82 const CpixEncryptionParams& cpix_params,
83 const std::string& key_id_string,
84 std::set<std::string>* labels) {
85 const int64_t kUnbounded = std::numeric_limits<int64_t>::max();
91 {
"SD", 0, cpix_params.max_sd_pixels},
92 {
"HD", int64_t{cpix_params.max_sd_pixels} + 1, cpix_params.max_hd_pixels},
93 {
"UHD1", int64_t{cpix_params.max_hd_pixels} + 1,
94 cpix_params.max_uhd1_pixels},
95 {
"UHD2", int64_t{cpix_params.max_uhd1_pixels} + 1, kUnbounded},
99 for (
const auto& bucket : kBuckets) {
100 const bool overlaps = video_filter.min_pixels <= bucket.max_pixels &&
101 video_filter.max_pixels >= bucket.min_pixels;
104 const bool covers = video_filter.min_pixels <= bucket.min_pixels &&
105 video_filter.max_pixels >= bucket.max_pixels;
107 std::string bucket_ranges;
108 for (
const auto& other_bucket : kBuckets) {
109 bucket_ranges += std::string(
" ") + other_bucket.label +
" " +
110 PixelRangeToString(other_bucket.min_pixels,
111 other_bucket.max_pixels);
114 error::INVALID_ARGUMENT,
115 "The VideoFilter pixel range " +
116 PixelRangeToString(video_filter.min_pixels,
117 video_filter.max_pixels) +
118 " of the usage rule for key " + key_id_string +
119 " only partially covers the " + bucket.label +
" label bucket " +
120 PixelRangeToString(bucket.min_pixels, bucket.max_pixels) +
121 ". Filter boundaries must align with the label buckets:" +
123 ". Adjust --max_sd_pixels, --max_hd_pixels and "
124 "--max_uhd1_pixels to match the ranges in the CPIX document.");
126 labels->insert(bucket.label);
130 return Status(error::INVALID_ARGUMENT,
131 "The VideoFilter of the usage rule for key " + key_id_string +
132 " matches no pixel range.");
137Status ApplyIntendedTrackType(
const CpixUsageRule& usage_rule,
138 const std::string& key_id_string,
139 std::set<std::string>* labels) {
140 if (usage_rule.intended_track_type.empty())
143 if (labels->find(usage_rule.intended_track_type) == labels->end()) {
144 return Status(error::INVALID_ARGUMENT,
145 "ContentKeyUsageRule for key " + key_id_string +
146 " has intendedTrackType '" +
147 usage_rule.intended_track_type +
148 "' that does not match its filters.");
151 labels->insert(usage_rule.intended_track_type);
158Status RuleToLabels(
const CpixUsageRule& usage_rule,
159 const CpixEncryptionParams& cpix_params,
160 std::set<std::string>* labels) {
161 if (!usage_rule.has_audio_filter && usage_rule.video_filters.empty()) {
162 labels->insert(usage_rule.intended_track_type.empty()
164 : usage_rule.intended_track_type);
168 const std::string key_id_string = KeyIdToString(usage_rule.key_id);
169 std::set<std::string> rule_labels;
170 if (usage_rule.has_audio_filter) {
171 rule_labels.insert(
"AUDIO");
173 for (
const CpixVideoFilter& video_filter : usage_rule.video_filters) {
174 RETURN_IF_ERROR(VideoFilterToLabels(video_filter, cpix_params,
175 key_id_string, &rule_labels));
179 ApplyIntendedTrackType(usage_rule, key_id_string, &rule_labels));
180 labels->insert(rule_labels.begin(), rule_labels.end());
184const char kAlgorithmRsaOaep[] =
185 "http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p";
186const char kAlgorithmAes256Cbc[] =
187 "http://www.w3.org/2001/04/xmlenc#aes256-cbc";
188const char kAlgorithmKwAes256[] =
"http://www.w3.org/2001/04/xmlenc#kw-aes256";
189const char kAlgorithmHmacSha512[] =
190 "http://www.w3.org/2001/04/xmldsig-more#hmac-sha512";
192Status RsaOaepDecrypt(RsaPrivateKey* private_key,
193 const CpixEncryptedValue& encrypted_value,
194 const std::string& error_context,
195 std::vector<uint8_t>* plaintext) {
196 if (!encrypted_value.algorithm.empty() &&
197 encrypted_value.algorithm != kAlgorithmRsaOaep) {
198 return Status(error::UNIMPLEMENTED,
199 "Unsupported encryption algorithm '" +
200 encrypted_value.algorithm +
"' for " + error_context +
201 ". Only " + kAlgorithmRsaOaep +
" is supported.");
203 const std::string ciphertext(encrypted_value.cipher_value.begin(),
204 encrypted_value.cipher_value.end());
205 std::string decrypted;
206 if (!private_key->Decrypt(ciphertext, &decrypted)) {
207 return Status(error::INVALID_ARGUMENT,
208 "Failed to decrypt " + error_context +
209 ". The CPIX document may be intended for a different "
212 plaintext->assign(decrypted.begin(), decrypted.end());
216std::vector<uint8_t> HmacSha512(
const std::vector<uint8_t>& key,
217 const std::vector<uint8_t>& data) {
218 const mbedtls_md_info_t* md_info =
219 mbedtls_md_info_from_type(MBEDTLS_MD_SHA512);
221 std::vector<uint8_t> mac(mbedtls_md_get_size(md_info));
222 CHECK_EQ(0, mbedtls_md_hmac(md_info, key.data(), key.size(), data.data(),
223 data.size(), mac.data()));
227Status DecryptContentKeyValue(
const std::vector<uint8_t>& document_key,
228 const CpixEncryptedValue& encrypted_value,
229 const std::string& key_id_string,
230 std::vector<uint8_t>* key) {
231 if (encrypted_value.algorithm == kAlgorithmAes256Cbc) {
232 if (encrypted_value.cipher_value.size() < 32 ||
233 encrypted_value.cipher_value.size() % 16 != 0) {
234 return Status(error::INVALID_ARGUMENT,
235 "Invalid AES-CBC ciphertext size " +
236 std::to_string(encrypted_value.cipher_value.size()) +
237 " for key " + key_id_string +
".");
239 const std::vector<uint8_t> iv(encrypted_value.cipher_value.begin(),
240 encrypted_value.cipher_value.begin() + 16);
241 const std::vector<uint8_t> ciphertext(
242 encrypted_value.cipher_value.begin() + 16,
243 encrypted_value.cipher_value.end());
244 AesCbcDecryptor decryptor(kNoPadding);
245 if (!decryptor.InitializeWithIv(document_key, iv)) {
246 return Status(error::INTERNAL_ERROR,
247 "Failed to initialize the AES-CBC decryptor.");
249 std::vector<uint8_t> padded;
250 if (!decryptor.Crypt(ciphertext, &padded)) {
252 error::INVALID_ARGUMENT,
253 "Failed to decrypt the value of key " + key_id_string +
".");
257 const uint8_t padding_size = padded.back();
258 if (padding_size < 1 || padding_size > 16 ||
259 padding_size >= padded.size()) {
260 return Status(error::INVALID_ARGUMENT,
261 "Invalid padding in the encrypted value of key " +
262 key_id_string +
". The document key may be incorrect.");
264 key->assign(padded.begin(), padded.end() - padding_size);
267 if (encrypted_value.algorithm == kAlgorithmKwAes256) {
268 if (!AesKeyUnwrap(document_key, encrypted_value.cipher_value, key)) {
269 return Status(error::INVALID_ARGUMENT,
270 "Failed to unwrap the value of key " + key_id_string +
271 ". The document may be corrupted.");
275 return Status(error::UNIMPLEMENTED,
276 "Unsupported encryption algorithm '" +
277 encrypted_value.algorithm +
"' for key " + key_id_string +
278 ". Only " + kAlgorithmAes256Cbc +
" and " +
279 kAlgorithmKwAes256 +
" are supported.");
285Status DecryptDocument(
const CpixEncryptionParams& cpix_params,
286 CpixDocument* document) {
287 const bool any_encrypted =
288 std::any_of(document->content_keys.begin(), document->content_keys.end(),
289 [](
const CpixContentKey& content_key) {
290 return content_key.encrypted_key.has_value();
292 if (!any_encrypted) {
293 if (!cpix_params.private_key_source.empty()) {
294 LOG(WARNING) <<
"--cpix_private_key is set, but the CPIX document is "
299 if (cpix_params.private_key_source.empty()) {
300 return Status(error::INVALID_ARGUMENT,
301 "The CPIX document contains encrypted content keys. "
302 "Provide the recipient private key to decrypt them.");
304 if (document->delivery_data.empty()) {
305 return Status(error::INVALID_ARGUMENT,
306 "The CPIX document contains encrypted content keys but no "
307 "DeliveryData with a document key.");
310 std::string private_key_data;
311 if (!File::ReadFileToString(cpix_params.private_key_source.c_str(),
312 &private_key_data)) {
313 return Status(error::FILE_FAILURE,
314 "Failed to read the CPIX private key from '" +
315 cpix_params.private_key_source +
"'.");
318 if (private_key_data.find(
"-----BEGIN") != std::string::npos)
319 private_key_data.push_back(
'\0');
320 std::unique_ptr<RsaPrivateKey> private_key(
323 return Status(error::INVALID_ARGUMENT,
324 "Failed to load the RSA private key from '" +
325 cpix_params.private_key_source +
"'.");
330 std::vector<uint8_t> document_key;
331 std::vector<uint8_t> mac_key;
332 std::string mac_algorithm;
333 Status last_error = Status::OK;
334 bool document_key_decrypted =
false;
335 for (
const CpixDeliveryData& delivery_data : document->delivery_data) {
336 std::vector<uint8_t> candidate_key;
338 RsaOaepDecrypt(private_key.get(), delivery_data.document_key,
339 "the document key", &candidate_key);
344 document_key = std::move(candidate_key);
345 mac_algorithm = delivery_data.mac_algorithm;
346 if (!mac_algorithm.empty()) {
347 RETURN_IF_ERROR(RsaOaepDecrypt(private_key.get(), delivery_data.mac_key,
348 "the MAC key", &mac_key));
350 document_key_decrypted =
true;
353 if (!document_key_decrypted)
356 if (document_key.size() != 32) {
357 return Status(error::INVALID_ARGUMENT,
358 "Invalid CPIX document key size " +
359 std::to_string(document_key.size()) +
360 ", must be 32 bytes.");
362 if (!mac_algorithm.empty() && mac_algorithm != kAlgorithmHmacSha512) {
363 return Status(error::UNIMPLEMENTED,
364 "Unsupported MAC algorithm '" + mac_algorithm +
"'. Only " +
365 kAlgorithmHmacSha512 +
" is supported.");
367 if (mac_algorithm.empty()) {
370 LOG(WARNING) <<
"The CPIX document declares no MACMethod; encrypted "
371 "content keys are decrypted without integrity "
375 for (CpixContentKey& content_key : document->content_keys) {
376 if (!content_key.encrypted_key)
378 const CpixEncryptedValue& encrypted_value = *content_key.encrypted_key;
379 const std::string key_id_string = KeyIdToString(content_key.key_id);
380 if (!mac_algorithm.empty()) {
381 if (encrypted_value.value_mac.empty()) {
382 return Status(error::INVALID_ARGUMENT,
383 "The document declares a MACMethod, but key " +
384 key_id_string +
" has no ValueMAC.");
386 if (HmacSha512(mac_key, encrypted_value.cipher_value) !=
387 encrypted_value.value_mac) {
388 return Status(error::INVALID_ARGUMENT,
389 "ValueMAC verification failed for key " + key_id_string +
390 ". The document may be corrupted or tampered "
394 RETURN_IF_ERROR(DecryptContentKeyValue(document_key, encrypted_value,
395 key_id_string, &content_key.key));
400Status ValidateContentKey(
const CpixContentKey& content_key) {
401 if (content_key.key_id.size() != 16) {
402 return Status(error::INVALID_ARGUMENT,
403 "Invalid key ID size '" +
404 std::to_string(content_key.key_id.size()) +
405 "', must be 16 bytes.");
407 if (content_key.key.size() != 16) {
409 return Status(error::INVALID_ARGUMENT,
410 "Invalid key size '" +
411 std::to_string(content_key.key.size()) +
"' for key " +
412 KeyIdToString(content_key.key_id) +
413 ", must be 16 bytes.");
415 if (!content_key.iv.empty() && content_key.iv.size() != 8 &&
416 content_key.iv.size() != 16) {
417 return Status(error::INVALID_ARGUMENT,
418 "Invalid explicitIV size '" +
419 std::to_string(content_key.iv.size()) +
"' for key " +
420 KeyIdToString(content_key.key_id) +
421 ", must be 8 or 16 bytes.");
433Status GetKeySystemInfo(
434 const CpixDocument& document,
435 const CpixContentKey& content_key,
436 std::vector<ProtectionSystemSpecificInfo>* key_system_info) {
437 for (
const CpixDrmSystem& drm_system : document.drm_systems) {
438 if (drm_system.key_id != content_key.key_id)
441 ProtectionSystemSpecificInfo info;
442 info.system_id = drm_system.system_id;
443 if (!drm_system.pssh.empty()) {
444 const std::string error_context =
"The PSSH element of DRMSystem " +
445 KeyIdToString(drm_system.system_id) +
447 KeyIdToString(content_key.key_id);
448 std::vector<ProtectionSystemSpecificInfo> parsed_boxes;
450 drm_system.pssh.data(), drm_system.pssh.size(), &parsed_boxes)) {
451 return Status(error::INVALID_ARGUMENT,
452 error_context +
" does not contain full PSSH boxes.");
454 for (
const ProtectionSystemSpecificInfo& parsed : parsed_boxes) {
455 if (parsed.system_id != drm_system.system_id) {
456 return Status(error::INVALID_ARGUMENT,
458 " contains a PSSH box with mismatching system ID " +
459 KeyIdToString(parsed.system_id) +
".");
462 info.psshs = drm_system.pssh;
464 key_system_info->push_back(std::move(info));
469Status BuildEncryptionKeyMap(
const CpixEncryptionParams& cpix_params,
470 CpixFetcher* fetcher,
472 EncryptionKeyMap* encryption_key_map) {
473 if (cpix_params.document_source.empty()) {
474 return Status(error::INVALID_ARGUMENT,
475 "CPIX document source should not be empty.");
479 if (IsHttpUrl(cpix_params.document_source)) {
480 std::string request_body;
481 if (!cpix_params.request_document_source.empty() &&
482 !File::ReadFileToString(cpix_params.request_document_source.c_str(),
484 return Status(error::FILE_FAILURE,
485 "Failed to read CPIX request document from '" +
486 cpix_params.request_document_source +
"'.");
488 RETURN_IF_ERROR(fetcher->Fetch(cpix_params.document_source, request_body,
489 cpix_params.headers, &xml));
491 if (!cpix_params.request_document_source.empty()) {
492 return Status(error::INVALID_ARGUMENT,
493 "A CPIX request document requires the CPIX document "
494 "source to be an HTTP(S) URL to POST it to.");
496 if (!File::ReadFileToString(cpix_params.document_source.c_str(), &xml)) {
497 return Status(error::FILE_FAILURE,
"Failed to read CPIX document from '" +
498 cpix_params.document_source +
503 CpixDocument document;
504 RETURN_IF_ERROR(ParseCpixDocument(xml, &document));
505 RETURN_IF_ERROR(DecryptDocument(cpix_params, &document));
509 if (!for_decryption) {
510 for (
const CpixDrmSystem& drm_system : document.drm_systems) {
511 const bool known_key = std::any_of(
512 document.content_keys.begin(), document.content_keys.end(),
513 [&drm_system](
const CpixContentKey& content_key) {
514 return content_key.key_id == drm_system.key_id;
517 return Status(error::INVALID_ARGUMENT,
518 "DRMSystem " + KeyIdToString(drm_system.system_id) +
519 " references unknown key " +
520 KeyIdToString(drm_system.key_id) +
".");
528 const CpixContentKey* content_key;
529 std::set<std::string> labels;
531 std::vector<UsedKey> used_keys;
532 for (
const CpixContentKey& content_key : document.content_keys) {
533 std::set<std::string> labels;
534 if (for_decryption) {
537 labels.insert(KeyIdToString(content_key.key_id));
539 for (
const CpixUsageRule& usage_rule : document.usage_rules) {
540 if (usage_rule.key_id == content_key.key_id)
541 RETURN_IF_ERROR(RuleToLabels(usage_rule, cpix_params, &labels));
543 if (labels.empty()) {
544 if (document.usage_rules.empty() && document.content_keys.size() == 1) {
546 labels.insert(kEmptyDrmLabel);
550 LOG(WARNING) <<
"Ignoring key " << KeyIdToString(content_key.key_id)
551 <<
": it is not referenced by any "
552 "ContentKeyUsageRule.";
558 Status valid = ValidateContentKey(content_key);
560 if (for_decryption) {
563 LOG(WARNING) <<
"Ignoring key " << KeyIdToString(content_key.key_id)
564 <<
": " << valid.error_message();
569 used_keys.push_back({&content_key, std::move(labels)});
572 if (used_keys.empty()) {
573 return Status(error::INVALID_ARGUMENT,
574 "No usable content key in the CPIX document could be "
575 "mapped to streams.");
578 std::vector<std::vector<uint8_t>> key_ids;
579 for (
const UsedKey& used_key : used_keys)
580 key_ids.emplace_back(used_key.content_key->key_id);
582 for (
const UsedKey& used_key : used_keys) {
583 const CpixContentKey& content_key = *used_key.content_key;
584 EncryptionKey encryption_key;
585 encryption_key.key_id = content_key.key_id;
586 encryption_key.key = content_key.key;
587 encryption_key.iv = content_key.iv;
588 encryption_key.common_encryption_scheme =
589 content_key.common_encryption_scheme;
590 if (!for_decryption) {
593 encryption_key.key_ids = key_ids;
594 RETURN_IF_ERROR(GetKeySystemInfo(document, content_key,
595 &encryption_key.key_system_info));
598 for (
const std::string& label : used_key.labels) {
599 if (encryption_key_map->find(label) != encryption_key_map->end()) {
601 error::INVALID_ARGUMENT,
602 "Multiple keys map to the same stream label '" + label +
"'.");
604 (*encryption_key_map)[label] =
605 std::make_unique<EncryptionKey>(encryption_key);
613CpixKeySource::~CpixKeySource() {}
616 const std::vector<uint8_t>& init_data) {
617 UNUSED(init_data_type);
628 auto iter = encryption_key_map_.find(stream_label);
629 if (iter == encryption_key_map_.end()) {
630 iter = encryption_key_map_.find(kEmptyDrmLabel);
631 if (iter == encryption_key_map_.end()) {
632 return Status(error::NOT_FOUND,
"Key for '" + stream_label +
633 "' was not found in the CPIX "
637 *key = *iter->second;
644 for (
const auto& pair : encryption_key_map_) {
645 if (pair.second->key_id == key_id) {
650 return Status(error::NOT_FOUND,
653 " was not found in the CPIX document.");
657 uint32_t crypto_period_index,
658 int32_t crypto_period_duration_in_seconds,
659 const std::string& stream_label,
661 UNUSED(crypto_period_index);
662 UNUSED(crypto_period_duration_in_seconds);
663 UNUSED(stream_label);
665 return Status(error::UNIMPLEMENTED,
666 "The CPIX key source does not support key rotation.");
670 const CpixEncryptionParams& cpix_params) {
671 HttpCpixFetcher fetcher;
676 const CpixEncryptionParams& cpix_params,
678 return CreateInternal(cpix_params, fetcher,
false);
682 const CpixEncryptionParams& cpix_params) {
683 HttpCpixFetcher fetcher;
684 return CreateInternal(cpix_params, &fetcher,
true);
687std::unique_ptr<CpixKeySource> CpixKeySource::CreateInternal(
688 const CpixEncryptionParams& cpix_params,
690 bool for_decryption) {
692 EncryptionKeyMap encryption_key_map;
693 Status status = BuildEncryptionKeyMap(cpix_params, fetcher, for_decryption,
694 &encryption_key_map);
696 LOG(ERROR) <<
"Failed to create CPIX key source: " << status.ToString();
699 return std::unique_ptr<CpixKeySource>(
700 new CpixKeySource(std::move(encryption_key_map)));
703CpixKeySource::CpixKeySource(EncryptionKeyMap&& encryption_key_map)
704 : encryption_key_map_(std::move(encryption_key_map)) {}
All the methods that are virtual are virtual for mocking.
std::string_view byte_vector_to_string_view(const std::vector< uint8_t > &bytes)
Convert byte vector to string_view.