Shaka Packager SDK
Loading...
Searching...
No Matches
cpix_key_source.cc
1// Copyright 2026 Google LLC. All rights reserved.
2//
3// Use of this source code is governed by a BSD-style
4// license that can be found in the LICENSE file or at
5// https://developers.google.com/open-source/licenses/bsd
6
7#include <packager/media/base/cpix_key_source.h>
8
9#include <algorithm>
10#include <cstdint>
11#include <limits>
12#include <memory>
13#include <set>
14#include <string>
15#include <utility>
16#include <vector>
17
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>
23
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>
35
36namespace {
37const char kEmptyDrmLabel[] = "";
38} // namespace
39
40namespace shaka {
41namespace media {
42namespace {
43
44const char kXmlContentType[] = "application/xml";
45
46std::string KeyIdToString(const std::vector<uint8_t>& key_id) {
47 return absl::BytesToHexString(byte_vector_to_string_view(key_id));
48}
49
50bool IsHttpUrl(const std::string& source) {
51 return absl::StartsWith(source, "http://") ||
52 absl::StartsWith(source, "https://");
53}
54
55// Fetches CPIX documents over HTTP(S) via HttpKeyFetcher.
56class HttpCpixFetcher : public CpixFetcher {
57 public:
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);
67 }
68};
69
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()
73 ? "unbounded"
74 : std::to_string(max_pixels)) +
75 "]";
76}
77
78// Maps a video filter's pixel range to the video stream labels (SD, HD,
79// UHD1, UHD2) it covers. The filter must fully cover every label bucket it
80// touches; otherwise the mapping would be ambiguous.
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();
86 const struct {
87 const char* label;
88 int64_t min_pixels;
89 int64_t max_pixels;
90 } kBuckets[] = {
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},
96 };
97
98 bool matched = false;
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;
102 if (!overlaps)
103 continue;
104 const bool covers = video_filter.min_pixels <= bucket.min_pixels &&
105 video_filter.max_pixels >= bucket.max_pixels;
106 if (!covers) {
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);
112 }
113 return Status(
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:" +
122 bucket_ranges +
123 ". Adjust --max_sd_pixels, --max_hd_pixels and "
124 "--max_uhd1_pixels to match the ranges in the CPIX document.");
125 }
126 labels->insert(bucket.label);
127 matched = true;
128 }
129 if (!matched) {
130 return Status(error::INVALID_ARGUMENT,
131 "The VideoFilter of the usage rule for key " + key_id_string +
132 " matches no pixel range.");
133 }
134 return Status::OK;
135}
136
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())
141 return Status::OK;
142
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.");
149 }
150 labels->clear();
151 labels->insert(usage_rule.intended_track_type);
152 return Status::OK;
153}
154
155// Resolves the stream labels a usage rule applies to. Filters narrow the rule;
156// if 'intendedTrackType' is also present, it must match the filtered labels.
157// A rule with neither applies to all streams (the default label).
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()
163 ? kEmptyDrmLabel
164 : usage_rule.intended_track_type);
165 return Status::OK;
166 }
167
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");
172 } else {
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));
176 }
177 }
178 RETURN_IF_ERROR(
179 ApplyIntendedTrackType(usage_rule, key_id_string, &rule_labels));
180 labels->insert(rule_labels.begin(), rule_labels.end());
181 return Status::OK;
182}
183
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";
191
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.");
202 }
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 "
210 "recipient.");
211 }
212 plaintext->assign(decrypted.begin(), decrypted.end());
213 return Status::OK;
214}
215
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);
220 DCHECK(md_info);
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()));
224 return mac;
225}
226
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 + ".");
238 }
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.");
248 }
249 std::vector<uint8_t> padded;
250 if (!decryptor.Crypt(ciphertext, &padded)) {
251 return Status(
252 error::INVALID_ARGUMENT,
253 "Failed to decrypt the value of key " + key_id_string + ".");
254 }
255 // Strip XML Encryption block padding: the last byte holds the padding
256 // length; the padding byte values themselves are arbitrary.
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.");
263 }
264 key->assign(padded.begin(), padded.end() - padding_size);
265 return Status::OK;
266 }
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.");
272 }
273 return Status::OK;
274 }
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.");
280}
281
282// Decrypts encrypted content key values in |document| in place, after
283// unwrapping the document key (and MAC key) from the DeliveryData matching
284// the recipient private key and verifying each value's MAC.
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();
291 });
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 "
295 "not encrypted.";
296 }
297 return Status::OK;
298 }
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.");
303 }
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.");
308 }
309
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 + "'.");
316 }
317 // mbedtls requires PEM input to include the terminating NUL.
318 if (private_key_data.find("-----BEGIN") != std::string::npos)
319 private_key_data.push_back('\0');
320 std::unique_ptr<RsaPrivateKey> private_key(
321 RsaPrivateKey::Create(private_key_data));
322 if (!private_key) {
323 return Status(error::INVALID_ARGUMENT,
324 "Failed to load the RSA private key from '" +
325 cpix_params.private_key_source + "'.");
326 }
327
328 // Multiple DeliveryData elements address multiple recipients; find the one
329 // our private key can decrypt.
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;
337 Status status =
338 RsaOaepDecrypt(private_key.get(), delivery_data.document_key,
339 "the document key", &candidate_key);
340 if (!status.ok()) {
341 last_error = status;
342 continue;
343 }
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));
349 }
350 document_key_decrypted = true;
351 break;
352 }
353 if (!document_key_decrypted)
354 return last_error;
355
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.");
361 }
362 if (!mac_algorithm.empty() && mac_algorithm != kAlgorithmHmacSha512) {
363 return Status(error::UNIMPLEMENTED,
364 "Unsupported MAC algorithm '" + mac_algorithm + "'. Only " +
365 kAlgorithmHmacSha512 + " is supported.");
366 }
367 if (mac_algorithm.empty()) {
368 // MACMethod is optional in CPIX, but without it the encrypted content
369 // keys are decrypted with no integrity verification.
370 LOG(WARNING) << "The CPIX document declares no MACMethod; encrypted "
371 "content keys are decrypted without integrity "
372 "verification.";
373 }
374
375 for (CpixContentKey& content_key : document->content_keys) {
376 if (!content_key.encrypted_key)
377 continue;
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.");
385 }
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 "
391 "with.");
392 }
393 }
394 RETURN_IF_ERROR(DecryptContentKeyValue(document_key, encrypted_value,
395 key_id_string, &content_key.key));
396 }
397 return Status::OK;
398}
399
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.");
406 }
407 if (content_key.key.size() != 16) {
408 // CENC only supports AES-128, i.e. 16 bytes.
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.");
414 }
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.");
422 }
423 // The commonEncryptionScheme binding is not validated here: the protection
424 // scheme may be overridden per stream (e.g. Apple Sample AES for TS
425 // output), so it is enforced by the encryption handler where the stream's
426 // actual scheme is known.
427 return Status::OK;
428}
429
430// Collects the DRM signaling for |content_key| from the document's
431// DRMSystemList. The PSSH element must contain full PSSH box(es) whose
432// system ID matches the DRMSystem's systemId attribute.
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)
439 continue;
440
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) +
446 " for key " +
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.");
453 }
454 for (const ProtectionSystemSpecificInfo& parsed : parsed_boxes) {
455 if (parsed.system_id != drm_system.system_id) {
456 return Status(error::INVALID_ARGUMENT,
457 error_context +
458 " contains a PSSH box with mismatching system ID " +
459 KeyIdToString(parsed.system_id) + ".");
460 }
461 }
462 info.psshs = drm_system.pssh;
463 }
464 key_system_info->push_back(std::move(info));
465 }
466 return Status::OK;
467}
468
469Status BuildEncryptionKeyMap(const CpixEncryptionParams& cpix_params,
470 CpixFetcher* fetcher,
471 bool for_decryption,
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.");
476 }
477
478 std::string xml;
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(),
483 &request_body)) {
484 return Status(error::FILE_FAILURE,
485 "Failed to read CPIX request document from '" +
486 cpix_params.request_document_source + "'.");
487 }
488 RETURN_IF_ERROR(fetcher->Fetch(cpix_params.document_source, request_body,
489 cpix_params.headers, &xml));
490 } else {
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.");
495 }
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 +
499 "'.");
500 }
501 }
502
503 CpixDocument document;
504 RETURN_IF_ERROR(ParseCpixDocument(xml, &document));
505 RETURN_IF_ERROR(DecryptDocument(cpix_params, &document));
506
507 // DRM signaling is only used for encryption; decryption needs the raw key
508 // values only, so an imperfect DRMSystemList should not fail decryption.
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;
515 });
516 if (!known_key) {
517 return Status(error::INVALID_ARGUMENT,
518 "DRMSystem " + KeyIdToString(drm_system.system_id) +
519 " references unknown key " +
520 KeyIdToString(drm_system.key_id) + ".");
521 }
522 }
523 }
524
525 // Determine the stream labels of each key first, so that keys that are not
526 // used by this packaging run are skipped rather than validated.
527 struct UsedKey {
528 const CpixContentKey* content_key;
529 std::set<std::string> labels;
530 };
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) {
535 // Decryption looks up keys by key ID only; a synthetic unique label
536 // keeps every key in the map without requiring usage rules.
537 labels.insert(KeyIdToString(content_key.key_id));
538 } else {
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));
542 }
543 if (labels.empty()) {
544 if (document.usage_rules.empty() && document.content_keys.size() == 1) {
545 // A single key without usage rules applies to all streams.
546 labels.insert(kEmptyDrmLabel);
547 } else {
548 // Usage rules are optional per key, e.g. for keys intended for
549 // other workflows or track types not packaged in this run.
550 LOG(WARNING) << "Ignoring key " << KeyIdToString(content_key.key_id)
551 << ": it is not referenced by any "
552 "ContentKeyUsageRule.";
553 continue;
554 }
555 }
556 }
557
558 Status valid = ValidateContentKey(content_key);
559 if (!valid.ok()) {
560 if (for_decryption) {
561 // The key may not be needed to decrypt the input streams; if it is,
562 // the key lookup will fail with a clear error later.
563 LOG(WARNING) << "Ignoring key " << KeyIdToString(content_key.key_id)
564 << ": " << valid.error_message();
565 continue;
566 }
567 return valid;
568 }
569 used_keys.push_back({&content_key, std::move(labels)});
570 }
571
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.");
576 }
577
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);
581
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) {
591 // key_ids and the DRM signaling are only used to build PSSH info for
592 // encryption; decryption looks up keys by key ID alone.
593 encryption_key.key_ids = key_ids;
594 RETURN_IF_ERROR(GetKeySystemInfo(document, content_key,
595 &encryption_key.key_system_info));
596 }
597
598 for (const std::string& label : used_key.labels) {
599 if (encryption_key_map->find(label) != encryption_key_map->end()) {
600 return Status(
601 error::INVALID_ARGUMENT,
602 "Multiple keys map to the same stream label '" + label + "'.");
603 }
604 (*encryption_key_map)[label] =
605 std::make_unique<EncryptionKey>(encryption_key);
606 }
607 }
608 return Status::OK;
609}
610
611} // namespace
612
613CpixKeySource::~CpixKeySource() {}
614
615Status CpixKeySource::FetchKeys(EmeInitDataType init_data_type,
616 const std::vector<uint8_t>& init_data) {
617 UNUSED(init_data_type);
618 UNUSED(init_data);
619 // All keys are fetched upfront when the key source is created.
620 return Status::OK;
621}
622
623Status CpixKeySource::GetKey(const std::string& stream_label,
624 EncryptionKey* key) {
625 DCHECK(key);
626 // Try to find the key with label |stream_label|. If it is not available,
627 // fall back to the default empty label if it is available.
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 "
634 "document.");
635 }
636 }
637 *key = *iter->second;
638 return Status::OK;
639}
640
641Status CpixKeySource::GetKey(const std::vector<uint8_t>& key_id,
642 EncryptionKey* key) {
643 DCHECK(key);
644 for (const auto& pair : encryption_key_map_) {
645 if (pair.second->key_id == key_id) {
646 *key = *pair.second;
647 return Status::OK;
648 }
649 }
650 return Status(error::NOT_FOUND,
651 "Key for key_id=" +
652 absl::BytesToHexString(byte_vector_to_string_view(key_id)) +
653 " was not found in the CPIX document.");
654}
655
657 uint32_t crypto_period_index,
658 int32_t crypto_period_duration_in_seconds,
659 const std::string& stream_label,
660 EncryptionKey* key) {
661 UNUSED(crypto_period_index);
662 UNUSED(crypto_period_duration_in_seconds);
663 UNUSED(stream_label);
664 UNUSED(key);
665 return Status(error::UNIMPLEMENTED,
666 "The CPIX key source does not support key rotation.");
667}
668
669std::unique_ptr<CpixKeySource> CpixKeySource::Create(
670 const CpixEncryptionParams& cpix_params) {
671 HttpCpixFetcher fetcher;
672 return CreateWithFetcher(cpix_params, &fetcher);
673}
674
675std::unique_ptr<CpixKeySource> CpixKeySource::CreateWithFetcher(
676 const CpixEncryptionParams& cpix_params,
677 CpixFetcher* fetcher) {
678 return CreateInternal(cpix_params, fetcher, /* for_decryption= */ false);
679}
680
681std::unique_ptr<CpixKeySource> CpixKeySource::CreateForDecryption(
682 const CpixEncryptionParams& cpix_params) {
683 HttpCpixFetcher fetcher;
684 return CreateInternal(cpix_params, &fetcher, /* for_decryption= */ true);
685}
686
687std::unique_ptr<CpixKeySource> CpixKeySource::CreateInternal(
688 const CpixEncryptionParams& cpix_params,
689 CpixFetcher* fetcher,
690 bool for_decryption) {
691 DCHECK(fetcher);
692 EncryptionKeyMap encryption_key_map;
693 Status status = BuildEncryptionKeyMap(cpix_params, fetcher, for_decryption,
694 &encryption_key_map);
695 if (!status.ok()) {
696 LOG(ERROR) << "Failed to create CPIX key source: " << status.ToString();
697 return nullptr;
698 }
699 return std::unique_ptr<CpixKeySource>(
700 new CpixKeySource(std::move(encryption_key_map)));
701}
702
703CpixKeySource::CpixKeySource(EncryptionKeyMap&& encryption_key_map)
704 : encryption_key_map_(std::move(encryption_key_map)) {}
705
706} // namespace media
707} // namespace shaka
Fetches CPIX documents over HTTP(S). Replaceable for testing.
Status FetchKeys(EmeInitDataType init_data_type, const std::vector< uint8_t > &init_data) override
static std::unique_ptr< CpixKeySource > CreateForDecryption(const CpixEncryptionParams &cpix_params)
static std::unique_ptr< CpixKeySource > CreateWithFetcher(const CpixEncryptionParams &cpix_params, CpixFetcher *fetcher)
static std::unique_ptr< CpixKeySource > Create(const CpixEncryptionParams &cpix_params)
Status GetCryptoPeriodKey(uint32_t crypto_period_index, int32_t crypto_period_duration_in_seconds, const std::string &stream_label, EncryptionKey *key) override
Status GetKey(const std::string &stream_label, EncryptionKey *key) override
static RsaPrivateKey * Create(const std::string &serialized_key)
Definition rsa_key.cc:84
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.
static bool ParseBoxes(const uint8_t *data, size_t data_size, std::vector< ProtectionSystemSpecificInfo > *pssh_boxes)