Support CAST V2 authentication

bug: 12702350

Squashed commit of these CLs from the widevine cdm repo:

Cast V2 cdm support
https://widevine-internal-review.googlesource.com/#/c/9190/

Add CASTv2 Support to DrmPlugin
https://widevine-internal-review.googlesource.com/#/c/9228/

Test for CastV2 authentication APIs
https://widevine-internal-review.googlesource.com/9550

Change-Id: I6d66bc1bbd653db5542c68687b30b441dd20617f
This commit is contained in:
Jeff Tinker
2014-03-10 12:41:14 -07:00
parent f111bea1b1
commit 3db90f54c1
26 changed files with 864 additions and 44 deletions

View File

@@ -0,0 +1,14 @@
LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE_TAGS := optional
LOCAL_SRC_FILES := $(call all-java-files-under, src)
LOCAL_STATIC_JAVA_LIBRARIES := com.android.mediadrm.signer
LOCAL_PACKAGE_NAME := CastSignAPITest
include $(BUILD_PACKAGE)
include $(call all-makefiles-under,$(LOCAL_PATH))

View File

@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.castv2.test"
>
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-library android:name="com.android.mediadrm.signer"
android:required="true" />
<uses-sdk android:minSdkVersion="18"></uses-sdk>
<application android:icon="@drawable/icon" android:label="@string/app_name" android:theme="@android:style/Theme.Holo.NoActionBar">
<activity android:name="CastSignAPITest"
android:label="@string/app_name"
android:screenOrientation="landscape">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>

View File

@@ -0,0 +1,2 @@
# Project target.
target=android-IceCreamSandwich

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical">
</LinearLayout>

View File

@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">CastSignAPI Test</string>
</resources>

View File

@@ -0,0 +1,206 @@
package com.android.castv2.test;
import android.app.Activity;
import android.os.Bundle;
import android.os.Looper;
import android.view.View;
import android.view.SurfaceView;
import android.view.SurfaceHolder;
import android.view.Surface;
import android.view.Display;
import android.content.Context;
import android.graphics.Point;
import android.media.MediaDrm;
import android.widget.TextView;
import android.media.MediaDrm.CryptoSession;
import android.media.MediaDrmException;
import android.media.NotProvisionedException;
import android.media.MediaCrypto;
import android.media.MediaCodec;
import android.media.MediaCryptoException;
import android.media.MediaCodec.CryptoException;
import android.media.MediaCodecList;
import android.media.MediaCodec.CryptoInfo;
import android.media.MediaCodecInfo;
import android.media.MediaFormat;
import android.util.Log;
import android.util.TypedValue;
import android.util.AttributeSet;
import java.util.UUID;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.ListIterator;
import java.util.Iterator;
import java.util.HashMap;
import java.util.Random;
import java.nio.ByteBuffer;
import java.lang.Exception;
import java.lang.InterruptedException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.lang.Math;
import java.security.cert.CertificateFactory;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.PublicKey;
import java.security.Signature;
import java.security.SignatureException;
import java.security.InvalidKeyException;
import com.android.mediadrm.signer.MediaDrmSigner;
import java.io.InputStream;
import java.io.ByteArrayInputStream;
import dalvik.system.DexClassLoader;
public class CastSignAPITest extends Activity {
private final String TAG = "CastSignAPITest";
static final UUID kWidevineScheme = new UUID(0xEDEF8BA979D64ACEL, 0xA3C827DCD51D21EDL);
private boolean mTestFailed;
private TextView mTextView;
private String mDisplayText;
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mTextView = new TextView(this);
mDisplayText = new String("Cast Signing API Test\n\n");
Display display = getWindowManager().getDefaultDisplay();
Point size = new Point();
display.getSize(size);
int width = size.x;
int height = size.y;
final int max_lines = 20;
int textSize = Math.min(width, height) / max_lines;
mTextView.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize);
setContentView(mTextView);
Log.i(TAG, "width=" + width + " height=" + height + " size=" + textSize);
new Thread() {
@Override
public void run() {
mTestFailed = false;
testCastSign();
if (mTestFailed) {
displayText("\nTEST FAILED!");
} else {
displayText("\nTEST PASSED!");
}
}
}.start();
}
// draw text on the surface view
private void displayText(String text) {
Log.i(TAG, text);
mDisplayText += text + "\n";
runOnUiThread(new Runnable() {
public void run() {
mTextView.setText(mDisplayText);
}
});
}
private byte[] openSession(MediaDrm drm) {
byte[] sessionId = null;
boolean retryOpen;
do {
try {
retryOpen = false;
sessionId = drm.openSession();
} catch (NotProvisionedException e) {
Log.i(TAG, "Missing certificate, provisioning");
ProvisionRequester provisionRequester = new ProvisionRequester();
provisionRequester.doTransact(drm);
retryOpen = true;
}
} while (retryOpen);
return sessionId;
}
private void testCastSign() {
final byte[] kMessage = hex2ba("ee07514066c23c770a665719abf051e0" +
"f75a399578305eb2547ca67ecd2356ca" +
"7bc0f5dc1854533eb98ee0fae1107ad2" +
"a8707c671be10fcd4853990897b71378" +
"70e39957a1536b39132e438ba681810d" +
"ebc3f9fb38907a1066b09c63af9016a7" +
"57425b29ff7fdf53859ebdc1659bdc49" +
"f40a5cb5f597b0b8a836b424ad9c68a9");
final byte[] kDigest = hex2ba(// digest info header
"3021300906052b0e03021a05000414" +
// sha1 of kMessage
"d2662f893aaec72f3ca6decc2aa942f3949e8b21");
MediaDrm drm;
try {
drm = new MediaDrm(kWidevineScheme);
} catch (MediaDrmException e) {
Log.e(TAG, "Failed to create MediaDrm: ", e);
mTestFailed = true;
return;
}
CertificateRequester certificateRequester = new CertificateRequester();
MediaDrmSigner.Certificate signerCert = certificateRequester.doTransact(drm);
if (signerCert == null) {
displayText("Failed to get certificate from server!");
mTestFailed = true;
} else {
byte[] sessionId = openSession(drm);
byte[] signature = MediaDrmSigner.signRSA(this, drm, sessionId, "PKCS1-BlockType1",
signerCert.getWrappedPrivateKey(),
kDigest);
try {
byte[] certBuf = signerCert.getContent();
InputStream certStream = new ByteArrayInputStream(certBuf);
CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
Certificate javaCert = certFactory.generateCertificate(certStream);
PublicKey pubkey = javaCert.getPublicKey();
Signature instance = Signature.getInstance("SHA1WithRSA");
instance.initVerify(pubkey);
instance.update(kMessage);
if (instance.verify(signature)) {
Log.d(TAG, "Signatures match, test passed!");
} else {
Log.e(TAG, "Signature did not verify, test failed!");
mTestFailed = true;
}
} catch (CertificateException e) {
Log.e(TAG, "Failed to parse certificate", e);
mTestFailed = true;
} catch (NoSuchAlgorithmException e) {
mTestFailed = true;
Log.e(TAG, "Failed to get signature instance", e);
} catch (InvalidKeyException e) {
mTestFailed = true;
Log.e(TAG, "Invalid public key", e);
} catch (SignatureException e) {
mTestFailed = true;
Log.e(TAG, "Failed to update", e);
}
drm.closeSession(sessionId);
}
}
private static byte[] hex2ba(String s) {
int len = s.length();
byte[] data = new byte[len / 2];
for (int i = 0; i < len; i += 2) {
data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4)
+ Character.digit(s.charAt(i+1), 16));
}
return data;
}
}

View File

@@ -0,0 +1,124 @@
package com.android.castv2.test;
import android.media.MediaDrm;
import com.android.mediadrm.signer.MediaDrmSigner;
import android.media.DeniedByServerException;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.HttpClient;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.HttpResponse;
import org.apache.http.util.EntityUtils;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.io.InputStream;
import java.io.ByteArrayInputStream;
import java.util.Arrays;
import android.os.AsyncTask;
import android.util.Log;
public class CertificateRequester {
private final String TAG = "CertificateRequester";
public CertificateRequester() {
}
public MediaDrmSigner.Certificate doTransact(MediaDrm drm) {
MediaDrmSigner.Certificate result = null;
MediaDrmSigner.CertificateRequest certificateRequest =
MediaDrmSigner.getCertificateRequest(drm, MediaDrmSigner.CERTIFICATE_TYPE_X509,
"test-certificate");
PostRequestTask postTask = new PostRequestTask(certificateRequest.getData());
Log.i(TAG, "Requesting certificate from server '" + certificateRequest.getDefaultUrl() + "'");
postTask.execute(certificateRequest.getDefaultUrl());
// wait for post task to complete
byte[] responseBody;
long startTime = System.currentTimeMillis();
do {
responseBody = postTask.getResponseBody();
if (responseBody == null) {
sleep(100);
} else {
break;
}
} while (System.currentTimeMillis() - startTime < 5000);
if (responseBody == null) {
Log.e(TAG, "No response from certificate server!");
} else {
try {
result = MediaDrmSigner.provideCertificateResponse(drm, responseBody);
} catch (DeniedByServerException e) {
Log.e(TAG, "Server denied certificate request");
}
}
return result;
}
private class PostRequestTask extends AsyncTask<String, Void, Void> {
private final String TAG = "PostRequestTask";
private byte[] mCertificateRequest;
private byte[] mResponseBody;
public PostRequestTask(byte[] certificateRequest) {
mCertificateRequest = certificateRequest;
}
protected Void doInBackground(String... urls) {
mResponseBody = postRequest(urls[0], mCertificateRequest);
if (mResponseBody != null) {
Log.d(TAG, "response length=" + mResponseBody.length);
}
return null;
}
public byte[] getResponseBody() {
return mResponseBody;
}
private byte[] postRequest(String url, byte[] certificateRequest) {
HttpClient httpclient = new DefaultHttpClient();
HttpPost httppost = new HttpPost(url + "?signedRequest=" + new String(certificateRequest));
Log.d(TAG, "PostRequest:" + httppost.getRequestLine());
try {
// Add data
httppost.setHeader("Accept", "*/*");
httppost.setHeader("User-Agent", "Widevine CDM v1.0");
httppost.setHeader("Content-Type", "application/json");
// Execute HTTP Post Request
HttpResponse response = httpclient.execute(httppost);
byte[] responseBody;
int responseCode = response.getStatusLine().getStatusCode();
if (responseCode == 200) {
responseBody = EntityUtils.toByteArray(response.getEntity());
} else {
Log.d(TAG, "Server returned HTTP error code " + responseCode);
return null;
}
return responseBody;
} catch (ClientProtocolException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
}
private void sleep(int msec) {
try {
Thread.sleep(msec);
} catch (InterruptedException e) {
}
}
}

View File

@@ -0,0 +1,117 @@
package com.android.castv2.test;
import android.media.MediaDrm;
import android.media.DeniedByServerException;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.HttpClient;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.HttpResponse;
import org.apache.http.util.EntityUtils;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.Arrays;
import android.os.AsyncTask;
import android.util.Log;
public class ProvisionRequester {
private final String TAG = "ProvisionRequester";
public ProvisionRequester() {
}
public void doTransact(MediaDrm drm) {
MediaDrm.ProvisionRequest drmRequest;
drmRequest = drm.getProvisionRequest();
PostRequestTask postTask = new PostRequestTask(drmRequest.getData());
Log.i(TAG, "Attempting to provision from server '" + drmRequest.getDefaultUrl() + "'");
postTask.execute(drmRequest.getDefaultUrl());
// wait for post task to complete
byte[] responseBody;
long startTime = System.currentTimeMillis();
do {
responseBody = postTask.getResponseBody();
if (responseBody == null) {
sleep(100);
} else {
break;
}
} while (System.currentTimeMillis() - startTime < 5000);
if (responseBody == null) {
Log.e(TAG, "No response from provisioning server!");
} else {
try {
drm.provideProvisionResponse(responseBody);
} catch (DeniedByServerException e) {
Log.e(TAG, "Server denied provisioning request");
}
}
}
private class PostRequestTask extends AsyncTask<String, Void, Void> {
private final String TAG = "PostRequestTask";
private byte[] mDrmRequest;
private byte[] mResponseBody;
public PostRequestTask(byte[] drmRequest) {
mDrmRequest = drmRequest;
}
protected Void doInBackground(String... urls) {
mResponseBody = postRequest(urls[0], mDrmRequest);
if (mResponseBody != null) {
Log.d(TAG, "response length=" + mResponseBody.length);
}
return null;
}
public byte[] getResponseBody() {
return mResponseBody;
}
private byte[] postRequest(String url, byte[] drmRequest) {
HttpClient httpclient = new DefaultHttpClient();
HttpPost httppost = new HttpPost(url + "?signedRequest=" + new String(drmRequest));
Log.d(TAG, "PostRequest:" + httppost.getRequestLine());
try {
// Add data
httppost.setHeader("Accept", "*/*");
httppost.setHeader("User-Agent", "Widevine CDM v1.0");
httppost.setHeader("Content-Type", "application/json");
// Execute HTTP Post Request
HttpResponse response = httpclient.execute(httppost);
byte[] responseBody;
int responseCode = response.getStatusLine().getStatusCode();
if (responseCode == 200) {
responseBody = EntityUtils.toByteArray(response.getEntity());
} else {
Log.d(TAG, "Server returned HTTP error code " + responseCode);
return null;
}
return responseBody;
} catch (ClientProtocolException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
}
private void sleep(int msec) {
try {
Thread.sleep(msec);
} catch (InterruptedException e) {
}
}
}