blob: a16d95cb8e9fc55638e415a886c3f4f867fae813 [file] [log] [blame]
// Copyright 2018 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "media/cdm/cenc_decryptor.h"
#include <stdint.h>
#include <memory>
#include <string>
#include <string_view>
#include <vector>
#include "base/containers/heap_array.h"
#include "base/containers/span.h"
#include "base/logging.h"
#include "crypto/aes_ctr.h"
#include "media/base/decoder_buffer.h"
#include "media/base/decrypt_config.h"
#include "media/base/subsample_entry.h"
namespace media {
namespace {
constexpr size_t kRequiredKeyBytes = 16;
enum ClearBytesBufferSel { kSrcContainsClearBytes, kDstContainsClearBytes };
// Copy the cypher bytes as specified by |subsamples| from |src| to |dst|.
// If |sel| == kSrcContainsClearBytes, then |src| is expected to contain any
// clear bytes specified by |subsamples| and will be skipped. This is used
// when copying all the protected data out of a sample. If |sel| ==
// kDstContainsClearBytes, then any clear bytes mentioned in |subsamples|
// will be skipped in |dst|. This is used when copying the decrypted bytes
// back into the buffer, replacing the encrypted portions.
void CopySubsamples(const std::vector<SubsampleEntry>& subsamples,
const ClearBytesBufferSel sel,
base::span<const uint8_t> src,
base::span<uint8_t> dst) {
size_t src_i = 0;
size_t dst_i = 0;
for (const auto& subsample : subsamples) {
if (sel == kSrcContainsClearBytes) {
src_i += subsample.clear_bytes;
} else {
dst_i += subsample.clear_bytes;
}
auto src_view = src.subspan(src_i, subsample.cypher_bytes);
auto dst_view = dst.subspan(dst_i, subsample.cypher_bytes);
dst_view.copy_from(src_view);
src_i += subsample.cypher_bytes;
dst_i += subsample.cypher_bytes;
}
}
// TODO(crbug.com/40575437): This should be done in DecoderBuffer so that
// additional fields are more easily handled.
void CopyExtraSettings(const DecoderBuffer& input, DecoderBuffer* output) {
output->set_timestamp(input.timestamp());
output->set_duration(input.duration());
output->set_is_key_frame(input.is_key_frame());
if (input.side_data()) {
output->set_side_data(input.side_data()->Clone());
}
}
} // namespace
scoped_refptr<DecoderBuffer> DecryptCencBuffer(const DecoderBuffer& input,
base::span<const uint8_t> key) {
base::span<const uint8_t> sample = input;
DCHECK(!sample.empty()) << "No data to decrypt.";
const DecryptConfig* decrypt_config = input.decrypt_config();
DCHECK(decrypt_config) << "No need to call Decrypt() on unencrypted buffer.";
DCHECK_EQ(EncryptionScheme::kCenc, decrypt_config->encryption_scheme());
if (key.size() != kRequiredKeyBytes) {
DVLOG(1) << "Supplied key is the wrong size for CENC";
return nullptr;
}
auto iv = base::as_byte_span(decrypt_config->iv())
.to_fixed_extent<crypto::aes_ctr::kCounterSize>();
if (!iv) {
DVLOG(1) << "Supplied IV is the wrong size for CENC";
return nullptr;
}
const std::vector<SubsampleEntry>& subsamples = decrypt_config->subsamples();
if (subsamples.empty()) {
auto decrypted = base::HeapArray<uint8_t>::Uninit(sample.size());
crypto::aes_ctr::Decrypt(key, *iv, sample, decrypted);
auto output = DecoderBuffer::FromArray(std::move(decrypted));
CopyExtraSettings(input, output.get());
return output;
}
if (!VerifySubsamplesMatchSize(subsamples, sample.size())) {
DVLOG(1) << "Subsample sizes do not equal input size";
return nullptr;
}
// Compute the size of the encrypted portion. Overflow, etc. checked by
// the call to VerifySubsamplesMatchSize().
size_t total_encrypted_size = 0;
for (const auto& subsample : subsamples)
total_encrypted_size += subsample.cypher_bytes;
// No need to decrypt if there is no encrypted data.
if (total_encrypted_size == 0) {
auto output = DecoderBuffer::CopyFrom(sample);
CopyExtraSettings(input, output.get());
return output;
}
// The encrypted portions of all subsamples must form a contiguous block,
// such that an encrypted subsample that ends away from a block boundary is
// immediately followed by the start of the next encrypted subsample. We
// copy all encrypted subsamples to a contiguous buffer, decrypt them, then
// copy the decrypted bytes over the encrypted bytes in the output.
// TODO(strobe): attempt to reduce number of memory copies
auto encrypted = base::HeapArray<uint8_t>::Uninit(total_encrypted_size);
auto decrypted = base::HeapArray<uint8_t>::Uninit(total_encrypted_size);
CopySubsamples(subsamples, kSrcContainsClearBytes, sample, encrypted);
crypto::aes_ctr::Decrypt(key, *iv, encrypted, decrypted);
scoped_refptr<DecoderBuffer> output = DecoderBuffer::CopyFrom(sample);
CopySubsamples(subsamples, kDstContainsClearBytes, decrypted,
output->writable_span());
CopyExtraSettings(input, output.get());
return output;
}
} // namespace media