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 committed Feb 18, 2020
1 parent a967ff3 commit d93b57c
Show file tree
Hide file tree
Showing 4 changed files with 107 additions and 24 deletions.
6 changes: 6 additions & 0 deletions RELEASENOTES.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# Release notes #

### 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 @@ -25,27 +25,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
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.ExtractorInput;
import com.google.android.exoplayer2.extractor.TrackOutput.CryptoData;
Expand All @@ -27,6 +28,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 @@ -228,10 +230,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 @@ -246,11 +252,11 @@ private void readEncryptionData(DecoderInputBuffer buffer, SampleExtrasHolder ex
}

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

// Populate the cryptoInfo.
CryptoData cryptoData = 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 d93b57c

Please sign in to comment.