Merge "Support CAST V2 Authentication in MediaDrm" into klp-modular-dev

This commit is contained in:
Jeff Tinker
2014-04-01 21:54:29 +00:00
committed by Android (Google) Code Review
11 changed files with 490 additions and 26 deletions

View File

@@ -1,4 +1,4 @@
/*
/*
* Copyright (C) 2013 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -21,12 +21,15 @@ import java.lang.ref.WeakReference;
import java.util.UUID;
import java.util.HashMap;
import java.util.List;
import android.os.Binder;
import android.os.Debug;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.os.Bundle;
import android.os.Parcel;
import android.util.Log;
import android.content.Context;
/**
* MediaDrm can be used to obtain keys for decrypting protected media streams, in
@@ -102,6 +105,20 @@ public final class MediaDrm {
private long mNativeContext;
/**
* Specify no certificate type
*
* @hide - not part of the public API at this time
*/
public static final int CERTIFICATE_TYPE_NONE = 0;
/**
* Specify X.509 certificate type
*
* @hide - not part of the public API at this time
*/
public static final int CERTIFICATE_TYPE_X509 = 1;
/**
* Query if the given scheme identified by its UUID is supported on
* this device.
@@ -318,6 +335,9 @@ public final class MediaDrm {
* Contains the opaque data an app uses to request keys from a license server
*/
public final static class KeyRequest {
private byte[] mData;
private String mDefaultUrl;
KeyRequest() {}
/**
@@ -331,9 +351,6 @@ public final class MediaDrm {
* server URL from other sources.
*/
public String getDefaultUrl() { return mDefaultUrl; }
private byte[] mData;
private String mDefaultUrl;
};
/**
@@ -458,7 +475,12 @@ public final class MediaDrm {
* is returned in ProvisionRequest.data. The recommended URL to deliver the provision
* request to is returned in ProvisionRequest.defaultUrl.
*/
public native ProvisionRequest getProvisionRequest();
public ProvisionRequest getProvisionRequest() {
return getProvisionRequestNative(CERTIFICATE_TYPE_NONE, "");
}
private native ProvisionRequest getProvisionRequestNative(int certType,
String certAuthority);
/**
* After a provision response is received by the app, it is provided to the DRM
@@ -470,7 +492,12 @@ public final class MediaDrm {
* @throws DeniedByServerException if the response indicates that the
* server rejected the request
*/
public native void provideProvisionResponse(byte[] response)
public void provideProvisionResponse(byte[] response)
throws DeniedByServerException {
provideProvisionResponseNative(response);
}
private native Certificate provideProvisionResponseNative(byte[] response)
throws DeniedByServerException;
/**
@@ -685,6 +712,120 @@ public final class MediaDrm {
return new CryptoSession(this, sessionId, cipherAlgorithm, macAlgorithm);
}
/**
* Contains the opaque data an app uses to request a certificate from a provisioning
* server
*
* @hide - not part of the public API at this time
*/
public final static class CertificateRequest {
private byte[] mData;
private String mDefaultUrl;
CertificateRequest(byte[] data, String defaultUrl) {
mData = data;
mDefaultUrl = defaultUrl;
}
/**
* Get the opaque message data
*/
public byte[] getData() { return mData; }
/**
* Get the default URL to use when sending the certificate request
* message to a server, if known. The app may prefer to use a different
* certificate server URL obtained from other sources.
*/
public String getDefaultUrl() { return mDefaultUrl; }
}
/**
* Generate a certificate request, specifying the certificate type
* and authority. The response received should be passed to
* provideCertificateResponse.
*
* @param certType Specifies the certificate type.
*
* @param certAuthority is passed to the certificate server to specify
* the chain of authority.
*
* @hide - not part of the public API at this time
*/
public CertificateRequest getCertificateRequest(int certType,
String certAuthority)
{
ProvisionRequest provisionRequest = getProvisionRequestNative(certType, certAuthority);
return new CertificateRequest(provisionRequest.getData(),
provisionRequest.getDefaultUrl());
}
/**
* Contains the wrapped private key and public certificate data associated
* with a certificate.
*
* @hide - not part of the public API at this time
*/
public final static class Certificate {
Certificate() {}
/**
* Get the wrapped private key data
*/
public byte[] getWrappedPrivateKey() { return mWrappedKey; }
/**
* Get the PEM-encoded certificate chain
*/
public byte[] getContent() { return mCertificateData; }
private byte[] mWrappedKey;
private byte[] mCertificateData;
}
/**
* Process a response from the certificate server. The response
* is obtained from an HTTP Post to the url provided by getCertificateRequest.
* <p>
* The public X509 certificate chain and wrapped private key are returned
* in the returned Certificate objec. The certificate chain is in PEM format.
* The wrapped private key should be stored in application private
* storage, and used when invoking the signRSA method.
*
* @param response the opaque certificate response byte array to provide to the
* DRM engine plugin.
*
* @throws DeniedByServerException if the response indicates that the
* server rejected the request
*
* @hide - not part of the public API at this time
*/
public Certificate provideCertificateResponse(byte[] response)
throws DeniedByServerException {
return provideProvisionResponseNative(response);
}
private static final native byte[] signRSANative(MediaDrm drm, byte[] sessionId,
String algorithm, byte[] wrappedKey,
byte[] message);
/**
* Sign data using an RSA key
*
* @param context the app context
* @param sessionId a sessionId obtained from openSession on the MediaDrm object
* @param algorithm the signing algorithm to use, e.g. "PKCS1-BlockType1"
* @param wrappedKey - the wrapped (encrypted) RSA private key obtained
* from provideCertificateResponse
* @param message the data for which a signature is to be computed
*
* @hide - not part of the public API at this time
*/
public byte[] signRSA(Context context, byte[] sessionId, String algorithm, byte[] wrappedKey, byte[] message) {
return signRSANative(this, sessionId, algorithm, wrappedKey, message);
}
@Override
protected void finalize() {
native_finalize();

View File

@@ -100,6 +100,16 @@ struct KeyTypes {
jint kKeyTypeRelease;
} gKeyTypes;
struct CertificateTypes {
jint kCertificateTypeNone;
jint kCertificateTypeX509;
} gCertificateTypes;
struct CertificateFields {
jfieldID wrappedPrivateKey;
jfieldID certificateData;
};
struct fields_t {
jfieldID context;
jmethodID post_event;
@@ -110,6 +120,11 @@ struct fields_t {
SetFields set;
IteratorFields iterator;
EntryFields entry;
CertificateFields certificate;
jclass certificateClassId;
jclass hashmapClassId;
jclass arraylistClassId;
jclass stringClassId;
};
static fields_t gFields;
@@ -406,8 +421,7 @@ static String8 JStringToString8(JNIEnv *env, jstring const &jstr) {
*/
static KeyedVector<String8, String8> HashMapToKeyedVector(JNIEnv *env, jobject &hashMap) {
jclass clazz;
FIND_CLASS(clazz, "java/lang/String");
jclass clazz = gFields.stringClassId;
KeyedVector<String8, String8> keyedVector;
jobject entrySet = env->CallObjectMethod(hashMap, gFields.hashmap.entrySet);
@@ -450,8 +464,7 @@ static KeyedVector<String8, String8> HashMapToKeyedVector(JNIEnv *env, jobject &
}
static jobject KeyedVectorToHashMap (JNIEnv *env, KeyedVector<String8, String8> const &map) {
jclass clazz;
FIND_CLASS(clazz, "java/util/HashMap");
jclass clazz = gFields.hashmapClassId;
jobject hashMap = env->NewObject(clazz, gFields.hashmap.init);
for (size_t i = 0; i < map.size(); ++i) {
jstring jkey = env->NewStringUTF(map.keyAt(i).string());
@@ -465,8 +478,7 @@ static jobject KeyedVectorToHashMap (JNIEnv *env, KeyedVector<String8, String8>
static jobject ListOfVectorsToArrayListOfByteArray(JNIEnv *env,
List<Vector<uint8_t> > list) {
jclass clazz;
FIND_CLASS(clazz, "java/util/ArrayList");
jclass clazz = gFields.arraylistClassId;
jobject arrayList = env->NewObject(clazz, gFields.arraylist.init);
List<Vector<uint8_t> >::iterator iter = list.begin();
while (iter != list.end()) {
@@ -542,6 +554,11 @@ static void android_media_MediaDrm_native_init(JNIEnv *env) {
GET_STATIC_FIELD_ID(field, clazz, "KEY_TYPE_RELEASE", "I");
gKeyTypes.kKeyTypeRelease = env->GetStaticIntField(clazz, field);
GET_STATIC_FIELD_ID(field, clazz, "CERTIFICATE_TYPE_NONE", "I");
gCertificateTypes.kCertificateTypeNone = env->GetStaticIntField(clazz, field);
GET_STATIC_FIELD_ID(field, clazz, "CERTIFICATE_TYPE_X509", "I");
gCertificateTypes.kCertificateTypeX509 = env->GetStaticIntField(clazz, field);
FIND_CLASS(clazz, "android/media/MediaDrm$KeyRequest");
GET_FIELD_ID(gFields.keyRequest.data, clazz, "mData", "[B");
GET_FIELD_ID(gFields.keyRequest.defaultUrl, clazz, "mDefaultUrl", "Ljava/lang/String;");
@@ -550,6 +567,11 @@ static void android_media_MediaDrm_native_init(JNIEnv *env) {
GET_FIELD_ID(gFields.provisionRequest.data, clazz, "mData", "[B");
GET_FIELD_ID(gFields.provisionRequest.defaultUrl, clazz, "mDefaultUrl", "Ljava/lang/String;");
FIND_CLASS(clazz, "android/media/MediaDrm$Certificate");
GET_FIELD_ID(gFields.certificate.wrappedPrivateKey, clazz, "mWrappedKey", "[B");
GET_FIELD_ID(gFields.certificate.certificateData, clazz, "mCertificateData", "[B");
gFields.certificateClassId = reinterpret_cast<jclass>(env->NewGlobalRef(clazz));
FIND_CLASS(clazz, "java/util/ArrayList");
GET_METHOD_ID(gFields.arraylist.init, clazz, "<init>", "()V");
GET_METHOD_ID(gFields.arraylist.add, clazz, "add", "(Ljava/lang/Object;)Z");
@@ -571,6 +593,15 @@ static void android_media_MediaDrm_native_init(JNIEnv *env) {
FIND_CLASS(clazz, "java/util/Map$Entry");
GET_METHOD_ID(gFields.entry.getKey, clazz, "getKey", "()Ljava/lang/Object;");
GET_METHOD_ID(gFields.entry.getValue, clazz, "getValue", "()Ljava/lang/Object;");
FIND_CLASS(clazz, "java/util/HashMap");
gFields.hashmapClassId = reinterpret_cast<jclass>(env->NewGlobalRef(clazz));
FIND_CLASS(clazz, "java/lang/String");
gFields.stringClassId = reinterpret_cast<jclass>(env->NewGlobalRef(clazz));
FIND_CLASS(clazz, "java/util/ArrayList");
gFields.arraylistClassId = reinterpret_cast<jclass>(env->NewGlobalRef(clazz));
}
static void android_media_MediaDrm_native_setup(
@@ -826,8 +857,8 @@ static jobject android_media_MediaDrm_queryKeyStatus(
return KeyedVectorToHashMap(env, infoMap);
}
static jobject android_media_MediaDrm_getProvisionRequest(
JNIEnv *env, jobject thiz) {
static jobject android_media_MediaDrm_getProvisionRequestNative(
JNIEnv *env, jobject thiz, jint jcertType, jstring jcertAuthority) {
sp<IDrm> drm = GetDrm(env, thiz);
if (drm == NULL) {
@@ -839,7 +870,17 @@ static jobject android_media_MediaDrm_getProvisionRequest(
Vector<uint8_t> request;
String8 defaultUrl;
status_t err = drm->getProvisionRequest(request, defaultUrl);
String8 certType;
if (jcertType == gCertificateTypes.kCertificateTypeX509) {
certType = "X.509";
} else if (jcertType == gCertificateTypes.kCertificateTypeNone) {
certType = "none";
} else {
certType = "invalid";
}
String8 certAuthority = JStringToString8(env, jcertAuthority);
status_t err = drm->getProvisionRequest(certType, certAuthority, request, defaultUrl);
if (throwExceptionAsNecessary(env, err, "Failed to get provision request")) {
return NULL;
@@ -863,27 +904,43 @@ static jobject android_media_MediaDrm_getProvisionRequest(
return provisionObj;
}
static void android_media_MediaDrm_provideProvisionResponse(
static jobject android_media_MediaDrm_provideProvisionResponseNative(
JNIEnv *env, jobject thiz, jbyteArray jresponse) {
sp<IDrm> drm = GetDrm(env, thiz);
if (drm == NULL) {
jniThrowException(env, "java/lang/IllegalStateException",
"MediaDrm obj is null");
return;
return NULL;
}
if (jresponse == NULL) {
jniThrowException(env, "java/lang/IllegalArgumentException",
"provision response is null");
return;
return NULL;
}
Vector<uint8_t> response(JByteArrayToVector(env, jresponse));
Vector<uint8_t> certificate, wrappedKey;
status_t err = drm->provideProvisionResponse(response);
status_t err = drm->provideProvisionResponse(response, certificate, wrappedKey);
// Fill out return obj
jclass clazz = gFields.certificateClassId;
jobject certificateObj = NULL;
if (clazz && certificate.size() && wrappedKey.size()) {
certificateObj = env->AllocObject(clazz);
jbyteArray jcertificate = VectorToJByteArray(env, certificate);
env->SetObjectField(certificateObj, gFields.certificate.certificateData, jcertificate);
jbyteArray jwrappedKey = VectorToJByteArray(env, wrappedKey);
env->SetObjectField(certificateObj, gFields.certificate.wrappedPrivateKey, jwrappedKey);
}
throwExceptionAsNecessary(env, err, "Failed to handle provision response");
return certificateObj;
}
static jobject android_media_MediaDrm_getSecureStops(
@@ -1209,6 +1266,38 @@ static jboolean android_media_MediaDrm_verifyNative(
}
static jbyteArray android_media_MediaDrm_signRSANative(
JNIEnv *env, jobject thiz, jobject jdrm, jbyteArray jsessionId,
jstring jalgorithm, jbyteArray jwrappedKey, jbyteArray jmessage) {
sp<IDrm> drm = GetDrm(env, jdrm);
if (!CheckSession(env, drm, jsessionId)) {
return NULL;
}
if (jalgorithm == NULL || jwrappedKey == NULL || jmessage == NULL) {
jniThrowException(env, "java/lang/IllegalArgumentException",
"required argument is null");
return NULL;
}
Vector<uint8_t> sessionId(JByteArrayToVector(env, jsessionId));
String8 algorithm = JStringToString8(env, jalgorithm);
Vector<uint8_t> wrappedKey(JByteArrayToVector(env, jwrappedKey));
Vector<uint8_t> message(JByteArrayToVector(env, jmessage));
Vector<uint8_t> signature;
status_t err = drm->signRSA(sessionId, algorithm, message, wrappedKey, signature);
if (throwExceptionAsNecessary(env, err, "Failed to sign")) {
return NULL;
}
return VectorToJByteArray(env, signature);
}
static JNINativeMethod gMethods[] = {
{ "release", "()V", (void *)android_media_MediaDrm_release },
{ "native_init", "()V", (void *)android_media_MediaDrm_native_init },
@@ -1244,11 +1333,11 @@ static JNINativeMethod gMethods[] = {
{ "queryKeyStatus", "([B)Ljava/util/HashMap;",
(void *)android_media_MediaDrm_queryKeyStatus },
{ "getProvisionRequest", "()Landroid/media/MediaDrm$ProvisionRequest;",
(void *)android_media_MediaDrm_getProvisionRequest },
{ "getProvisionRequestNative", "(ILjava/lang/String;)Landroid/media/MediaDrm$ProvisionRequest;",
(void *)android_media_MediaDrm_getProvisionRequestNative },
{ "provideProvisionResponse", "([B)V",
(void *)android_media_MediaDrm_provideProvisionResponse },
{ "provideProvisionResponseNative", "([B)Landroid/media/MediaDrm$Certificate;",
(void *)android_media_MediaDrm_provideProvisionResponseNative },
{ "getSecureStops", "()Ljava/util/List;",
(void *)android_media_MediaDrm_getSecureStops },
@@ -1287,6 +1376,9 @@ static JNINativeMethod gMethods[] = {
{ "verifyNative", "(Landroid/media/MediaDrm;[B[B[B[B)Z",
(void *)android_media_MediaDrm_verifyNative },
{ "signRSANative", "(Landroid/media/MediaDrm;[BLjava/lang/String;[B[B)[B",
(void *)android_media_MediaDrm_signRSANative },
};
int register_android_media_Drm(JNIEnv *env) {

View File

@@ -15,7 +15,7 @@
#
LOCAL_PATH := $(call my-dir)
# the library
# the remotedisplay library
# ============================================================
include $(CLEAR_VARS)
@@ -23,7 +23,7 @@ LOCAL_MODULE:= com.android.media.remotedisplay
LOCAL_MODULE_TAGS := optional
LOCAL_SRC_FILES := \
$(call all-subdir-java-files) \
$(call all-java-files-under, java) \
$(call all-aidl-files-under, java)
include $(BUILD_JAVA_LIBRARY)

View File

@@ -25,4 +25,3 @@ with the framework in a new API. That API isn't ready yet so this
library is a compromise to make new capabilities available to the system
without exposing the full surface area of the support library media
route provider protocol.

View File

@@ -0,0 +1,45 @@
#
# Copyright (C) 2013 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
LOCAL_PATH := $(call my-dir)
# the mediadrm signer library
# ============================================================
include $(CLEAR_VARS)
LOCAL_MODULE:= com.android.mediadrm.signer
LOCAL_MODULE_TAGS := optional
LOCAL_SRC_FILES := \
$(call all-java-files-under, java)
include $(BUILD_STATIC_JAVA_LIBRARY)
# ==== com.android.mediadrm.signer.xml lib def ========================
include $(CLEAR_VARS)
LOCAL_MODULE := com.android.mediadrm.signer.xml
LOCAL_MODULE_TAGS := optional
LOCAL_MODULE_CLASS := ETC
# This will install the file in /system/etc/permissions
#
LOCAL_MODULE_PATH := $(TARGET_OUT_ETC)/permissions
LOCAL_SRC_FILES := $(LOCAL_MODULE)
include $(BUILD_PREBUILT)

View File

@@ -0,0 +1,28 @@
This library (com.android.mediadrm.signer.jar) is a shared java library
containing classes required by unbundled apps running on devices that use
the certficate provisioning and private key signing capabilities provided
by the MediaDrm API.
--- Rules of this library ---
o This library is effectively a PUBLIC API for unbundled CAST receivers
that may be distributed outside the system image. So it MUST BE API STABLE.
You can add but not remove. The rules are the same as for the
public platform SDK API.
o This library can see and instantiate internal platform classes, but it must not
expose them in any public method (or by extending them via inheritance). This would
break clients of the library because they cannot see the internal platform classes.
This library is distributed in the system image, and loaded as
a shared library. So you can change the implementation, but not
the interface. In this way it is like framework.jar.
--- Why does this library exist? ---
Unbundled apps cannot use internal platform classes.
This library will eventually be replaced when the provisioned certificate-
based signing infrastructure that is currently defined in the support library
is reintegrated with the framework in a new API. That API isn't ready yet so
this library is a compromise to make new capabilities available to the system
without exposing the full surface area of the support library.

View File

@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2013 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<permissions>
<library name="com.android.media.drm.signer"
file="/system/framework/com.android.media.drm.signer.jar" />
</permissions>

View File

@@ -0,0 +1,139 @@
/*
* Copyright (C) 2013 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.mediadrm.signer;
import android.content.Context;
import android.media.MediaDrm;
import android.media.DeniedByServerException;
/**
* Provides certificate request generation, response handling and
* signing APIs
*/
public final class MediaDrmSigner {
private MediaDrmSigner() {}
/**
* Specify X.509 certificate type
*/
public static final int CERTIFICATE_TYPE_X509 = MediaDrm.CERTIFICATE_TYPE_X509;
/**
* Contains the opaque data an app uses to request a certificate from a provisioning
* server
*/
public final static class CertificateRequest {
private MediaDrm.CertificateRequest mCertRequest;
CertificateRequest(MediaDrm.CertificateRequest certRequest) {
mCertRequest = certRequest;
}
/**
* Get the opaque message data
*/
public byte[] getData() {
return mCertRequest.getData();
}
/**
* Get the default URL to use when sending the certificate request
* message to a server, if known. The app may prefer to use a different
* certificate server URL obtained from other sources.
*/
public String getDefaultUrl() {
return mCertRequest.getDefaultUrl();
}
}
/**
* Contains the wrapped private key and public certificate data associated
* with a certificate.
*/
public final static class Certificate {
private MediaDrm.Certificate mCertificate;
Certificate(MediaDrm.Certificate certificate) {
mCertificate = certificate;
}
/**
* Get the wrapped private key data
*/
public byte[] getWrappedPrivateKey() {
return mCertificate.getWrappedPrivateKey();
}
/**
* Get the PEM-encoded public certificate chain
*/
public byte[] getContent() {
return mCertificate.getContent();
}
}
/**
* Generate a certificate request, specifying the certificate type
* and authority. The response received should be passed to
* provideCertificateResponse.
*
* @param drm the MediaDrm object
* @param certType Specifies the certificate type.
* @param certAuthority is passed to the certificate server to specify
* the chain of authority.
*/
public static CertificateRequest getCertificateRequest(MediaDrm drm, int certType,
String certAuthority) {
return new CertificateRequest(drm.getCertificateRequest(certType, certAuthority));
}
/**
* Process a response from the provisioning server. The response
* is obtained from an HTTP Post to the url provided by getCertificateRequest.
*
* The public X509 certificate chain and wrapped private key are returned
* in the returned Certificate objec. The certificate chain is in BIO serialized
* PEM format. The wrapped private key should be stored in application private
* storage, and used when invoking the signRSA method.
*
* @param drm the MediaDrm object
* @param response the opaque certificate response byte array to provide to the
* DRM engine plugin.
* @throws android.media.DeniedByServerException if the response indicates that the
* server rejected the request
*/
public static Certificate provideCertificateResponse(MediaDrm drm, byte[] response)
throws DeniedByServerException {
return new Certificate(drm.provideCertificateResponse(response));
}
/**
* Sign data using an RSA key
*
* @param context the App context
* @param drm the MediaDrm object
* @param sessionId a sessionId obtained from openSession on the MediaDrm object
* @param algorithm the signing algorithm to use, e.g. "PKCS1-BlockType1"
* @param wrappedKey - the wrapped (encrypted) RSA private key obtained
* from provideCertificateResponse
* @param message the data for which a signature is to be computed
*/
public static byte[] signRSA(Context context, MediaDrm drm, byte[] sessionId,
String algorithm, byte[] wrappedKey, byte[] message) {
return drm.signRSA(context, sessionId, algorithm, wrappedKey, message);
}
}