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:
14
libwvdrmengine/test/castv2/Android.mk
Normal file
14
libwvdrmengine/test/castv2/Android.mk
Normal 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))
|
||||
22
libwvdrmengine/test/castv2/AndroidManifest.xml
Normal file
22
libwvdrmengine/test/castv2/AndroidManifest.xml
Normal 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>
|
||||
2
libwvdrmengine/test/castv2/default.properties
Normal file
2
libwvdrmengine/test/castv2/default.properties
Normal file
@@ -0,0 +1,2 @@
|
||||
# Project target.
|
||||
target=android-IceCreamSandwich
|
||||
BIN
libwvdrmengine/test/castv2/res/drawable-hdpi/icon.png
Normal file
BIN
libwvdrmengine/test/castv2/res/drawable-hdpi/icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.0 KiB |
BIN
libwvdrmengine/test/castv2/res/drawable-ldpi/icon.png
Normal file
BIN
libwvdrmengine/test/castv2/res/drawable-ldpi/icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.7 KiB |
BIN
libwvdrmengine/test/castv2/res/drawable-mdpi/icon.png
Normal file
BIN
libwvdrmengine/test/castv2/res/drawable-mdpi/icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.5 KiB |
6
libwvdrmengine/test/castv2/res/layout/main.xml
Normal file
6
libwvdrmengine/test/castv2/res/layout/main.xml
Normal 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>
|
||||
4
libwvdrmengine/test/castv2/res/values/strings.xml
Normal file
4
libwvdrmengine/test/castv2/res/values/strings.xml
Normal file
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="app_name">CastSignAPI Test</string>
|
||||
</resources>
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user