Skip to content

Commit

Permalink
Zero out trailing bytes in CryptoInfo.iv
Browse files Browse the repository at this point in the history
CryptoInfo.iv length is always 16. When the actual initialization vector
is shorter, zero out the trailing bytes.

Issue: #6982
PiperOrigin-RevId: 295575845
  • Loading branch information
ojw28 authored and icbaker committed Feb 17, 2020
1 parent 27bd129 commit d1e4a63
Show file tree
Hide file tree
Showing 5 changed files with 114 additions and 74 deletions.
6 changes: 6 additions & 0 deletions RELEASENOTES.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,12 @@
show how to render video to a `GLSurfaceView` while applying a GL shader.
([#6920](https://github.com/google/ExoPlayer/issues/6920)).

### 2.11.3 (2020-02-19) ###

* DRM: Fix issue switching from protected content that uses a 16-byte
initialization vector to one that uses an 8-byte initialization vector
([#6982](https://github.com/google/ExoPlayer/issues/6982)).

### 2.11.2 (2020-02-13) ###

* Add Java FLAC extractor
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,27 +27,41 @@
public final class CryptoInfo {

/**
* The 16 byte initialization vector. If the initialization vector of the content is shorter than
* 16 bytes, 0 byte padding is appended to extend the vector to the required 16 byte length.
*
* @see android.media.MediaCodec.CryptoInfo#iv
*/
public byte[] iv;
/**
* The 16 byte key id.
*
* @see android.media.MediaCodec.CryptoInfo#key
*/
public byte[] key;
/**
* The type of encryption that has been applied. Must be one of the {@link C.CryptoMode} values.
*
* @see android.media.MediaCodec.CryptoInfo#mode
*/
@C.CryptoMode
public int mode;
@C.CryptoMode public int mode;
/**
* The number of leading unencrypted bytes in each sub-sample. If null, all bytes are treated as
* encrypted and {@link #numBytesOfEncryptedData} must be specified.
*
* @see android.media.MediaCodec.CryptoInfo#numBytesOfClearData
*/
public int[] numBytesOfClearData;
/**
* The number of trailing encrypted bytes in each sub-sample. If null, all bytes are treated as
* clear and {@link #numBytesOfClearData} must be specified.
*
* @see android.media.MediaCodec.CryptoInfo#numBytesOfEncryptedData
*/
public int[] numBytesOfEncryptedData;
/**
* The number of subSamples that make up the buffer's contents.
*
* @see android.media.MediaCodec.CryptoInfo#numSubSamples
*/
public int numSubSamples;
Expand Down Expand Up @@ -112,10 +126,10 @@ public void copyTo(android.media.MediaCodec.CryptoInfo cryptoInfo) {
// Update cryptoInfo fields directly because CryptoInfo.set performs an unnecessary
// object allocation on Android N.
cryptoInfo.numSubSamples = numSubSamples;
cryptoInfo.numBytesOfClearData = copyOrNull(frameworkCryptoInfo.numBytesOfClearData);
cryptoInfo.numBytesOfEncryptedData = copyOrNull(frameworkCryptoInfo.numBytesOfEncryptedData);
cryptoInfo.key = copyOrNull(frameworkCryptoInfo.key);
cryptoInfo.iv = copyOrNull(frameworkCryptoInfo.iv);
cryptoInfo.numBytesOfClearData = copyOrNull(numBytesOfClearData);
cryptoInfo.numBytesOfEncryptedData = copyOrNull(numBytesOfEncryptedData);
cryptoInfo.key = copyOrNull(key);
cryptoInfo.iv = copyOrNull(iv);
cryptoInfo.mode = mode;
if (Util.SDK_INT >= 24) {
android.media.MediaCodec.CryptoInfo.Pattern pattern = patternHolder.pattern;
Expand Down Expand Up @@ -148,31 +162,19 @@ public void increaseClearDataFirstSubSampleBy(int count) {
if (count == 0) {
return;
}

if (numBytesOfClearData == null) {
numBytesOfClearData = new int[1];
}
numBytesOfClearData[0] += count;

// It is OK to have numBytesOfClearData and frameworkCryptoInfo.numBytesOfClearData point to
// the same array, see set().
if (frameworkCryptoInfo.numBytesOfClearData == null) {
frameworkCryptoInfo.numBytesOfClearData = numBytesOfClearData;
}

// Update frameworkCryptoInfo.numBytesOfClearData only if it points to a different array than
// numBytesOfClearData (all fields are public and non-final, therefore they can set be set
// directly without calling set()). Otherwise, the array has been updated already in the steps
// above.
if (frameworkCryptoInfo.numBytesOfClearData != numBytesOfClearData) {
frameworkCryptoInfo.numBytesOfClearData[0] += count;
}
numBytesOfClearData[0] += count;
}

@Nullable
private static int[] copyOrNull(@Nullable int[] array) {
return array != null ? Arrays.copyOf(array, array.length) : null;
}

@Nullable
private static byte[] copyOrNull(@Nullable byte[] array) {
return array != null ? Arrays.copyOf(array, array.length) : null;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,35 +65,4 @@ public void increaseClearDataFirstSubSampleBy_withSharedClearDataPointer_setsVal
assertThat(cryptoInfo.numBytesOfClearData[0]).isEqualTo(6);
assertThat(cryptoInfo.getFrameworkCryptoInfo().numBytesOfClearData[0]).isEqualTo(6);
}

@Test
public void increaseClearDataFirstSubSampleBy_withDifferentClearDataArrays_setsValue() {
cryptoInfo.numBytesOfClearData = new int[] {1, 1, 1, 1};
cryptoInfo.getFrameworkCryptoInfo().numBytesOfClearData = new int[] {5, 5, 5, 5};

cryptoInfo.increaseClearDataFirstSubSampleBy(5);

assertThat(cryptoInfo.numBytesOfClearData[0]).isEqualTo(6);
assertThat(cryptoInfo.getFrameworkCryptoInfo().numBytesOfClearData[0]).isEqualTo(10);
}

@Test
public void increaseClearDataFirstSubSampleBy_withInternalClearDataArraysNull_setsValue() {
cryptoInfo.numBytesOfClearData = new int[] {10, 10, 10, 10};

cryptoInfo.increaseClearDataFirstSubSampleBy(5);

assertThat(cryptoInfo.numBytesOfClearData[0]).isEqualTo(15);
assertThat(cryptoInfo.getFrameworkCryptoInfo().numBytesOfClearData[0]).isEqualTo(15);
}

@Test
public void increaseClearDataFirstSubSampleBy_internalClearDataIsNotNull_setsValue() {
cryptoInfo.getFrameworkCryptoInfo().numBytesOfClearData = new int[] {5, 5, 5, 5};

cryptoInfo.increaseClearDataFirstSubSampleBy(5);

assertThat(cryptoInfo.numBytesOfClearData[0]).isEqualTo(5);
assertThat(cryptoInfo.getFrameworkCryptoInfo().numBytesOfClearData[0]).isEqualTo(10);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

import androidx.annotation.Nullable;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.decoder.CryptoInfo;
import com.google.android.exoplayer2.decoder.DecoderInputBuffer;
import com.google.android.exoplayer2.extractor.SampleDataReader;
import com.google.android.exoplayer2.extractor.TrackOutput.CryptoData;
Expand All @@ -28,6 +29,7 @@
import java.io.EOFException;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.Arrays;

/** A queue of media sample data. */
/* package */ class SampleDataQueue {
Expand Down Expand Up @@ -229,10 +231,14 @@ private void readEncryptionData(DecoderInputBuffer buffer, SampleExtrasHolder ex
int ivSize = signalByte & 0x7F;

// Read the initialization vector.
if (buffer.cryptoInfo.iv == null) {
buffer.cryptoInfo.iv = new byte[16];
CryptoInfo cryptoInfo = buffer.cryptoInfo;
if (cryptoInfo.iv == null) {
cryptoInfo.iv = new byte[16];
} else {
// Zero out cryptoInfo.iv so that if ivSize < 16, the remaining bytes are correctly set to 0.
Arrays.fill(cryptoInfo.iv, (byte) 0);
}
readData(offset, buffer.cryptoInfo.iv, ivSize);
readData(offset, cryptoInfo.iv, ivSize);
offset += ivSize;

// Read the subsample count, if present.
Expand All @@ -247,11 +253,11 @@ private void readEncryptionData(DecoderInputBuffer buffer, SampleExtrasHolder ex
}

// Write the clear and encrypted subsample sizes.
@Nullable int[] clearDataSizes = buffer.cryptoInfo.numBytesOfClearData;
@Nullable int[] clearDataSizes = cryptoInfo.numBytesOfClearData;
if (clearDataSizes == null || clearDataSizes.length < subsampleCount) {
clearDataSizes = new int[subsampleCount];
}
@Nullable int[] encryptedDataSizes = buffer.cryptoInfo.numBytesOfEncryptedData;
@Nullable int[] encryptedDataSizes = cryptoInfo.numBytesOfEncryptedData;
if (encryptedDataSizes == null || encryptedDataSizes.length < subsampleCount) {
encryptedDataSizes = new int[subsampleCount];
}
Expand All @@ -272,12 +278,12 @@ private void readEncryptionData(DecoderInputBuffer buffer, SampleExtrasHolder ex

// Populate the cryptoInfo.
CryptoData cryptoData = Util.castNonNull(extrasHolder.cryptoData);
buffer.cryptoInfo.set(
cryptoInfo.set(
subsampleCount,
clearDataSizes,
encryptedDataSizes,
cryptoData.encryptionKey,
buffer.cryptoInfo.iv,
cryptoInfo.iv,
cryptoData.cryptoMode,
cryptoData.encryptedBlocks,
cryptoData.clearBlocks);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,15 @@
*/
package com.google.android.exoplayer2.source;

import static com.google.android.exoplayer2.C.BUFFER_FLAG_ENCRYPTED;
import static com.google.android.exoplayer2.C.BUFFER_FLAG_KEY_FRAME;
import static com.google.android.exoplayer2.C.RESULT_BUFFER_READ;
import static com.google.android.exoplayer2.C.RESULT_FORMAT_READ;
import static com.google.android.exoplayer2.C.RESULT_NOTHING_READ;
import static com.google.common.truth.Truth.assertThat;
import static java.lang.Long.MIN_VALUE;
import static java.util.Arrays.copyOfRange;
import static org.junit.Assert.assertArrayEquals;
import static org.mockito.Mockito.when;

import androidx.annotation.Nullable;
Expand Down Expand Up @@ -114,17 +116,13 @@ public final class SampleQueueTest {
C.BUFFER_FLAG_KEY_FRAME, C.BUFFER_FLAG_ENCRYPTED, 0, C.BUFFER_FLAG_ENCRYPTED,
};
private static final long[] ENCRYPTED_SAMPLE_TIMESTAMPS = new long[] {0, 1000, 2000, 3000};
private static final Format[] ENCRYPTED_SAMPLES_FORMATS =
private static final Format[] ENCRYPTED_SAMPLE_FORMATS =
new Format[] {FORMAT_ENCRYPTED, FORMAT_ENCRYPTED, FORMAT_1, FORMAT_ENCRYPTED};
/** Encrypted samples require the encryption preamble. */
private static final int[] ENCRYPTED_SAMPLES_SIZES = new int[] {1, 3, 1, 3};
private static final int[] ENCRYPTED_SAMPLE_SIZES = new int[] {1, 3, 1, 3};

private static final int[] ENCRYPTED_SAMPLES_OFFSETS = new int[] {7, 4, 3, 0};
private static final byte[] ENCRYPTED_SAMPLES_DATA = new byte[8];

static {
Arrays.fill(ENCRYPTED_SAMPLES_DATA, (byte) 1);
}
private static final int[] ENCRYPTED_SAMPLE_OFFSETS = new int[] {7, 4, 3, 0};
private static final byte[] ENCRYPTED_SAMPLE_DATA = new byte[] {1, 1, 1, 1, 1, 1, 1, 1};

private static final TrackOutput.CryptoData DUMMY_CRYPTO_DATA =
new TrackOutput.CryptoData(C.CRYPTO_MODE_AES_CTR, new byte[16], 0, 0);
Expand Down Expand Up @@ -461,6 +459,60 @@ public void testAllowPlaceholderSessionPopulatesDrmSession() {
/* decodeOnlyUntilUs= */ 0);
assertThat(result).isEqualTo(RESULT_FORMAT_READ);
assertThat(formatHolder.drmSession).isSameInstanceAs(mockDrmSession);
assertReadEncryptedSample(/* sampleIndex= */ 3);
}

@Test
@SuppressWarnings("unchecked")
public void testTrailingCryptoInfoInitializationVectorBytesZeroed() {
when(mockDrmSession.getState()).thenReturn(DrmSession.STATE_OPENED_WITH_KEYS);
DrmSession<ExoMediaCrypto> mockPlaceholderDrmSession =
(DrmSession<ExoMediaCrypto>) Mockito.mock(DrmSession.class);
when(mockPlaceholderDrmSession.getState()).thenReturn(DrmSession.STATE_OPENED_WITH_KEYS);
when(mockDrmSessionManager.acquirePlaceholderSession(
ArgumentMatchers.any(), ArgumentMatchers.anyInt()))
.thenReturn(mockPlaceholderDrmSession);

writeFormat(ENCRYPTED_SAMPLE_FORMATS[0]);
byte[] sampleData = new byte[] {0, 1, 2};
byte[] initializationVector = new byte[] {7, 6, 5, 4, 3, 2, 1, 0};
byte[] encryptedSampleData =
TestUtil.joinByteArrays(
new byte[] {
0x08, // subsampleEncryption = false (1 bit), ivSize = 8 (7 bits).
},
initializationVector,
sampleData);
writeSample(
encryptedSampleData, /* timestampUs= */ 0, BUFFER_FLAG_KEY_FRAME | BUFFER_FLAG_ENCRYPTED);

int result =
sampleQueue.read(
formatHolder,
inputBuffer,
/* formatRequired= */ false,
/* loadingFinished= */ false,
/* decodeOnlyUntilUs= */ 0);
assertThat(result).isEqualTo(RESULT_FORMAT_READ);

// Fill cryptoInfo.iv with non-zero data. When the 8 byte initialization vector is written into
// it, we expect the trailing 8 bytes to be zeroed.
inputBuffer.cryptoInfo.iv = new byte[16];
Arrays.fill(inputBuffer.cryptoInfo.iv, (byte) 1);

result =
sampleQueue.read(
formatHolder,
inputBuffer,
/* formatRequired= */ false,
/* loadingFinished= */ false,
/* decodeOnlyUntilUs= */ 0);
assertThat(result).isEqualTo(RESULT_BUFFER_READ);

// Assert cryptoInfo.iv contains the 8-byte initialization vector and that the trailing 8 bytes
// have been zeroed.
byte[] expectedInitializationVector = Arrays.copyOf(initializationVector, 16);
assertArrayEquals(expectedInitializationVector, inputBuffer.cryptoInfo.iv);
}

@Test
Expand Down Expand Up @@ -995,11 +1047,11 @@ private void writeTestData() {

private void writeTestDataWithEncryptedSections() {
writeTestData(
ENCRYPTED_SAMPLES_DATA,
ENCRYPTED_SAMPLES_SIZES,
ENCRYPTED_SAMPLES_OFFSETS,
ENCRYPTED_SAMPLE_DATA,
ENCRYPTED_SAMPLE_SIZES,
ENCRYPTED_SAMPLE_OFFSETS,
ENCRYPTED_SAMPLE_TIMESTAMPS,
ENCRYPTED_SAMPLES_FORMATS,
ENCRYPTED_SAMPLE_FORMATS,
ENCRYPTED_SAMPLES_FLAGS);
}

Expand Down Expand Up @@ -1033,7 +1085,12 @@ private void writeFormat(Format format) {
/** Writes a single sample to {@code sampleQueue}. */
private void writeSample(byte[] data, long timestampUs, int sampleFlags) {
sampleQueue.sampleData(new ParsableByteArray(data), data.length);
sampleQueue.sampleMetadata(timestampUs, sampleFlags, data.length, 0, null);
sampleQueue.sampleMetadata(
timestampUs,
sampleFlags,
data.length,
/* offset= */ 0,
(sampleFlags & C.BUFFER_FLAG_ENCRYPTED) != 0 ? DUMMY_CRYPTO_DATA : null);
}

/**
Expand Down Expand Up @@ -1206,7 +1263,7 @@ private void assertReadFormat(boolean formatRequired, Format format) {
}

private void assertReadEncryptedSample(int sampleIndex) {
byte[] sampleData = new byte[ENCRYPTED_SAMPLES_SIZES[sampleIndex]];
byte[] sampleData = new byte[ENCRYPTED_SAMPLE_SIZES[sampleIndex]];
Arrays.fill(sampleData, (byte) 1);
boolean isKeyFrame = (ENCRYPTED_SAMPLES_FLAGS[sampleIndex] & C.BUFFER_FLAG_KEY_FRAME) != 0;
boolean isEncrypted = (ENCRYPTED_SAMPLES_FLAGS[sampleIndex] & C.BUFFER_FLAG_ENCRYPTED) != 0;
Expand All @@ -1216,7 +1273,7 @@ private void assertReadEncryptedSample(int sampleIndex) {
isEncrypted,
sampleData,
/* offset= */ 0,
ENCRYPTED_SAMPLES_SIZES[sampleIndex] - (isEncrypted ? 2 : 0));
ENCRYPTED_SAMPLE_SIZES[sampleIndex] - (isEncrypted ? 2 : 0));
}

/**
Expand Down

0 comments on commit d1e4a63

Please sign in to comment.